feat: export and sync workspace sidebar and desktop icon

This commit is contained in:
sokumon 2025-09-30 10:25:00 +05:30
parent 1209487274
commit 9658ca1642
12 changed files with 133 additions and 33 deletions

View file

@ -529,26 +529,27 @@ def get_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] = []
if frappe.db.get_value("Desktop Icon", w.desktop_icon, "label"):
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,
}
if si.link_type == "Report":
report_type, ref_doctype = frappe.db.get_value(
"Report", si.link_to, ["report_type", "ref_doctype"]
)
workspace_sidebar["report"] = {
"report_type": report_type,
"ref_doctype": ref_doctype,
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)
if si.link_type == "Report":
report_type, ref_doctype = frappe.db.get_value(
"Report", si.link_to, ["report_type", "ref_doctype"]
)
workspace_sidebar["report"] = {
"report_type": report_type,
"ref_doctype": ref_doctype,
}
sidebar_items[desktop_icon].append(workspace_sidebar)
return sidebar_items

View file

@ -707,3 +707,8 @@ def update_onboarding_step(name, field, value):
frappe.db.set_value("Onboarding Step", name, field, value)
capture(frappe.scrub(name), app="frappe_onboarding", properties={field: value})
@frappe.whitelist()
def get_installed_apps():
return frappe.get_installed_apps()

View file

@ -2,7 +2,8 @@
// For license information, please see license.txt
frappe.ui.form.on("Desktop Icon", {
refresh: function (frm) {
setup: function (frm) {
load_installed_apps();
frm.fields_dict.color.set_data(Object.keys(frappe.palette_map));
},
before_save: function (frm) {
@ -33,3 +34,14 @@ frappe.ui.form.on("Desktop Icon", {
}
},
});
async function load_installed_apps(frm) {
await frappe.call({
method: "frappe.desk.desktop.get_installed_apps",
callback: function (r) {
if (r.message) {
cur_frm.fields_dict["app"].set_data(r.message);
}
},
});
}

View file

@ -60,9 +60,8 @@
},
{
"fieldname": "app",
"fieldtype": "Data",
"label": "App",
"read_only": 1
"fieldtype": "Autocomplete",
"label": "App"
},
{
"fieldname": "description",
@ -166,7 +165,7 @@
}
],
"links": [],
"modified": "2025-09-08 02:43:09.997790",
"modified": "2025-09-29 01:47:25.718356",
"modified_by": "Administrator",
"module": "Desk",
"name": "Desktop Icon",

View file

@ -2,11 +2,14 @@
# License: MIT. See LICENSE
import json
import os
import random
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.modules.export_file import write_document_file
from frappe.modules.import_file import import_file_by_path
class DesktopIcon(Document):
@ -20,7 +23,7 @@ class DesktopIcon(Document):
_doctype: DF.Link | None
_report: DF.Link | None
app: DF.Data | None
app: DF.Autocomplete | None
blocked: DF.Check
category: DF.Data | None
color: DF.Autocomplete | None
@ -46,11 +49,49 @@ class DesktopIcon(Document):
def on_trash(self):
clear_desktop_icons_cache()
if frappe.conf.developer_mode:
if self.standard == 1 and self.app:
self.delete_desktop_icon_file()
def on_update(self):
if frappe.conf.developer_mode:
if self.standard == 1 and self.app:
self.export_desktop_icon()
def export_desktop_icon(self):
folder_path = create_directory_if_not_exists(self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.label)}.json")
doc_export = self.as_dict(no_nulls=True, no_private_properties=True)
with open(file_path, "w+") as icon_file_doc:
icon_file_doc.write(frappe.as_json(doc_export) + "\n")
def delete_desktop_icon_file(self):
folder_path = create_directory_if_not_exists(self.app)
file_path = os.path.join(folder_path, f"{frappe.scrub(self.label)}.json")
if not os.path.exists(file_path):
os.remove(file_path)
def after_insert(self):
clear_desktop_icons_cache()
def create_directory_if_not_exists(app_name):
app_path = frappe.get_app_path(app_name)
desktop_icon_path = os.path.join(app_path, "desktop_icon")
if not os.path.exists(desktop_icon_path):
frappe.create_folder(desktop_icon_path)
return desktop_icon_path
def get_desktop_icon_directory(app_name):
app_path = frappe.get_app_path(app_name)
desktop_icon_path = os.path.join(app_path, "desktop_icon")
return desktop_icon_path
def after_doctype_insert():
frappe.db.add_unique("Desktop Icon", ("module_name", "owner", "standard"))
@ -401,7 +442,19 @@ def make_user_copy(module_name, user):
def sync_desktop_icons():
"""Sync desktop icons from all apps"""
for app in frappe.get_installed_apps():
sync_from_app(app)
sync_icons(app)
# sync_from_app(app)
def sync_icons(app_name):
icon_directory = get_desktop_icon_directory(app_name)
if os.path.exists(icon_directory):
icon_files = [os.path.join(icon_directory, filename) for filename in os.listdir(icon_directory)]
for doc_path in icon_files:
imported = import_file_by_path(doc_path)
if imported:
frappe.db.commit(chain=True)
# print(icon_directory)
def sync_from_app(app):

