diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 73819a1222..cc4ae4f8e3 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -10,8 +10,10 @@ context("Awesome Bar", () => { }); beforeEach(() => { - cy.get(".navbar .navbar-home").click(); - cy.findByPlaceholderText("Search or type a command (Ctrl + G)").as("awesome_bar"); + let txt = `Search or type a command (${ + window.navigator.platform === "MacIntel" ? "⌘" : "Ctrl" + } + G)`; + cy.findByPlaceholderText(txt).as("awesome_bar"); cy.get("@awesome_bar").type("{selectall}"); }); @@ -35,13 +37,13 @@ context("Awesome Bar", () => { cy.get("@awesome_bar").type("{enter}"); cy.get(".title-text").should("contain", "To Do"); cy.wait(400); // Wait a bit longer before checking the filter. - cy.get('[data-original-title="ID"] > input').should("have.value", "%test%"); + cy.get('[data-original-title="ID"]:visible > input').should("have.value", "%test%"); // filter preserved, now finds something else cy.visit("/app/todo"); cy.get(".title-text").should("contain", "To Do"); cy.wait(200); // Wait a bit longer before checking the filter. - cy.get('[data-original-title="ID"] > input').as("filter"); + cy.get('[data-original-title="ID"]:visible > input').as("filter"); cy.get("@filter").should("have.value", "%test%"); cy.get("@awesome_bar").type("anothertest in todo"); cy.wait(200); // Wait a bit longer before hitting enter. diff --git a/cypress/integration/form.js b/cypress/integration/form.js index 3ff00c7e49..1ddf365c71 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -71,6 +71,8 @@ context("Form", () => { let expectBackgroundColor = "rgb(255, 245, 245)"; cy.visit("/app/contact/new"); + cy.fill_field("company_name", "Test Company"); + cy.get('.frappe-control[data-fieldname="email_ids"]').as("table"); cy.get("@table").find("button.grid-add-row").click(); cy.get("@table").find("button.grid-add-row").click(); @@ -80,7 +82,6 @@ context("Form", () => { cy.get("@row1").find("input.input-with-feedback.form-control").as("email_input1"); cy.get("@email_input1").type(website_input, { waitForAnimations: false }); - cy.fill_field("company_name", "Test Company"); cy.get("@row2").click(); cy.get("@row2").find("input.input-with-feedback.form-control").as("email_input2"); diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js index 79800faa01..7c15ee1fab 100644 --- a/cypress/integration/workspace.js +++ b/cypress/integration/workspace.js @@ -20,6 +20,8 @@ context("Workspace 2.0", () => { cy.get(".codex-editor__redactor .ce-block"); cy.get(".btn-new-workspace").click(); cy.fill_field("title", "Test Private Page", "Data"); + cy.wait(300); + cy.get_open_dialog().find(".modal-header").click(); cy.get_open_dialog().find(".btn-primary").click(); @@ -29,7 +31,7 @@ context("Workspace 2.0", () => { "item-public", "0" ); - + cy.wait(300); cy.get('.standard-actions .btn-primary[data-label="Save"]').click(); cy.wait(300); cy.get('.sidebar-item-container[item-name="Test Private Page"]').should( @@ -52,6 +54,7 @@ context("Workspace 2.0", () => { cy.fill_field("title", "Test Child Page", "Data"); cy.fill_field("parent", "Test Private Page", "Select"); cy.get_open_dialog().find(".modal-header").click(); + cy.wait(300); cy.get_open_dialog().find(".btn-primary").click(); // check if sidebar item is added in pubic section @@ -60,7 +63,7 @@ context("Workspace 2.0", () => { "item-public", "0" ); - + cy.wait(300); cy.get('.standard-actions .btn-primary[data-label="Save"]').click(); cy.wait(300); cy.get('.sidebar-item-container[item-name="Test Child Page"]').should( @@ -72,84 +75,12 @@ context("Workspace 2.0", () => { cy.wait("@new_page"); }); - it("Duplicate Page", () => { - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.duplicate_page", - }).as("page_duplicated"); - - cy.get(".codex-editor__redactor .ce-block"); - cy.get(".btn-edit-workspace").click(); - - cy.get('.sidebar-item-container[item-name="Test Private Page"]').as("sidebar-item"); - - cy.get("@sidebar-item").find(".standard-sidebar-item").first().click(); - cy.get("@sidebar-item").find(".dropdown-btn").first().click(); - cy.get("@sidebar-item") - .find(".dropdown-list .dropdown-item") - .contains("Duplicate") - .first() - .click({ force: true }); - - cy.get_open_dialog().fill_field("title", "Duplicate Page", "Data"); - cy.click_modal_primary_button("Duplicate"); - - cy.wait("@page_duplicated"); - }); - - it("Drag Sidebar Item", () => { - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.sort_pages", - }).as("page_sorted"); - - cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as("sidebar-item"); - - cy.get("@sidebar-item").find(".standard-sidebar-item").first().click(); - cy.get("@sidebar-item").find(".drag-handle").first().move({ deltaX: 0, deltaY: 100 }); - - cy.get('.sidebar-item-container[item-name="Build"]').as("sidebar-item"); - - cy.get("@sidebar-item").find(".standard-sidebar-item").first().click(); - cy.get("@sidebar-item").find(".drag-handle").first().move({ deltaX: 0, deltaY: 100 }); - - cy.wait("@page_sorted"); - }); - - it("Edit Page Detail", () => { - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.update_page", - }).as("page_updated"); - - cy.get('.sidebar-item-container[item-name="Test Private Page"]').as("sidebar-item"); - - cy.get("@sidebar-item").find(".standard-sidebar-item").first().click(); - cy.get("@sidebar-item").find(".dropdown-btn").first().click(); - cy.get("@sidebar-item") - .find(".dropdown-list .dropdown-item") - .contains("Edit") - .first() - .click({ force: true }); - - cy.get_open_dialog().fill_field("title", " 1", "Data"); - cy.get_open_dialog().find('input[data-fieldname="is_public"]').check(); - cy.click_modal_primary_button("Update"); - - cy.get( - '.standard-sidebar-section:first .sidebar-item-container[item-name="Test Private Page"]' - ).should("not.exist"); - cy.get( - '.standard-sidebar-section:last .sidebar-item-container[item-name="Test Private Page 1"]' - ).should("exist"); - - cy.wait("@page_updated"); - }); - it("Add New Block", () => { - cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as("sidebar-item"); + cy.get('.sidebar-item-container[item-name="Test Private Page"]').as("sidebar-item"); - cy.get("@sidebar-item").find(".standard-sidebar-item").first().click(); + cy.get("@sidebar-item").find(".standard-sidebar-item").first().click({ force: true }); + + cy.get(".btn-edit-workspace").click({ force: true }); cy.get(".ce-block").click().type("{enter}"); cy.get(".block-list-container .block-list-item").contains("Heading").click(); @@ -184,71 +115,7 @@ context("Workspace 2.0", () => { cy.get(".ce-block:last").should("have.class", "col-xs-11"); cy.get(".ce-block:last .dropdown-item").contains("Expand").click(); cy.get(".ce-block:last").should("have.class", "col-xs-12"); - + cy.wait(300); cy.get('.standard-actions .btn-primary[data-label="Save"]').click(); }); - - it("Hide/Unhide Workspaces", () => { - // hide - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.hide_page", - }).as("hide_page"); - - cy.get(".codex-editor__redactor .ce-block"); - cy.get(".btn-edit-workspace").click(); - - cy.get('.sidebar-item-container[item-name="Duplicate Page"]') - .find(".sidebar-item-control .setting-btn") - .click(); - cy.get('.sidebar-item-container[item-name="Duplicate Page"]') - .find('.dropdown-item[title="Hide Workspace"]') - .click({ force: true }); - cy.wait(300); - cy.get('.standard-actions .btn-secondary[data-label="Discard"]').click(); - cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("not.be.visible"); - - cy.wait("@hide_page"); - - // unhide - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.unhide_page", - }).as("unhide_page"); - - cy.get(".codex-editor__redactor .ce-block"); - cy.get(".btn-edit-workspace").click(); - - cy.get('.sidebar-item-container[item-name="Duplicate Page"]') - .find('[title="Unhide Workspace"]') - .click({ force: true }); - cy.wait(300); - - cy.get('.standard-actions .btn-secondary[data-label="Discard"]').click(); - cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("be.visible"); - - cy.wait("@unhide_page"); - }); - - it("Delete Duplicate Page", () => { - cy.intercept({ - method: "POST", - url: "api/method/frappe.desk.doctype.workspace.workspace.delete_page", - }).as("page_deleted"); - - cy.get(".codex-editor__redactor .ce-block"); - cy.get(".btn-edit-workspace").click(); - - cy.get('.sidebar-item-container[item-name="Duplicate Page"]') - .find(".sidebar-item-control .setting-btn") - .click(); - cy.get('.sidebar-item-container[item-name="Duplicate Page"]') - .find('.dropdown-item[title="Delete Workspace"]') - .click({ force: true }); - cy.wait(300); - cy.get(".modal-footer > .standard-actions > .btn-modal-primary:visible").first().click(); - cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("not.exist"); - - cy.wait("@page_deleted"); - }); }); diff --git a/cypress/integration/workspace_blocks.js b/cypress/integration/workspace_blocks.js index e601efc9b3..959c05b8cb 100644 --- a/cypress/integration/workspace_blocks.js +++ b/cypress/integration/workspace_blocks.js @@ -29,7 +29,7 @@ context("Workspace Blocks", () => { "item-public", "0" ); - + cy.wait(300); cy.get('.standard-actions .btn-primary[data-label="Save"]').click(); cy.wait(300); cy.get('.sidebar-item-container[item-name="Test Block Page"]').should( diff --git a/frappe/boot.py b/frappe/boot.py index 94c34abce3..240924291b 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -143,7 +143,7 @@ def load_conf_settings(bootinfo): def load_desktop_data(bootinfo): from frappe.desk.desktop import get_workspace_sidebar_items - bootinfo.allowed_workspaces = get_workspace_sidebar_items().get("pages") + bootinfo.sidebar_pages = get_workspace_sidebar_items() bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces() bootinfo.dashboards = frappe.get_all("Dashboard") diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index c7199c925d..9bc28f6cfa 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -77,10 +77,6 @@ frappe.ui.form.on("DocType", { let msg = __( "This site is running in developer mode. Any change made here will be updated in code." ); - msg += "
"; - msg += __("If you just want to customize for your site, use {0} instead.", [ - customize_form_link, - ]); frm.dashboard.add_comment(msg, "yellow", true); } diff --git a/frappe/core/workspace/build/build.json b/frappe/core/workspace/build/build.json index 8ecce4c7e8..b3028f080b 100644 --- a/frappe/core/workspace/build/build.json +++ b/frappe/core/workspace/build/build.json @@ -7,7 +7,7 @@ "doctype": "Workspace", "for_user": "", "hide_custom": 0, - "icon": "tool", + "icon": "organization", "idx": 1, "is_hidden": 0, "label": "Build", @@ -301,7 +301,7 @@ "type": "Link" } ], - "modified": "2024-08-16 12:31:51.279839", + "modified": "2024-08-26 15:14:40.193261", "modified_by": "Administrator", "module": "Core", "name": "Build", @@ -312,7 +312,7 @@ "quick_lists": [], "restrict_to_domain": "", "roles": [], - "sequence_id": 27.0, + "sequence_id": 2.0, "shortcuts": [ { "color": "Grey", diff --git a/frappe/desk/doctype/workspace/workspace.js b/frappe/desk/doctype/workspace/workspace.js index b5f298c477..cd53041fe7 100644 --- a/frappe/desk/doctype/workspace/workspace.js +++ b/frappe/desk/doctype/workspace/workspace.js @@ -20,15 +20,11 @@ frappe.ui.form.on("Workspace", { .attr("target", "_blank"); frm.layout.message.empty(); - let message = __( - "This document allows you to edit limited fields. For all kinds of workspace customization, use the Edit button located on the workspace page" - ); + let message = __("Please click Edit on the Workspace for best results"); if ( - frm.doc.for_user || - (frm.doc.public && - !frm.has_perm("write") && - !frappe.user.has_role("Workspace Manager")) + (frm.doc.for_user && frm.doc.for_user !== frappe.session.user) || + (frm.doc.public && !frappe.user.has_role("Workspace Manager")) ) { frm.trigger("disable_form"); diff --git a/frappe/desk/doctype/workspace/workspace.json b/frappe/desk/doctype/workspace/workspace.json index 05f33e5ae0..c9c68137fe 100644 --- a/frappe/desk/doctype/workspace/workspace.json +++ b/frappe/desk/doctype/workspace/workspace.json @@ -77,8 +77,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Module", - "options": "Module Def", - "read_only": 1 + "options": "Module Def" }, { "fieldname": "column_break_3", @@ -101,8 +100,7 @@ { "fieldname": "for_user", "fieldtype": "Data", - "label": "For User", - "read_only": 1 + "label": "For User" }, { "default": "0", @@ -114,8 +112,7 @@ { "fieldname": "icon", "fieldtype": "Icon", - "label": "Icon", - "read_only": 1 + "label": "Icon" }, { "fieldname": "links", @@ -137,7 +134,6 @@ "fieldname": "title", "fieldtype": "Data", "label": "Title", - "read_only": 1, "reqd": 1 }, { @@ -156,8 +152,7 @@ { "fieldname": "sequence_id", "fieldtype": "Float", - "label": "Sequence Id", - "read_only": 1 + "label": "Sequence Id" }, { "fieldname": "roles", @@ -219,7 +214,7 @@ ], "in_create": 1, "links": [], - "modified": "2024-05-30 17:30:36.791171", + "modified": "2024-08-26 17:16:05.820503", "modified_by": "Administrator", "module": "Desk", "name": "Workspace", diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 1ea99d33f0..7f8bcea15b 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -6,7 +6,7 @@ from json import loads import frappe from frappe import _ -from frappe.desk.desktop import save_new_widget +from frappe.desk.desktop import get_workspace_sidebar_items, save_new_widget from frappe.desk.utils import validate_route_conflict from frappe.model.document import Document from frappe.model.rename_doc import rename_doc @@ -261,7 +261,7 @@ def new_page(new_page): doc = frappe.new_doc("Workspace") doc.title = page.get("title") - doc.icon = page.get("icon") + doc.icon = page.get("icon") or "dashboard" doc.indicator_color = page.get("indicator_color") doc.content = page.get("content") doc.parent_page = page.get("parent_page") @@ -271,7 +271,7 @@ def new_page(new_page): doc.sequence_id = last_sequence_id(doc) + 1 doc.save(ignore_permissions=True) - return doc + return get_workspace_sidebar_items() @frappe.whitelist() @@ -341,125 +341,6 @@ def update_page(name, title, icon, indicator_color, parent, public): return {"name": title, "public": public, "label": new_name} -def hide_unhide_page(page_name: str, is_hidden: bool): - page = frappe.get_doc("Workspace", page_name) - - if page.get("public") and not is_workspace_manager(): - frappe.throw( - _("Need Workspace Manager role to hide/unhide public workspaces"), frappe.PermissionError - ) - - if not page.get("public") and page.get("for_user") != frappe.session.user and not is_workspace_manager(): - frappe.throw(_("Cannot update private workspace of other users"), frappe.PermissionError) - - page.is_hidden = int(is_hidden) - page.save(ignore_permissions=True) - return True - - -@frappe.whitelist() -def hide_page(page_name: str): - return hide_unhide_page(page_name, 1) - - -@frappe.whitelist() -def unhide_page(page_name: str): - return hide_unhide_page(page_name, 0) - - -@frappe.whitelist() -def duplicate_page(page_name, new_page): - if not loads(new_page): - return - - new_page = loads(new_page) - - if new_page.get("is_public") and not is_workspace_manager(): - return - - old_doc = frappe.get_doc("Workspace", page_name) - doc = frappe.copy_doc(old_doc) - doc.title = new_page.get("title") - doc.icon = new_page.get("icon") - doc.indicator_color = new_page.get("indicator_color") - doc.parent_page = new_page.get("parent") or "" - doc.public = new_page.get("is_public") - doc.for_user = "" - doc.label = doc.title - doc.module = "" - if not doc.public: - doc.for_user = doc.for_user or frappe.session.user - doc.label = f"{doc.title}-{doc.for_user}" - doc.name = doc.label - if old_doc.public == doc.public: - doc.sequence_id += 0.1 - else: - doc.sequence_id = last_sequence_id(doc) + 1 - doc.insert(ignore_permissions=True) - - return doc - - -@frappe.whitelist() -def delete_page(page): - if not loads(page): - return - - page = loads(page) - - if page.get("public") and not is_workspace_manager(): - frappe.throw( - _("Cannot delete public workspace without Workspace Manager role"), - frappe.PermissionError, - ) - elif not page.get("public") and not is_workspace_manager(): - workspace_owner = frappe.get_value("Workspace", page.get("name"), "for_user") - if workspace_owner != frappe.session.user: - frappe.throw( - _("Cannot delete private workspace of other users"), - frappe.PermissionError, - ) - - if frappe.db.exists("Workspace", page.get("name")): - frappe.get_doc("Workspace", page.get("name")).delete(ignore_permissions=True) - - return {"name": page.get("name"), "public": page.get("public"), "title": page.get("title")} - - -@frappe.whitelist() -def sort_pages(sb_public_items, sb_private_items): - if not loads(sb_public_items) and not loads(sb_private_items): - return - - sb_public_items = loads(sb_public_items) - sb_private_items = loads(sb_private_items) - - workspace_public_pages = get_page_list(["name", "title"], {"public": 1}) - workspace_private_pages = get_page_list(["name", "title"], {"for_user": frappe.session.user}) - - if sb_private_items: - return sort_page(workspace_private_pages, sb_private_items) - - if sb_public_items and is_workspace_manager(): - return sort_page(workspace_public_pages, sb_public_items) - - return False - - -def sort_page(workspace_pages, pages): - for seq, d in enumerate(pages): - for page in workspace_pages: - if page.title == d.get("title"): - doc = frappe.get_doc("Workspace", page.name) - doc.sequence_id = seq + 1 - doc.parent_page = d.get("parent_page") or "" - doc.flags.ignore_links = True - doc.save(ignore_permissions=True) - break - - return True - - def last_sequence_id(doc): doc_exists = frappe.db.exists({"doctype": "Workspace", "public": doc.public, "for_user": doc.for_user}) diff --git a/frappe/desk/doctype/workspace_settings/workspace_settings.js b/frappe/desk/doctype/workspace_settings/workspace_settings.js index d1ba550205..06915000c8 100644 --- a/frappe/desk/doctype/workspace_settings/workspace_settings.js +++ b/frappe/desk/doctype/workspace_settings/workspace_settings.js @@ -5,33 +5,35 @@ frappe.ui.form.on("Workspace Settings", { setup(frm) { frm.hide_full_form_button = true; frm.docfields = []; + frm.workspace_map = {}; let workspace_visibilty = JSON.parse(frm.doc.workspace_visibility_json || "{}"); // build fields from workspaces let cnt = 0, column_added = false; - for (let w of frappe.boot.allowed_workspaces) { - if (w.public) { + for (let page of frappe.boot.allowed_workspaces) { + if (page.public) { + frm.workspace_map[page.name] = page; cnt++; frm.docfields.push({ fieldtype: "Check", - fieldname: w.name, - label: w.title, - initial_value: workspace_visibilty[w.name] !== 0, // not set is also visible + fieldname: page.name, + label: page.title + (page.parent_page ? ` (${page.parent_page})` : ""), + initial_value: workspace_visibilty[page.name] !== 0, // not set is also visible }); } - - if (cnt >= frappe.boot.allowed_workspaces.length / 2 && !column_added) { - // add column break to split into 2 columns - frm.docfields.push({ fieldtype: "Column Break" }); - column_added = true; - } } frappe.temp = frm; }, validate(frm) { frm.doc.workspace_visibility_json = JSON.stringify(frm.dialog.get_values()); + frm.doc.workspace_sequence = JSON.stringify( + frm.wrapper + .find(".frappe-control") + .get() + .map((e) => e.fieldobj.df.fieldname) + ); frm.doc.workspace_setup_completed = 1; }, after_save(frm) { @@ -39,6 +41,50 @@ frappe.ui.form.on("Workspace Settings", { window.location.reload(); }, refresh(frm) { - frm.dialog.set_alert(__("Select modules you want to see in the sidebar")); + let get_page = (e) => frm.workspace_map[e.fieldobj.df.fieldname]; + + frm.dialog.set_alert(__("Select, sort modules you want to see in the sidebar")); + if (!frm.workspace_sortable) { + frm.wrapper.find(".frappe-control").css({ "margin-bottom": "0.5rem" }); + + let forms = frm.wrapper.find("form"); + frm.workspace_sortable = Sortable.create(forms.get(0), { + group: "workspace_settings", + animation: 150, + onEnd: (o) => { + // re-order so that child items are below parent items + for (let e of frm.wrapper.find(".frappe-control").get()) { + let page = get_page(e); + if (page.parent_page) { + // insert as the last child of the parent element + let parent_element = frm.wrapper + .find(`[data-fieldname="${page.parent_page}"`) + .closest(".frappe-control"); + let parent_page = page.parent_page; + + // find the last child + while (parent_element) { + let next_element = parent_element.next(".frappe-control"); + + if (!next_element.length) { + // end of list + $(e).insertAfter(parent_element); + break; + } else { + let page = get_page(next_element.get(0)); + if (page.parent_page != parent_page) { + // different parent, last child found + $(e).insertAfter(parent_element); + break; + } + } + + parent_element = next_element; + } + } + } + }, + }); + } }, }); diff --git a/frappe/desk/doctype/workspace_settings/workspace_settings.py b/frappe/desk/doctype/workspace_settings/workspace_settings.py index 7909a37c1e..df5773e820 100644 --- a/frappe/desk/doctype/workspace_settings/workspace_settings.py +++ b/frappe/desk/doctype/workspace_settings/workspace_settings.py @@ -1,7 +1,9 @@ # Copyright (c) 2024, Frappe Technologies and contributors # For license information, please see license.txt -# import frappe +import json + +import frappe from frappe.model.document import Document @@ -19,3 +21,12 @@ class WorkspaceSettings(Document): # end: auto-generated types pass + + def validate(self): + cnt = 1 + for page_name in json.loads(self.workspace_sequence): + frappe.db.set_value("Workspace", page_name, "sequence_id", cnt) + cnt += 1 + + def on_update(self): + frappe.clear_cache() diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 49f15f3c81..6231e911ce 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -397,7 +397,6 @@ frappe.setup.slides_settings = [ fieldtype: "Select", reqd: 1, }, - { fieldtype: "Column Break" }, { fieldname: "currency", label: __("Currency"), diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 1be36c978f..b16e409397 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -37,6 +37,7 @@ frappe.Application = class Application { this.load_bootinfo(); this.load_user_permissions(); this.make_nav_bar(); + this.make_sidebar(); this.set_favicon(); this.set_fullwidth_if_enabled(); this.add_browser_class(); @@ -46,6 +47,51 @@ frappe.Application = class Application { frappe.ui.keys.setup(); + this.setup_theme(); + + // page container + this.make_page_container(); + this.setup_tours(); + this.set_route(); + + // trigger app startup + $(document).trigger("startup"); + $(document).trigger("app_ready"); + + this.show_notices(); + this.show_notes(); + + if (frappe.ui.startup_setup_dialog && !frappe.boot.setup_complete) { + frappe.ui.startup_setup_dialog.pre_show(); + frappe.ui.startup_setup_dialog.show(); + } + + // listen to build errors + this.setup_build_events(); + + if (frappe.sys_defaults.email_user_password) { + var email_list = frappe.sys_defaults.email_user_password.split(","); + for (var u in email_list) { + if (email_list[u] === frappe.user.name) { + this.set_password(email_list[u]); + } + } + } + + // REDESIGN-TODO: Fix preview popovers + this.link_preview = new frappe.ui.LinkPreview(); + + frappe.broadcast.emit("boot", { + csrf_token: frappe.csrf_token, + user: frappe.session.user, + }); + } + + make_sidebar() { + this.sidebar = new frappe.ui.Sidebar({}); + } + + setup_theme() { frappe.ui.keys.add_shortcut({ shortcut: "shift+ctrl+g", description: __("Switch Theme"), @@ -71,9 +117,9 @@ frappe.Application = class Application { }); frappe.ui.set_theme(); + } - // page container - this.make_page_container(); + setup_tours() { if ( !window.Cypress && frappe.boot.onboarding_tours && @@ -90,13 +136,9 @@ frappe.Application = class Application { }); } } - this.set_route(); - - // trigger app startup - $(document).trigger("startup"); - - $(document).trigger("app_ready"); + } + show_notices() { if (frappe.boot.messages) { frappe.msgprint(frappe.boot.messages); } @@ -116,13 +158,6 @@ frappe.Application = class Application { console.log(`%c${console_security_message}`, "font-size: large"); } - this.show_notes(); - - if (frappe.ui.startup_setup_dialog && !frappe.boot.setup_complete) { - frappe.ui.startup_setup_dialog.pre_show(); - frappe.ui.startup_setup_dialog.show(); - } - frappe.realtime.on("version-update", function () { var dialog = frappe.msgprint({ message: __( @@ -136,26 +171,6 @@ frappe.Application = class Application { }); dialog.get_close_btn().toggle(false); }); - - // listen to build errors - this.setup_build_events(); - - if (frappe.sys_defaults.email_user_password) { - var email_list = frappe.sys_defaults.email_user_password.split(","); - for (var u in email_list) { - if (email_list[u] === frappe.user.name) { - this.set_password(email_list[u]); - } - } - } - - // REDESIGN-TODO: Fix preview popovers - this.link_preview = new frappe.ui.LinkPreview(); - - frappe.broadcast.emit("boot", { - csrf_token: frappe.csrf_token, - user: frappe.session.user, - }); } set_route() { @@ -275,6 +290,8 @@ frappe.Application = class Application { setup_workspaces() { frappe.modules = {}; frappe.workspaces = {}; + frappe.boot.allowed_workspaces = frappe.boot.sidebar_pages.pages; + for (let page of frappe.boot.allowed_workspaces || []) { frappe.modules[page.module] = page; frappe.workspaces[frappe.router.slug(page.name)] = page; diff --git a/frappe/public/js/frappe/form/controls/table_multiselect.js b/frappe/public/js/frappe/form/controls/table_multiselect.js index c4372e54ac..cfe749a524 100644 --- a/frappe/public/js/frappe/form/controls/table_multiselect.js +++ b/frappe/public/js/frappe/form/controls/table_multiselect.js @@ -1,6 +1,7 @@ frappe.ui.form.ControlTableMultiSelect = class ControlTableMultiSelect extends ( frappe.ui.form.ControlLink ) { + static horizontal = false; make_input() { super.make_input(); diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 038e3c55ef..ce72887537 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -503,7 +503,7 @@ frappe.ui.form.Layout = class Layout { let tabs_content = this.tabs_content[0]; if (!tabs_list.length) return; - $(window).scroll( + $(".main-section").scroll( frappe.utils.throttle(() => { let current_scroll = document.documentElement.scrollTop; if (current_scroll > 0 && last_scroll <= current_scroll) { diff --git a/frappe/public/js/frappe/form/section.js b/frappe/public/js/frappe/form/section.js index 2767fe4ccf..5d14abf1e3 100644 --- a/frappe/public/js/frappe/form/section.js +++ b/frappe/public/js/frappe/form/section.js @@ -27,8 +27,8 @@ export default class Section { make() { let make_card = this.card_layout; - this.wrapper = $(`
`).appendTo(this.parent); diff --git a/frappe/public/js/frappe/form/sidebar/attachments.js b/frappe/public/js/frappe/form/sidebar/attachments.js index 4c8f37186e..e60b32dbea 100644 --- a/frappe/public/js/frappe/form/sidebar/attachments.js +++ b/frappe/public/js/frappe/form/sidebar/attachments.js @@ -124,7 +124,7 @@ frappe.ui.form.Attachments = class Attachments { let file_label = ` ${file_name} `; @@ -151,7 +151,7 @@ frappe.ui.form.Attachments = class Attachments { ${frappe.utils.icon(attachment.is_private ? "es-line-lock" : "es-line-unlock", "sm ml-0")} `; - $(`
  • `) + $(`
    `) .append(frappe.get_data_pill(file_label, fileid, remove_action, icon)) .insertAfter(this.add_attachment_wrapper); } diff --git a/frappe/public/js/frappe/form/sidebar/form_sidebar.js b/frappe/public/js/frappe/form/sidebar/form_sidebar.js index d335803a21..4d36402fd4 100644 --- a/frappe/public/js/frappe/form/sidebar/form_sidebar.js +++ b/frappe/public/js/frappe/form/sidebar/form_sidebar.js @@ -71,6 +71,7 @@ frappe.ui.form.Sidebar = class { frappe.utils.get_page_view_count(route).then((res) => { this.sidebar .find(".pageview-count") + .removeClass("hidden") .html(__("{0} Web page views", [String(res.message).bold()])); }); } @@ -145,6 +146,7 @@ frappe.ui.form.Sidebar = class { callback: function (res) { me.sidebar .find(".auto-repeat-status") + .removeClass("hidden") .html(__("Repeats {0}", [__(res.message.frequency)])); me.sidebar.find(".auto-repeat-status").on("click", function () { frappe.set_route("Form", "Auto Repeat", me.frm.doc.auto_repeat); @@ -198,7 +200,9 @@ frappe.ui.form.Sidebar = class { return $("") .html(label) .appendTo( - $('
  • ').appendTo(this.user_actions.removeClass("hidden")) + $('
    ').appendTo( + this.user_actions.removeClass("hidden") + ) ) .on("click", click); } diff --git a/frappe/public/js/frappe/form/templates/form_footer.html b/frappe/public/js/frappe/form/templates/form_footer.html index 56c9c99b93..89a995569e 100644 --- a/frappe/public/js/frappe/form/templates/form_footer.html +++ b/frappe/public/js/frappe/form/templates/form_footer.html @@ -3,7 +3,7 @@
  • - diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index a651d9a62b..c441b6e704 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -1,6 +1,6 @@ - - + + {% if frm.meta.beta %} - {% endif %} - - - - - - - - - - + + + + {% if(frappe.get_form_sidebar_extension) { %} {{ frappe.get_form_sidebar_extension() }} {% } %} diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 890c89ef0d..4d4748a548 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -169,7 +169,7 @@ frappe.views.BaseList = class BaseList { setup_page() { this.page = this.parent.page; this.$page = $(this.parent); - !this.hide_card_layout && this.page.main.addClass("frappe-card"); + this.page.main.addClass("layout-main-list"); this.page.page_form.removeClass("row").addClass("flex"); this.hide_page_form && this.page.page_form.hide(); this.hide_sidebar && this.$page.addClass("no-list-sidebar"); @@ -435,6 +435,20 @@ frappe.views.BaseList = class BaseList { }); } + set_result_height() { + // place it at the footer of the page + this.$result.css({ + height: + window.innerHeight - + this.$result.get(0).offsetTop - + this.$paging_area.get(0).offsetHeight + + "px", + }); + this.$no_result.css({ + height: window.innerHeight - this.$no_result.get(0).offsetTop + "px", + }); + } + get_fields() { // convert [fieldname, Doctype] => tabDoctype.fieldname return this.fields.map((f) => frappe.model.get_full_column_name(f[0], f[1])); @@ -516,6 +530,7 @@ frappe.views.BaseList = class BaseList { this.before_render(); this.render(); this.after_render(); + this.set_result_height(); this.freeze(false); this.reset_defaults(); if (this.settings.refresh) { @@ -581,8 +596,10 @@ frappe.views.BaseList = class BaseList { this.$paging_area.toggle(this.data.length > 0); this.$no_result.toggle(this.data.length == 0); - const show_more = this.start + this.page_length <= this.data.length; - this.$paging_area.find(".btn-more").toggle(show_more); + if (this.data.length) { + const show_more = this.start + this.page_length <= this.data.length; + this.$paging_area.find(".btn-more").toggle(show_more); + } } call_for_selected_items(method, args = {}) { diff --git a/frappe/public/js/frappe/list/list_filter.js b/frappe/public/js/frappe/list/list_filter.js index 2f7b1ba9bc..4047761ca9 100644 --- a/frappe/public/js/frappe/list/list_filter.js +++ b/frappe/public/js/frappe/list/list_filter.js @@ -13,10 +13,10 @@ export default class ListFilter { make() { // init dom this.wrapper.html(` -
  • - +
    `); diff --git a/frappe/public/js/frappe/list/list_sidebar.html b/frappe/public/js/frappe/list/list_sidebar.html index 0437f4af20..de420c98f5 100644 --- a/frappe/public/js/frappe/list/list_sidebar.html +++ b/frappe/public/js/frappe/list/list_sidebar.html @@ -1,91 +1,91 @@ - - +
    + + +
    + + + diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index 236d3ae9a8..6ad4e324e3 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -174,6 +174,7 @@ frappe.views.ListSidebar = class ListSidebar { let sections = [ ["tags-section", "list-tags"], ["save-filter-section", "list-filters"], + ["filter-section", "list-group-by"], ]; for (let s of sections) { @@ -237,11 +238,11 @@ frappe.views.ListSidebar = class ListSidebar { } set_loading_state(dropdown) { - dropdown.html(`
  • + dropdown.html(`
    ${__("Loading...")}
    -
  • `); + `); } render_stat(stats) { @@ -297,16 +298,8 @@ frappe.views.ListSidebar = class ListSidebar { const cta = "Frappe Insights"; this.insights_banner = $(` -
    -
    - ${message} ${cta} → -
    -
    - - - -
    + `).appendTo(this.sidebar); } catch (error) { diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js index 53f5406f4b..5ce58abfd9 100644 --- a/frappe/public/js/frappe/list/list_sidebar_group_by.js +++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js @@ -55,11 +55,11 @@ frappe.views.ListGroupBy = class ListGroupBy { let html = `
    - +
    `; this.$wrapper.html(html); } @@ -80,17 +80,17 @@ frappe.views.ListGroupBy = class ListGroupBy { fieldtype = docfield.fieldtype; } - return ``; + `; }; let html = this.group_by_fields.map(get_item_html).join(""); this.$wrapper.find(".list-group-by-fields").html(html); @@ -230,13 +230,13 @@ frappe.views.ListGroupBy = class ListGroupBy { let applied_html = applied ? ` ${frappe.utils.icon("tick", "xs")} ` : ""; - return `
  • + return `
    ${applied_html} ${label} ${field.count} -
  • `; + `; } setup_filter_by() { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index f50cd3baa4..9cc265a8b7 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -106,8 +106,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { }); } - if (this.view_name == "List") this.toggle_paging = true; - this.patch_refresh_and_load_lib(); return this.get_list_view_settings(); } @@ -566,6 +564,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (this.$result.find(".list-row-head").length === 0) { // append header once this.$result.prepend(this.get_header_html()); + + if (this.filter_area.filter_list.get_filter_value("_liked_by")) { + // if there is a liked fitler, then add liked + this.$result.find(".list-liked-by-me").addClass("liked"); + } } } @@ -584,17 +587,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { sort_by: this.sort_selector && this.sort_selector.sort_by, sort_order: this.sort_selector && this.sort_selector.sort_order, }); - this.toggle_paging && this.$paging_area.toggle(false); } after_render() { - this.$no_result.html(` -
    - ${this.get_no_result_message()} -
    - `); + this.$no_result.html(this.get_no_result_message()); this.setup_new_doc_event(); - this.toggle_paging && this.$paging_area.toggle(true); } render() { @@ -605,6 +602,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { render_list() { // clear rows this.$result.find(".list-row-container").remove(); + this.render_header(); if (this.data.length > 0) { // append rows @@ -1464,9 +1462,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.$result.on("click", ".list-liked-by-me", (e) => { const $this = $(e.currentTarget); - $this.toggleClass("active"); + $this.toggleClass("liked"); - if ($this.hasClass("active")) { + if ($this.hasClass("liked")) { this.filter_area.add( this.doctype, "_liked_by", diff --git a/frappe/public/js/frappe/logtypes.js b/frappe/public/js/frappe/logtypes.js index 1e65de48a7..0116aab6c1 100644 --- a/frappe/public/js/frappe/logtypes.js +++ b/frappe/public/js/frappe/logtypes.js @@ -11,7 +11,7 @@ frappe.utils.logtypes.show_log_retention_message = (doctype) => { } const add_sidebar_message = (message) => { - let sidebar_entry = $('').appendTo( + let sidebar_entry = $('