feat: App switcher

This commit is contained in:
Rushabh Mehta 2024-09-02 21:55:13 +05:30
parent 5c820a1e79
commit 8dbc2832c5
8 changed files with 176 additions and 37 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 626 B

View file

@ -146,6 +146,32 @@ def load_desktop_data(bootinfo):
bootinfo.sidebar_pages = get_workspace_sidebar_items() bootinfo.sidebar_pages = get_workspace_sidebar_items()
bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces() bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces()
bootinfo.dashboards = frappe.get_all("Dashboard") bootinfo.dashboards = frappe.get_all("Dashboard")
bootinfo.app_data = []
Workspace = frappe.qb.DocType("Workspace")
Module = frappe.qb.DocType("Module Def")
for app_name in frappe.get_installed_apps():
bootinfo.app_data.append(
dict(
app_name=app_name,
app_title=frappe.get_hooks("app_title", app_name=app_name),
app_home=frappe.get_hooks("app_home", app_name=app_name),
app_logo_url=frappe.get_hooks("app_logo_url", app_name=app_name),
modules=[m.name for m in frappe.get_all("Module Def", dict(app_name=app_name))],
workspaces=[
r[0]
for r in (
frappe.qb.from_(Workspace)
.inner_join(Module)
.on(Workspace.module == Module.name)
.select(Workspace.name)
.where(Module.app_name == app_name)
.run()
)
],
)
)
def get_allowed_pages(cache=False): def get_allowed_pages(cache=False):

View file

