diff --git a/frappe/boot.py b/frappe/boot.py index 6d8781c550..c312ea6eb8 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -14,6 +14,7 @@ from frappe.core.doctype.installed_applications.installed_applications import ( ) from frappe.core.doctype.navbar_settings.navbar_settings import get_app_logo, get_navbar_settings from frappe.desk.doctype.changelog_feed.changelog_feed import get_changelog_feed_items +from frappe.desk.doctype.desktop_icon.desktop_icon import get_desktop_icons from frappe.desk.doctype.form_tour.form_tour import get_onboarding_ui_tours from frappe.desk.doctype.route_history.route_history import frequently_visited_links from frappe.desk.form.load import get_meta_bundle @@ -148,8 +149,11 @@ def load_conf_settings(bootinfo): def load_desktop_data(bootinfo): from frappe.desk.desktop import get_workspace_sidebar_items - bootinfo.sidebar_pages = get_workspace_sidebar_items() - allowed_pages = [d.name for d in bootinfo.sidebar_pages.get("pages")] + bootinfo.desktop_icons = get_desktop_icons() + bootinfo.workspaces = get_workspace_sidebar_items() + bootinfo.workspace_sidebar_item = get_sidebar_items() + print(bootinfo.workspace_sidebar_item) + allowed_pages = [d.name for d in bootinfo.workspaces.get("pages")] bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces() bootinfo.dashboards = frappe.get_all("Dashboard") bootinfo.app_data = [] @@ -518,3 +522,26 @@ def get_sentry_dsn(): return return os.getenv("FRAPPE_SENTRY_DSN") + + +def get_sidebar_items(): + sidebars = frappe.get_all("Workspace Sidebar", pluck="name") + sidebar_items = {} + + for s in sidebars: + w = frappe.get_doc("Workspace Sidebar", s) + desktop_icon = frappe.db.get_value("Desktop Icon", w.desktop_icon, "label").lower() + sidebar_items[desktop_icon] = [] + + for si in w.items: + workspace_sidebar = { + "label": si.label, + "link_to": si.link_to, + "link_type": si.link_type, + "type": si.type, + "icon": si.icon, + "child": si.child, + } + sidebar_items[desktop_icon].append(workspace_sidebar) + + return sidebar_items diff --git a/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.json b/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.json index 6a0fb51580..b301d77357 100644 --- a/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.json +++ b/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.json @@ -9,7 +9,9 @@ "label", "type", "link_type", - "link_to" + "link_to", + "icon", + "child" ], "fields": [ { @@ -39,13 +41,26 @@ "in_list_view": 1, "label": "Link To", "options": "link_type" + }, + { + "fieldname": "icon", + "fieldtype": "Icon", + "in_list_view": 1, + "label": "Icon" + }, + { + "default": "0", + "fieldname": "child", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Child Item" } ], "grid_page_length": 50, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2025-08-12 12:55:03.654000", + "modified": "2025-08-18 03:41:56.405534", "modified_by": "Administrator", "module": "Desk", "name": "Workspace Sidebar Item", diff --git a/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.py b/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.py index b6e7a58680..ba42715839 100644 --- a/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.py +++ b/frappe/desk/doctype/workspace_sidebar_item/workspace_sidebar_item.py @@ -14,6 +14,7 @@ class WorkspaceSidebarItem(Document): if TYPE_CHECKING: from frappe.types import DF + child: DF.Check label: DF.Data | None link_to: DF.DynamicLink | None link_type: DF.Literal["Page", "DocType", "Report"] diff --git a/frappe/public/js/desk.bundle.js b/frappe/public/js/desk.bundle.js index e6cfc228e6..83b2942463 100644 --- a/frappe/public/js/desk.bundle.js +++ b/frappe/public/js/desk.bundle.js @@ -10,6 +10,7 @@ import "./frappe/ui/messages.js"; import "./frappe/ui/keyboard.js"; import "./frappe/ui/colors.js"; import "./frappe/ui/sidebar.html"; +import "./frappe/ui/sidebar_item.html"; import "./frappe/ui/sidebar.js"; import "./frappe/ui/sidebar_header.js"; import "./frappe/ui/sidebar_header.html"; diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 9b5f095144..3e7bf70ccc 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -292,7 +292,7 @@ frappe.Application = class Application { setup_workspaces() { frappe.modules = {}; frappe.workspaces = {}; - frappe.boot.allowed_workspaces = frappe.boot.sidebar_pages.pages; + frappe.boot.allowed_workspaces = frappe.boot.workspaces.pages; for (let page of frappe.boot.allowed_workspaces || []) { frappe.modules[page.module] = page; diff --git a/frappe/public/js/frappe/ui/sidebar.js b/frappe/public/js/frappe/ui/sidebar.js index 6c8d7fd08b..b170388e07 100644 --- a/frappe/public/js/frappe/ui/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar.js @@ -3,14 +3,14 @@ frappe.ui.Sidebar = class Sidebar { this.items = {}; this.parent_items = []; this.sidebar_expanded = false; - + this.workspace_sidebar_items = []; if (!frappe.boot.setup_complete) { // no sidebar if setup is not complete return; } this.set_all_pages(); - this.make_dom(); + // this.make_dom(); this.sidebar_items = { public: {}, private: {}, @@ -31,12 +31,14 @@ frappe.ui.Sidebar = class Sidebar { ]; this.setup_pages(); - this.apps_switcher.populate_apps_menu(); this.handle_outside_click(); } - + setup(workspace_title) { + this.make_dom(); + this.apps_switcher = new frappe.ui.SidebarHeader(this, workspace_title); + this.make_sidebar(workspace_title.toLowerCase()); + } make_dom() { - this.set_default_app(); this.wrapper = $(frappe.render_template("sidebar")).prependTo("body"); this.$sidebar = this.wrapper.find(".sidebar-items"); @@ -64,10 +66,7 @@ frappe.ui.Sidebar = class Sidebar { } set_all_pages() { - this.sidebar_pages = frappe.boot.sidebar_pages; - this.all_pages = this.sidebar_pages.pages; - this.has_access = this.sidebar_pages.has_access; - this.has_create_access = this.sidebar_pages.has_create_access; + this.sidebar_items = frappe.boot.workspace_sidebar_item; } set_default_app() { @@ -78,21 +77,7 @@ frappe.ui.Sidebar = class Sidebar { } set_active_workspace_item() { - if (!frappe.get_route()) return; - let current_route = frappe.get_route(); - let current_route_str = frappe.get_route_str(); - let current_item; - if (current_route[0] == "Workspaces") { - current_item = current_route[1]; - } else if (frappe.breadcrumbs) { - if (Object.keys(frappe.breadcrumbs.all).length == 0) return; - if (frappe.breadcrumbs.all[current_route_str]) { - current_item = - frappe.breadcrumbs.all[current_route_str].workspace || - frappe.breadcrumbs.all[current_route_str].module; - } - } - if (this.is_route_in_sidebar(current_item)) { + if (this.is_route_in_sidebar()) { this.active_item.addClass("active-sidebar"); } if (this.active_item) { @@ -135,11 +120,12 @@ frappe.ui.Sidebar = class Sidebar { }); return sidebar_item; } + is_route_in_sidebar(active_module) { let match = false; const that = this; $(".item-anchor").each(function () { - if ($(this).attr("title") == active_module) { + if ($(this).attr("href") == window.location.pathname) { match = true; if (that.active_item) that.active_item.removeClass("active-sidebar"); that.active_item = $(this).parent(); @@ -152,13 +138,6 @@ frappe.ui.Sidebar = class Sidebar { setup_pages() { this.set_all_pages(); - this.all_pages.forEach((page) => { - page.is_editable = !page.public || this.has_access; - if (typeof page.content == "string") { - page.content = JSON.parse(page.content); - } - }); - if (this.all_pages) { frappe.workspaces = {}; frappe.workspace_list = []; @@ -176,8 +155,8 @@ frappe.ui.Sidebar = class Sidebar { } this.make_sidebar(); } - this.set_hover(); - this.set_sidebar_state(); + // this.set_hover(); + // this.set_sidebar_state(); } set_sidebar_state() { this.sidebar_expanded = true; @@ -189,21 +168,31 @@ frappe.ui.Sidebar = class Sidebar { } this.expand_sidebar(); } - make_sidebar() { + make_sidebar(workspace_title) { if (this.wrapper.find(".standard-sidebar-section")[0]) { this.wrapper.find(".standard-sidebar-section").remove(); } - - let app_workspaces = frappe.boot.app_data_map[frappe.current_app || "frappe"].workspaces; - - let parent_pages = this.all_pages.filter((p) => !p.parent_page).uniqBy((p) => p.name); - if (frappe.current_app === "private") { - parent_pages = parent_pages.filter((p) => !p.public); + this.workspace_sidebar_items = frappe.boot.workspace_sidebar_item[workspace_title]; + let parent_pages = this.workspace_sidebar_items; + if (this.workspace_sidebar_items && this.workspace_sidebar_items.length > 0) { + this.workspace_sidebar_items.unshift({ + label: "Home", + icon: "home", + type: "Workspace", + route: `/app/${workspace_title}`, + }); } else { - parent_pages = parent_pages.filter((p) => p.public && app_workspaces.includes(p.name)); + this.workspace_sidebar_items = []; + this.workspace_sidebar_items[0] = { + label: "Home", + icon: "home", + type: "Workspace", + route: `/app/${workspace_title}`, + }; } - this.build_sidebar_section("All", parent_pages); + // this.build_sidebar_section("All", parent_pages); + this.create_sidebar(); // Scroll sidebar to selected page if it is not in viewport. this.wrapper.find(".selected").length && @@ -213,8 +202,21 @@ frappe.ui.Sidebar = class Sidebar { this.setup_sorting(); this.set_active_workspace_item(); this.set_hover(); + this.set_sidebar_state(); + } + create_sidebar() { + if (this.workspace_sidebar_items && this.workspace_sidebar_items.length > 0) { + let parent_links = this.workspace_sidebar_items.filter((f) => f.child !== 1); + parent_links.forEach((w) => { + this.append_item(w, this.wrapper.find(".sidebar-items")); + }); + } else { + let no_items_message = $( + "