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 = $( + "
No Sidebar Items
" + ); + this.wrapper.find(".sidebar-items").append(no_items_message); + } } - build_sidebar_section(title, root_pages) { let sidebar_section = $( `
` @@ -251,13 +253,14 @@ frappe.ui.Sidebar = class Sidebar { } // visibility not explicitly set to 0 - if (item.visibility !== 0) { + if (item.child !== 0) { this.append_item(item, child_container); } last_item = item; } child_container.appendTo(item_container); } + toggle_sidebar() { if (!this.sidebar_expanded) { this.open_sidebar(); @@ -265,6 +268,7 @@ frappe.ui.Sidebar = class Sidebar { this.close_sidebar(); } } + expand_sidebar() { let direction; if (this.sidebar_expanded) { @@ -295,28 +299,28 @@ frappe.ui.Sidebar = class Sidebar { let $item_container = this.sidebar_item_container(item); let sidebar_control = $item_container.find(".sidebar-item-control"); - let child_items = this.all_pages.filter( - (page) => page.parent_page == item.name || page.parent_page == item.title - ); - if (child_items.length > 0) { - let child_container = $item_container.find(".sidebar-child-item"); - child_container.addClass("hidden"); - this.prepare_sidebar(child_items, child_container, $item_container); - this.parent_items.push($item_container); + if (item.type == "Section Break") { + let current_index = this.workspace_sidebar_items.indexOf(item); + let child_items = this.workspace_sidebar_items + .slice(current_index) + .filter((page) => page.child == 1); + if (child_items.length > 0) { + let child_container = $item_container.find(".sidebar-child-item"); + child_container.addClass("hidden"); + this.prepare_sidebar(child_items, child_container, $item_container); + this.parent_items.push($item_container); + $item_container.find(".drop-icon").first().addClass("show-in-edit-mode"); + } } $item_container.appendTo(container); - this.sidebar_items[item.public ? "public" : "private"][item.name] = $item_container; + // this.sidebar_items[item.public ? "public" : "private"][item.name] = $item_container; - if ($item_container.parent().hasClass("hidden") && is_current_page) { + if ($item_container.parent().hasClass("hidden")) { $item_container.parent().toggleClass("hidden"); } this.add_toggle_children(item, sidebar_control, $item_container); - - if (child_items.length > 0) { - $item_container.find(".drop-icon").first().addClass("show-in-edit-mode"); - } } sidebar_item_container(item) { @@ -336,66 +340,32 @@ frappe.ui.Sidebar = class Sidebar { } } else if (item.type === "URL") { path = item.external_link; - } else { - if (item.public) { - path = "/app/" + frappe.router.slug(item.name); - } else { - path = "/app/private/" + frappe.router.slug(item.name.split("-")[0]); + } else if (item.type == "Workspace") { + path = "/app/" + frappe.router.slug(item.label); + if (item.route) { + path = item.route; } } - - return $(` - - `); + console.log(item); + return $( + frappe.render_template("sidebar_item", { + item: item, + path: path, + }) + ); } add_toggle_children(item, sidebar_control, item_container) { - let drop_icon = "es-line-down"; - if ( - this.current_page && - item_container.find(`[item-name="${this.current_page.name}"]`).length - ) { + let $child_item_section = item_container.find(".sidebar-child-item"); + let drop_icon = "es-line-up"; + if ($child_item_section.children() > 0) { drop_icon = "small-up"; } - - let $child_item_section = item_container.find(".sidebar-child-item"); let $drop_icon = $(`