@ -18,6 +18,9 @@ frappe.ui.form.on("Workspace Settings", {
frm.docfields.push({ frm.docfields.push({
fieldtype: "Check", fieldtype: "Check",
fieldname: page.name, fieldname: page.name,
hidden: !frappe.boot.app_data_map[frappe.current_app].workspaces.includes(
page.title
),
label: page.title + (page.parent_page ? ` (${page.parent_page})` : ""), label: page.title + (page.parent_page ? ` (${page.parent_page})` : ""),
initial_value: workspace_visibilty[page.name] !== 0, // not set is also visible initial_value: workspace_visibilty[page.name] !== 0, // not set is also visible
}); });
@ -38,7 +41,7 @@ frappe.ui.form.on("Workspace Settings", {
}, },
after_save(frm) { after_save(frm) {
// reload page to show latest sidebar // reload page to show latest sidebar
window.location.reload(); frappe.app.sidebar.reload();
}, },
refresh(frm) { refresh(frm) {
let get_page = (e) => frm.workspace_map[e.fieldobj.df.fieldname]; let get_page = (e) => frm.workspace_map[e.fieldobj.df.fieldname];

View file

@ -3,12 +3,13 @@ import os
from . import __version__ as app_version from . import __version__ as app_version
app_name = "frappe" app_name = "frappe"
app_title = "Frappe Framework" app_title = "Framework"
app_publisher = "Frappe Technologies" app_publisher = "Frappe Technologies"
app_description = "Full stack web framework with Python, Javascript, MariaDB, Redis, Node" app_description = "Full stack web framework with Python, Javascript, MariaDB, Redis, Node"
app_license = "MIT" app_license = "MIT"
app_logo_url = "/assets/frappe/images/frappe-framework-logo.svg" app_logo_url = "/assets/frappe/images/frappe-framework-logo.svg"
develop_version = "15.x.x-develop" develop_version = "15.x.x-develop"
app_home = "/app/build"
app_email = "developers@frappe.io" app_email = "developers@frappe.io"

View file

@ -1,5 +1,4 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg fill="none" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg">
<path d="M0 12C0 5.37258 5.37258 0 12 0H88C94.6274 0 100 5.37258 100 12V88C100 94.6274 94.6274 100 88 100H12C5.37258 100 0 94.6274 0 88V12Z" fill="#171717"/> <path d="M35.7143 0H14.2857C6.39593 0 0 6.39593 0 14.2857V35.7143C0 43.6041 6.39593 50 14.2857 50H35.7143C43.6041 50 50 43.6041 50 35.7143V14.2857C50 6.39593 43.6041 0 35.7143 0Z" fill="#7B808A"></path>
<path d="M68.4319 25H35V33.794H68.4319V25Z" fill="white"/> <path d="M25 10.7141L12.625 17.8569V19.9284L16.1964 21.9819L23.2143 26.0176V34.1249L16.1964 30.0712V26.6248L12.625 24.5177V32.1249L25 39.2677L37.375 32.1249V17.8391L25 10.6963V10.7141ZM17.9821 18.8927L25 14.8391L32.0178 18.8927L25 22.9463L17.9821 18.8927ZM33.8035 30.0891L26.7857 34.1427V26.0355L33.8035 21.9819V30.0891Z" fill="white"></path>
<path d="M35 47.4357V75H45.671V56.2445H66.2149V47.4357H35Z" fill="white"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 399 B

After

Width:  |  Height:  |  Size: 626 B

View file

@ -33,29 +33,46 @@ frappe.ui.Sidebar = class Sidebar {
} }
make_dom() { make_dom() {
this.set_default_app();
this.wrapper = $(` this.wrapper = $(`
<div class="body-sidebar-container"> <div class="body-sidebar-container">
<div class="body-sidebar-placeholder"></div> <div class="body-sidebar-placeholder"></div>
<div class="body-sidebar"> <div class="body-sidebar">
<a href="/app" style="text-decoration: none"> <a class="app-switcher-dropdown"
<div class="standard-sidebar-item"> style="text-decoration: none; width: 100%; position: relative;">
<div class="sidebar-item-icon">
<img <div class="standard-sidebar-item">
class="app-logo" <div class="d-flex">
src="${frappe.boot.app_logo_url}" <div class="sidebar-item-icon">
alt="${__("App Logo")}" <img
> class="app-logo"
</div> src="${frappe.boot.app_data[0].app_logo_url}"
<div class="sidebar-item-label" style="margin-left: 5px; margin-top: 1px"> alt="${__("App Logo")}"
${__(frappe.boot.sysdefaults.app_name)} >
</div>
<div class="sidebar-item-label" style="margin-left: 5px; margin-top: 1px">
${__(frappe.boot.app_data[0].app_title)}
</div>
</div>
<div class="sidebar-item-control">
<button class="btn-reset drop-icon show-in-edit-mode">
<svg class="es-icon es-line icon-sm" style="" aria-hidden="true">
<use class="" href="#es-line-down"></use>
</svg>
</button>
</div>
</div> </div>
</a>
<div class="app-switcher-menu hidden" role="menu">
</div>
<div class="sidebar-items">
</div>
<div class="mb-4">
<a class="edit-sidebar-link text-extra-muted">Edit sidebar</a>
</div> </div>
</a>
<div class="sidebar-items">
</div>
<div class="mb-4">
<a class="edit-sidebar-link text-extra-muted">Edit sidebar</a>
</div> </div>
</div> </div>
</div> </div>
`).prependTo("body"); `).prependTo("body");
@ -65,6 +82,70 @@ frappe.ui.Sidebar = class Sidebar {
this.wrapper.find(".body-sidebar .edit-sidebar-link").on("click", () => { this.wrapper.find(".body-sidebar .edit-sidebar-link").on("click", () => {
frappe.quick_edit("Workspace Settings"); frappe.quick_edit("Workspace Settings");
}); });
this.setup_app_switcher();
}
set_default_app() {
// sort apps based on # of workspaces
frappe.boot.app_data.sort((a, b) => (a.workspaces.length < b.workspaces.length ? 1 : -1));
frappe.current_app = frappe.boot.app_data[0].app_name;
}
setup_app_switcher() {
let app_switcher_menu = $(".app-switcher-menu");
$(".app-switcher-dropdown").on("click", () => {
app_switcher_menu.toggleClass("hidden");
});
// hover out of the sidebar
this.wrapper.find(".body-sidebar").on("mouseleave", () => {
app_switcher_menu.addClass("hidden");
// hide any expanded menus as they leave a blank space in the sidebar
this.wrapper.find(".drop-icon[data-state='opened'").click();
});
frappe.boot.app_data_map = {};
for (var app of frappe.boot.app_data) {
frappe.boot.app_data_map[app.app_name] = app;
if (app.workspaces?.length) {
$(`<div class="app-item" data-app-name="${app.app_name}"
data-app-home="${app.app_home}">
<a>
<div class="sidebar-item-icon">
<img
style="margin-right: var(--margin-sm);"
class="app-logo"
src="${app.app_logo_url}"
alt="${__("App Logo")}"
>
</div>
<span>${app.app_title}</span>
</a>
</div>`).appendTo(app_switcher_menu);
}
}
app_switcher_menu.find(".app-item").on("click", (e) => {
let item = $(e.delegateTarget);
frappe.current_app = item.attr("data-app-name");
frappe.set_route(item.attr("data-app-home"));
this.wrapper
.find(".app-switcher-dropdown .sidebar-item-icon img")
.attr("src", frappe.boot.app_data_map[frappe.current_app].app_logo_url);
this.wrapper
.find(".app-switcher-dropdown .sidebar-item-label")
.html(frappe.boot.app_data_map[frappe.current_app].app_title);
// hide menu
app_switcher_menu.toggleClass("hidden");
// re-render the sidebar
this.make_sidebar();
});
} }
setup_pages() { setup_pages() {
@ -97,10 +178,12 @@ frappe.ui.Sidebar = class Sidebar {
this.wrapper.find(".standard-sidebar-section").remove(); 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.title); let parent_pages = this.all_pages.filter((p) => !p.parent_page).uniqBy((p) => p.title);
parent_pages = [ parent_pages = [
...parent_pages.filter((p) => !p.public), ...parent_pages.filter((p) => !p.public && app_workspaces.includes(p.title)),
...parent_pages.filter((p) => p.public), ...parent_pages.filter((p) => p.public && app_workspaces.includes(p.title)),
]; ];
this.build_sidebar_section("All", parent_pages); this.build_sidebar_section("All", parent_pages);
@ -238,12 +321,22 @@ frappe.ui.Sidebar = class Sidebar {
$drop_icon.removeClass("hidden"); $drop_icon.removeClass("hidden");
} }
$drop_icon.on("click", () => { $drop_icon.on("click", () => {
let icon = let opened = $drop_icon.find("use").attr("href") === "#es-line-down";
$drop_icon.find("use").attr("href") === "#es-line-down"
? "#es-line-up" if (!opened) {
: "#es-line-down"; $drop_icon.attr("data-state", "closed").find("use").attr("href", "#es-line-down");
$drop_icon.find("use").attr("href", icon); } else {
$drop_icon.attr("data-state", "opened").find("use").attr("href", "#es-line-up");
}
``;
$child_item_section.toggleClass("hidden"); $child_item_section.toggleClass("hidden");
}); });
} }
reload() {
return frappe.workspace.get_pages().then((r) => {
frappe.boot.sidebar_pages = r;
this.setup_pages();
});
}
}; };

View file

@ -43,9 +43,6 @@ frappe.ui.toolbar.Toolbar = class {
search_modal.find("#modal-search").focus(); search_modal.find("#modal-search").focus();
}, 300); }, 300);
}); });
$(".navbar-toggle-full-width").click(() => {
frappe.ui.toolbar.toggle_full_width();
});
} }
setup_read_only_mode() { setup_read_only_mode() {

View file

@ -192,6 +192,27 @@ body {
} }
} }
.app-switcher-menu {
position: absolute;
top: 40px;
padding: var(--padding-xs);
box-shadow: var(--shadow-base);
background-color: var(--neutral);
min-width: 180px;
z-index: 1;
border-radius: var(--border-radius-tiny);
}
.app-item {
padding: var(--padding-xs);
a {
text-decoration: none;
display: flex;
vertical-align: middle;
}
}
// form sidebar // form sidebar
.form-sidebar { .form-sidebar {
.sidebar-section { .sidebar-section {