View file

@ -8,6 +8,7 @@
"field_order": [
"desktop_icon",
"title",
"module",
"items"
],
"fields": [
@ -30,12 +31,18 @@
"fieldtype": "Table",
"label": "Items",
"options": "Workspace Sidebar Item"
},
{
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"options": "Module Def"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-08-12 12:48:17.192321",
"modified": "2025-09-26 02:16:29.097807",
"modified_by": "Administrator",
"module": "Desk",
"name": "Workspace Sidebar",

View file

@ -1,8 +1,11 @@
# Copyright (c) 2025, Frappe Technologies and contributors
# For license information, please see license.txt
# import frappe
import frappe
from frappe import _
from frappe.desk.doctype.workspace.workspace import is_workspace_manager
from frappe.model.document import Document
from frappe.modules.export_file import delete_folder, export_to_files
class WorkspaceSidebar(Document):
@ -17,7 +20,18 @@ class WorkspaceSidebar(Document):
desktop_icon: DF.Link | None
items: DF.Table[WorkspaceSidebarItem]
module: DF.Link | None
title: DF.Data | None
# end: auto-generated types
pass
def on_update(self):
if self.module and frappe.conf.developer_mode:
export_to_files(record_list=[["Workspace Sidebar", self.name]], record_module=self.module)
def on_trash(self):
if not is_workspace_manager():
frappe.throw(_("You need to be Workspace Manager to delete a public workspace."))
def after_delete(self):
if self.module and frappe.conf.developer_mode:
delete_folder(self.module, "Workspace Sidebar", self.name)

View file

@ -1,6 +1,5 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-08-12 12:46:41.926121",
"doctype": "DocType",
"editable_grid": 1,
@ -61,7 +60,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2025-09-03 11:58:10.222194",
"modified": "2025-09-29 02:21:36.962559",
"modified_by": "Administrator",
"module": "Desk",
"name": "Workspace Sidebar Item",

View file

@ -85,7 +85,8 @@ function setup_navbar(navbar_style) {
}
frappe.router.on("change", function () {
if (frappe.get_route()[0] == "desktop") setup_navbar();
let navbar_style = $("#icon-style").attr("data-navbar-style");
if (frappe.get_route()[0] == "desktop") setup_navbar(navbar_style);
else $(".navbar").show();
});

View file

@ -17,6 +17,7 @@ from semantic_version import Version
import frappe
from frappe.defaults import _clear_cache
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.utils import cint, is_git_url
from frappe.utils.dashboard import sync_dashboards
from frappe.utils.synchronization import filelock
@ -337,6 +338,7 @@ def install_app(name, verbose=False, set_as_patched=True, force=False):
sync_fixtures(name)
sync_customizations(name)
sync_dashboards(name)
sync_desktop_icons()
for after_sync in app_hooks.after_sync or []:
frappe.get_attr(after_sync)() #

View file

@ -21,6 +21,7 @@ from frappe.core.doctype.navbar_settings.navbar_settings import sync_standard_it
from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs
from frappe.database.schema import add_column
from frappe.deferred_insert import save_to_db as flush_deferred_inserts
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.desk.notifications import clear_notifications
from frappe.modules.patch_handler import PatchType
from frappe.modules.utils import sync_customizations
@ -188,6 +189,9 @@ class SiteMigration:
print("Updating installed applications...")
frappe.get_single("Installed Applications").update_versions()
print("Syncing Desktop Icons...")
sync_desktop_icons()
print("Executing `after_migrate` hooks...")
for app in frappe.get_installed_apps():
for fn in frappe.get_hooks("after_migrate", app_name=app):

View file

@ -9,6 +9,7 @@ import os
import frappe
from frappe.cache_manager import clear_controller_cache
from frappe.desk.doctype.desktop_icon.desktop_icon import sync_desktop_icons
from frappe.model.base_document import get_controller
from frappe.modules.import_file import import_file_by_path
from frappe.modules.patch_handler import _patch_mode
@ -27,6 +28,7 @@ IMPORTABLE_DOCTYPES = [
("email", "notification"),
("printing", "print_style"),
("desk", "workspace"),
("desk", "workspace_sidebar"),
("desk", "onboarding_step"),
("desk", "module_onboarding"),
("desk", "form_tour"),
@ -39,7 +41,6 @@ IMPORTABLE_DOCTYPES = [
def sync_all(force=0, reset_permissions=False):
_patch_mode(True)
for app in frappe.get_installed_apps():
sync_for(app, force, reset_permissions=reset_permissions)
@ -93,6 +94,8 @@ def sync_for(app_name, force=0, reset_permissions=False):
"workspace_number_card",
"workspace_custom_block",
"workspace",
"workspace_sidebar",
"workspace_sidebar_item",
]:
files.append(os.path.join(FRAPPE_PATH, "desk", "doctype", desk_module, f"{desk_module}.json"))