diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index e15a354de0..60fa46bc88 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -24,6 +24,7 @@ context("Folder Navigation", () => { it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => { //Navigating inside the Attachments folder + cy.wait(500); cy.get('[title="Attachments"] > span').click(); //To check if the URL formed after visiting the attachments folder is correct @@ -36,6 +37,7 @@ context("Folder Navigation", () => { cy.click_modal_primary_button("Create"); //Navigating inside the added folder in the Attachments folder + cy.wait(500); cy.get('[title="Test Folder"] > span').click(); //To check if the URL is correct after visiting the Test Folder @@ -51,7 +53,12 @@ context("Folder Navigation", () => { cy.click_modal_primary_button("Upload"); //To check if the added file is present in the Test Folder - cy.get("span.level-item > span").should("contain", "Test Folder"); + cy.visit("/app/file/view/home/Attachments"); + cy.wait(500); + cy.get("span.level-item > a > span").should("contain", "Test Folder"); + cy.visit("/app/file/view/home/Attachments/Test%20Folder"); + + cy.wait(500); cy.get(".list-row-container").eq(0).should("contain.text", "72402.jpg"); cy.get(".list-row-checkbox").eq(0).click(); diff --git a/cypress/integration/view_routing.js b/cypress/integration/view_routing.js new file mode 100644 index 0000000000..1e3b841c79 --- /dev/null +++ b/cypress/integration/view_routing.js @@ -0,0 +1,231 @@ +context("View", () => { + before(() => { + cy.login(); + cy.visit("/app/website"); + }); + + it("Route to ToDo List View", () => { + cy.visit("/app/todo/view/list"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("List"); + }); + }); + + it("Route to ToDo Report View", () => { + cy.visit("/app/todo/view/report"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + + it("Route to ToDo Dashboard View", () => { + cy.visit("/app/todo/view/dashboard"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Dashboard"); + }); + }); + + it("Route to ToDo Gantt View", () => { + cy.visit("/app/todo/view/gantt"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Gantt"); + }); + }); + + it("Route to ToDo Kanban View", () => { + cy.call("frappe.tests.ui_test_helpers.create_kanban").then(() => { + cy.visit("/app/note/view/kanban/_Note _Kanban"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Kanban"); + }); + }); + }); + + it("Route to ToDo Calendar View", () => { + cy.visit("/app/todo/view/calendar"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Calendar"); + }); + }); + + it("Route to Custom Tree View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_tree_doctype").then(() => { + cy.visit("/app/custom-tree/view/tree"); + cy.wait(500); + cy.window() + .its("cur_tree") + .then((list) => { + expect(list.view_name).to.equal("Tree"); + }); + }); + }); + + it("Route to Custom Image View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_image_doctype").then(() => { + cy.visit("app/custom-image/view/image"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Image"); + }); + }); + }); + + it("Route to Communication Inbox View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_inbox").then(() => { + cy.visit("app/communication/view/inbox"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Inbox"); + }); + }); + }); + + it("Route to File View", () => { + cy.visit("app/file"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("File"); + expect(list.current_folder).to.equal("Home"); + }); + + cy.visit("app/file/view/home/Attachments"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("File"); + expect(list.current_folder).to.equal("Home/Attachments"); + }); + }); + + it("Re-route to default view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Route to default view from app/{doctype}", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event/view"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event/view"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event/view/list"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Validate Route History for Default View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event"); + cy.visit("/app/event/view/list"); + cy.location("pathname").should("eq", "/app/event/view/list"); + cy.go("back"); + cy.location("pathname").should("eq", "/app/event"); + }); + }); + + it("Route to Form", () => { + cy.call("frappe.tests.ui_test_helpers.create_note").then(() => { + cy.visit("/app/note/Routing Test"); + cy.window() + .its("cur_frm") + .then((frm) => { + expect(frm.doc.title).to.equal("Routing Test"); + }); + }); + }); + + it("Route to Settings Workspace", () => { + cy.visit("/app/settings"); + cy.get(".title-text").should("contain", "Settings"); + }); +}); diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index f9d15af483..293a6b2c87 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -2,6 +2,7 @@ "actions": [], "allow_import": 1, "creation": "2013-01-29 10:47:14", + "default_view": "Inbox", "description": "Keeps track of all communications", "doctype": "DocType", "document_type": "Setup", @@ -198,7 +199,6 @@ "label": "More Information" }, { - "bold": 0, "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", @@ -395,7 +395,7 @@ "icon": "fa fa-comment", "idx": 1, "links": [], - "modified": "2022-03-30 11:24:25.728637", + "modified": "2022-05-09 00:13:45.310564", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -454,8 +454,9 @@ "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", + "states": [], "subject_field": "subject", "title_field": "subject", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index e91a05e17d..24c367b115 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -55,6 +55,7 @@ frappe.ui.form.on("DocType", { if (frm.is_new()) { frm.events.set_default_permission(frm); + frm.set_value("default_view", "List"); } else { frm.toggle_enable("engine", 0); } @@ -66,12 +67,14 @@ frappe.ui.form.on("DocType", { frm.cscript.autoname(frm); frm.cscript.set_naming_rule_description(frm); + frm.trigger("setup_default_views"); }, istable: (frm) => { if (frm.doc.istable && frm.is_new()) { frm.set_value("autoname", "autoincrement"); frm.set_value("allow_rename", 0); + frm.set_value("default_view", null); } else if (!frm.doc.istable && !frm.is_new()) { frm.events.set_default_permission(frm); } @@ -82,6 +85,18 @@ frappe.ui.form.on("DocType", { frm.add_child("permissions", { role: "System Manager" }); } }, + + is_tree: (frm) => { + frm.trigger("setup_default_views"); + }, + + is_calendar_and_gantt: (frm) => { + frm.trigger("setup_default_views"); + }, + + setup_default_views: (frm) => { + frappe.model.set_default_views_for_doctype(frm.doc.name, frm); + }, }); frappe.ui.form.on("DocField", { @@ -171,6 +186,10 @@ frappe.ui.form.on("DocField", { fieldtype: function (frm) { frm.trigger("max_attachments"); }, + + fields_add: (frm) => { + frm.trigger("setup_default_views"); + }, }); extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm })); diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 0b2ced43da..6258241f5d 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -14,6 +14,7 @@ "istable", "issingle", "is_tree", + "is_calendar_and_gantt", "editable_grid", "quick_entry", "cb01", @@ -53,6 +54,8 @@ "default_print_format", "sort_field", "sort_order", + "default_view", + "force_re_route_to_default_view", "column_break_29", "document_type", "icon", @@ -606,6 +609,24 @@ "fieldname": "make_attachments_public", "fieldtype": "Check", "label": "Make Attachments Public by Default" + }, + { + "fieldname": "default_view", + "fieldtype": "Select", + "label": "Default View" + }, + { + "default": "0", + "fieldname": "force_re_route_to_default_view", + "fieldtype": "Check", + "label": "Force Re-route to Default View" + }, + { + "default": "0", + "description": "Enables Calendar and Gantt views.", + "fieldname": "is_calendar_and_gantt", + "fieldtype": "Check", + "label": "Is Calendar and Gantt" } ], "icon": "fa fa-bolt", @@ -688,7 +709,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2022-09-02 12:05:59.589751", + "modified": "2022-10-12 14:13:27.315351", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index cb727e48f0..d6c4a99bc3 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -2,6 +2,7 @@ "actions": [], "allow_import": 1, "creation": "2012-12-12 11:19:22", + "default_view": "File", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -169,10 +170,11 @@ "read_only": 1 } ], + "force_re_route_to_default_view": 1, "icon": "fa fa-file", "idx": 1, "links": [], - "modified": "2022-09-13 15:50:15.508250", + "modified": "2022-09-13 15:50:15.508251", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 8d8249b532..759a9e1b3a 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -72,6 +72,7 @@ frappe.ui.form.on("Customize Form", { } else { frm.refresh(); frm.trigger("setup_sortable"); + frm.trigger("setup_default_views"); } } localStorage["customize_doctype"] = frm.doc.doc_type; @@ -82,8 +83,12 @@ frappe.ui.form.on("Customize Form", { } }, + is_calendar_and_gantt: function (frm) { + frm.trigger("setup_default_views"); + }, + setup_sortable: function (frm) { - frm.doc.fields.forEach(function (f, i) { + frm.doc.fields.forEach(function (f) { if (!f.is_custom_field) { f._sortable = false; } @@ -222,6 +227,10 @@ frappe.ui.form.on("Customize Form", { frm.set_df_property("sort_field", "options", fields); } }, + + setup_default_views(frm) { + frappe.model.set_default_views_for_doctype(frm.doc.doc_type, frm); + }, }); // can't delete standard fields @@ -237,6 +246,7 @@ frappe.ui.form.on("Customize Form Field", { var f = frappe.model.get_doc(cdt, cdn); f.is_system_generated = false; f.is_custom_field = true; + frm.trigger("setup_default_views"); }, }); diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 05989eaa00..4840184966 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -13,6 +13,7 @@ "search_fields", "column_break_5", "istable", + "is_calendar_and_gantt", "editable_grid", "quick_entry", "track_changes", @@ -35,6 +36,8 @@ "show_title_field_in_link", "translated_doctype", "default_print_format", + "default_view", + "force_re_route_to_default_view", "column_break_29", "show_preview_popup", "email_settings_section", @@ -337,6 +340,25 @@ "fieldname": "make_attachments_public", "fieldtype": "Check", "label": "Make Attachments Public by Default" + }, + { + "fieldname": "default_view", + "fieldtype": "Select", + "label": "Default View" + }, + { + "default": "0", + "depends_on": "default_view", + "fieldname": "force_re_route_to_default_view", + "fieldtype": "Check", + "label": "Force Re-route to Default View" + }, + { + "default": "0", + "description": "Enables Calendar and Gantt views.", + "fieldname": "is_calendar_and_gantt", + "fieldtype": "Check", + "label": "Is Calendar and Gantt" } ], "hide_toolbar": 1, @@ -345,7 +367,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-08-24 06:57:47.966331", + "modified": "2022-08-30 11:45:16.772277", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index cacd38397a..be27ebbc0b 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -586,6 +586,10 @@ doctype_properties = { "naming_rule": "Data", "autoname": "Data", "show_title_field_in_link": "Check", + "translate_link_fields": "Check", + "is_calendar_and_gantt": "Check", + "default_view": "Select", + "force_re_route_to_default_view": "Check", "translated_doctype": "Check", } diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index a94f6b9b62..3079717198 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -196,7 +196,7 @@ frappe.views.BaseList = class BaseList { Map: "map", }; - if (frappe.boot.desk_settings.view_switcher) { + if (frappe.boot.desk_settings.view_switcher && !this.meta.force_re_route_to_default_view) { /* @preserve for translation, don't remove __("List View") __("Report View") __("Dashboard View") __("Gantt View"), diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 41faa0217c..99553f470c 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -349,7 +349,7 @@ $.extend(frappe.model, { is_tree: function (doctype) { if (!doctype) return false; - return frappe.boot.treeviews.indexOf(doctype) != -1; + return locals.DocType[doctype] && locals.DocType[doctype].is_tree; }, is_fresh(doc) { @@ -754,6 +754,42 @@ $.extend(frappe.model, { } return frappe.model.numeric_fieldtypes.includes(fieldtype); }, + + set_default_views_for_doctype(doctype, frm) { + frappe.model.with_doctype(doctype, () => { + let meta = frappe.get_meta(doctype); + let default_views = ["List", "Report", "Dashboard", "Kanban"]; + + if (meta.is_calendar_and_gantt && frappe.views.calendar[doctype]) { + let views = ["Calendar", "Gantt"]; + default_views.push(...views); + } + + if (meta.is_tree) { + default_views.push("Tree"); + } + + if (frm.doc.image_field) { + default_views.push("Image"); + } + + if (doctype === "Communication" && frappe.boot.email_accounts.length) { + default_views.push("Inbox"); + } + + if ( + (frm.doc.fields.find((i) => i.fieldname === "latitude") && + frm.doc.fields.find((i) => i.fieldname === "longitude")) || + frm.doc.fields.find( + (i) => i.fieldname === "location" && i.fieldtype == "Geolocation" + ) + ) { + default_views.push("Map"); + } + + frm.set_df_property("default_view", "options", default_views); + }); + }, }); // legacy diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 4524cbdce4..6c8932aa3c 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -89,6 +89,18 @@ frappe.router = { "image", "inbox", ], + list_views_route: { + list: "List", + kanban: "Kanban", + report: "Report", + calendar: "Calendar", + tree: "Tree", + gantt: "Gantt", + dashboard: "Dashboard", + image: "Image", + inbox: "Inbox", + file: "Home", + }, layout_mapped: {}, is_app_route(path) { @@ -115,7 +127,7 @@ frappe.router = { } }, - route() { + async route() { // resolve the route from the URL or hash // translate it so the objects are well defined // and render the page as required @@ -126,22 +138,22 @@ frappe.router = { if (this.re_route(sub_path)) return; this.current_sub_path = sub_path; - this.current_route = this.parse(); + this.current_route = await this.parse(); this.set_history(sub_path); this.render(); this.set_title(sub_path); this.trigger("change"); }, - parse(route) { + async parse(route) { route = this.get_sub_path_string(route).split("/"); if (!route) return []; route = $.map(route, this.decode_component); this.set_route_options_from_url(); - return this.convert_to_standard_route(route); + return await this.convert_to_standard_route(route); }, - convert_to_standard_route(route) { + async convert_to_standard_route(route) { // /app/settings = ["Workspaces", "Settings"] // /app/private/settings = ["Workspaces", "private", "Settings"] // /app/user = ["List", "User"] @@ -161,7 +173,7 @@ frappe.router = { route = ["Workspaces", "private", frappe.workspaces[private_workspace].title]; } else if (this.routes[route[0]]) { // route - route = this.set_doctype_route(route); + route = await this.set_doctype_route(route); } return route; @@ -174,36 +186,85 @@ frappe.router = { set_doctype_route(route) { let doctype_route = this.routes[route[0]]; - // doctype route - if (route[1]) { - if (route[2] && route[1] === "view") { - route = this.get_standard_route_for_list(route, doctype_route); - } else { + + return frappe.model.with_doctype(doctype_route.doctype).then(() => { + // doctype route + let meta = frappe.get_meta(doctype_route.doctype); + + if (route[1] && route[1] === "view" && route[2]) { + route = this.get_standard_route_for_list( + route, + doctype_route, + meta.force_re_route_to_default_view && meta.default_view + ? meta.default_view + : null + ); + } else if (route[1] && route[1] !== "view" && !route[2]) { let docname = route[1]; if (route.length > 2) { docname = route.slice(1).join("/"); } route = ["Form", doctype_route.doctype, docname]; + } else if (frappe.model.is_single(doctype_route.doctype)) { + route = ["Form", doctype_route.doctype, doctype_route.doctype]; + } else if (meta.default_view) { + route = [ + "List", + doctype_route.doctype, + this.list_views_route[meta.default_view.toLowerCase()], + ]; + } else { + route = ["List", doctype_route.doctype, "List"]; } - } else if (frappe.model.is_single(doctype_route.doctype)) { - route = ["Form", doctype_route.doctype, doctype_route.doctype]; - } else { - route = ["List", doctype_route.doctype, "List"]; - } - // reset the layout to avoid using incorrect views - this.doctype_layout = doctype_route.doctype_layout; - return route; + // reset the layout to avoid using incorrect views + this.doctype_layout = doctype_route.doctype_layout; + return route; + }); }, - get_standard_route_for_list(route, doctype_route) { + get_standard_route_for_list(route, doctype_route, default_view) { let standard_route; - if (route[2].toLowerCase() === "tree") { + let _route = default_view || route[2] || ""; + + if (_route.toLowerCase() === "tree") { standard_route = ["Tree", doctype_route.doctype]; } else { - standard_route = ["List", doctype_route.doctype, frappe.utils.to_title_case(route[2])]; + let new_route = this.list_views_route[_route.toLowerCase()]; + let re_route = route[2].toLowerCase() !== new_route.toLowerCase(); + + if (re_route) { + /** + * In case of force_re_route, the url of the route should change, + * if the _route and route[2] are different, it means there is a default_view + * with force_re_route enabled. + * + * To change the url, to the correct view, the route[2] is changed with default_view + * + * Eg: If default_view is set to Report with force_re_route enabled and user routes + * to List, + * route: [todo, view, list] + * default_view: report + * + * replaces the list to report and re-routes to the new route but should be replaced in + * the history since the list route should not exist in history as we are rerouting it to + * report + */ + frappe.route_flags.replace_route = true; + + route[2] = _route.toLowerCase(); + this.set_route(route); + } + + standard_route = [ + "List", + doctype_route.doctype, + this.list_views_route[_route.toLowerCase()], + ]; + // calendar / kanban / dashboard / folder if (route[3]) standard_route.push(...route.slice(3, route.length)); } + return standard_route; }, @@ -345,6 +406,7 @@ frappe.router = { } else if (view === "tree") { new_route = [this.slug(route[1]), "view", "tree"]; } + return new_route; }, diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index e3a34b2111..434fb888fc 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -208,13 +208,10 @@ frappe.search.utils = { }, }); } - if (in_list(frappe.boot.treeviews, item)) { - out.push(option("Tree", ["Tree", item], 0.05)); - } else { - out.push(option("List", ["List", item], 0.05)); - if (frappe.model.can_get_report(item)) { - out.push(option("Report", ["List", item, "Report"], 0.04)); - } + + out.push(option("List", ["List", item], 0.05)); + if (frappe.model.can_get_report(item)) { + out.push(option("Report", ["List", item, "Report"], 0.04)); } } } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 12c6e12697..3d17e8a8f6 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1260,20 +1260,12 @@ Object.assign(frappe.utils, { if (frappe.model.is_single(item.doctype)) { route = doctype_slug; } else { - if (!item.doc_view) { - if (frappe.model.is_tree(item.doctype)) { - item.doc_view = "Tree"; - } else { - item.doc_view = "List"; - } - } - switch (item.doc_view) { case "List": if (item.filters) { frappe.route_options = item.filters; } - route = doctype_slug; + route = `${doctype_slug}/view/list`; break; case "Tree": route = `${doctype_slug}/view/tree`; @@ -1290,12 +1282,11 @@ Object.assign(frappe.utils, { case "Calendar": route = `${doctype_slug}/view/calendar/default`; break; + case "Kanban": + route = `${doctype_slug}/view/kanban`; + break; default: - frappe.throw({ - message: __("Not a valid view:") + item.doc_view, - title: __("Unknown View"), - }); - route = ""; + route = doctype_slug; } } } else if (type === "report") { diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 93cff25df5..1a2a1198d1 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -144,7 +144,7 @@ frappe.breadcrumbs = { } else { let route; const doctype_route = frappe.router.slug(frappe.router.doctype_layout || doctype); - if (frappe.boot.treeviews.indexOf(doctype) !== -1) { + if (doctype_meta.is_tree) { let view = frappe.model.user_settings[doctype].last_view || "Tree"; route = `${doctype_route}/view/${view}`; } else { diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js index 122be935d7..2c54011e8a 100644 --- a/frappe/public/js/frappe/views/file/file_view.js +++ b/frappe/public/js/frappe/views/file/file_view.js @@ -74,7 +74,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { this.page_title = __("File Manager"); const route = frappe.get_route(); - this.current_folder = route.slice(2).join("/"); + this.current_folder = route.slice(2).join("/") || "Home"; this.filters = [["File", "folder", "=", this.current_folder, true]]; this.order_by = this.view_user_settings.order_by || "file_name asc"; @@ -286,7 +286,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { } get_breadcrumbs_html() { - const route = frappe.router.parse(); + const route = frappe.get_route(); const folders = route.slice(2); return folders diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index 677cc5d5e6..7eccd7ebf8 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -9,14 +9,9 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { const doctype = route[1]; const user_settings = frappe.get_user_settings(doctype)["Kanban"] || {}; if (!user_settings.last_kanban_board) { - frappe.msgprint({ - title: __("Error"), - indicator: "red", - message: __("Missing parameter Kanban Board Name"), - }); - frappe.set_route("List", doctype, "List"); - return true; + return new frappe.views.KanbanView({ doctype: doctype }); } + route.push(user_settings.last_kanban_board); frappe.set_route(route); return true; @@ -28,9 +23,35 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { return "Kanban"; } + show() { + frappe.views.KanbanView.get_kanbans(this.doctype).then((kanbans) => { + if (!kanbans.length) { + return frappe.views.KanbanView.show_kanban_dialog(this.doctype, true); + } else if (kanbans.length && frappe.get_route().length !== 4) { + return frappe.views.KanbanView.show_kanban_dialog(this.doctype, true); + } else { + this.kanbans = kanbans; + + return frappe.run_serially([ + () => this.show_skeleton(), + () => this.fetch_meta(), + () => this.hide_skeleton(), + () => this.check_permissions(), + () => this.init(), + () => this.before_refresh(), + () => this.refresh(), + ]); + } + }); + } + setup_defaults() { return super.setup_defaults().then(() => { - this.board_name = frappe.get_route()[3]; + let get_board_name = () => { + return this.kanbans.length && this.kanbans[0].name; + }; + + this.board_name = frappe.get_route()[3] || get_board_name() || null; this.page_title = __(this.board_name); this.card_meta = this.get_card_meta(); this.page_length = 0; @@ -143,21 +164,22 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { render() { const board_name = this.board_name; + if (!this.kanban) { + this.kanban = new frappe.views.KanbanBoard({ + doctype: this.doctype, + board: this.board, + board_name: board_name, + cards: this.data, + card_meta: this.card_meta, + wrapper: this.$result, + cur_list: this, + user_settings: this.view_user_settings, + }); + } + if (this.kanban && board_name === this.kanban.board_name) { this.kanban.update(this.data); - return; } - - this.kanban = new frappe.views.KanbanBoard({ - doctype: this.doctype, - board: this.board, - board_name: board_name, - cards: this.data, - card_meta: this.card_meta, - wrapper: this.$result, - cur_list: this, - user_settings: this.view_user_settings, - }); } get_card_meta() { diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 96e3bf7227..6cbc02c649 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -37,6 +37,10 @@ frappe.views.TreeFactory = class TreeFactory extends frappe.views.Factory { let treeview = frappe.views.trees[route[1]]; treeview && treeview.make_tree(); } + + get view_name() { + return "Tree"; + } }; frappe.views.TreeView = class TreeView { @@ -196,6 +200,7 @@ frappe.views.TreeView = class TreeView { }); cur_tree = this.tree; + cur_tree.view_name = "Tree"; this.post_render(); } diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index b577700715..db7336a883 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -384,18 +384,22 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { if (this.dialog.get_value("type") == "DocType") { let doctype = this.dialog.get_value("link_to"); - if (doctype && frappe.boot.single_types.includes(doctype)) { - this.hide_filters(); - } else if (doctype) { - this.setup_filter(doctype); - this.show_filters(); - } - - const views = ["List", "Report Builder", "Dashboard", "New"]; - if (frappe.boot.treeviews.includes(doctype)) views.push("Tree"); - if (frappe.boot.calendars.includes(doctype)) views.push("Calendar"); - - this.dialog.set_df_property("doc_view", "options", views.join("\n")); + frappe.model.with_doctype(doctype, () => { + let meta = frappe.get_meta(doctype); + + if (doctype && frappe.boot.single_types.includes(doctype)) { + this.hide_filters(); + } else if (doctype) { + this.setup_filter(doctype); + this.show_filters(); + } + + const views = ["List", "Report Builder", "Dashboard", "New"]; + if (meta.is_tree === "Tree") views.push("Tree"); + if (frappe.boot.calendars.includes(doctype)) views.push("Calendar"); + + this.dialog.set_df_property("doc_view", "options", views.join("\n")); + }); } else { this.hide_filters(); } @@ -405,7 +409,7 @@ class ShortcutDialog extends WidgetDialog { fieldtype: "Select", fieldname: "doc_view", label: "DocType View", - options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar", + options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar\nKanban", description: __( "Which view of the associated DocType should this shortcut take you to?" ), diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index cb8fe8e70a..297d9f5b12 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -43,16 +43,32 @@ def create_todo_records(): frappe.db.truncate("ToDo") frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), days=7), "description": "this is first todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), days=7), + "description": "this is first todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), days=-7), "description": "this is second todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), days=-7), + "description": "this is second todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), months=2), "description": "this is third todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), months=2), + "description": "this is third todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), months=-2), "description": "this is fourth todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), months=-2), + "description": "this is fourth todo", + } ).insert() @@ -431,3 +447,134 @@ def create_test_user(): user.append("roles", {"role": role}) user.save() + + +@frappe.whitelist() +def setup_tree_doctype(): + frappe.delete_doc_if_exists("DocType", "Custom Tree") + + frappe.get_doc( + { + "doctype": "DocType", + "module": "Core", + "custom": 1, + "fields": [ + {"fieldname": "tree", "fieldtype": "Data", "label": "Tree"}, + ], + "permissions": [{"role": "System Manager", "read": 1}], + "name": "Custom Tree", + "is_tree": True, + "naming_rule": "By fieldname", + "autoname": "field:tree", + } + ).insert() + + if not frappe.db.exists("Custom Tree", "All Trees"): + frappe.get_doc({"doctype": "Custom Tree", "tree": "All Trees"}).insert() + + +@frappe.whitelist() +def setup_image_doctype(): + frappe.delete_doc_if_exists("DocType", "Custom Image") + + frappe.get_doc( + { + "doctype": "DocType", + "module": "Core", + "custom": 1, + "fields": [ + {"fieldname": "image", "fieldtype": "Attach Image", "label": "Image"}, + ], + "permissions": [{"role": "System Manager", "read": 1}], + "name": "Custom Image", + "image_field": "image", + } + ).insert() + + +@frappe.whitelist() +def setup_inbox(): + frappe.db.sql("DELETE FROM `tabUser Email`") + + user = frappe.get_doc("User", frappe.session.user) + user.append("user_emails", {"email_account": "Email Linking"}) + user.save() + + +@frappe.whitelist() +def setup_default_view(view, force_reroute=None): + frappe.delete_doc_if_exists("Property Setter", "Event-main-default_view") + frappe.delete_doc_if_exists("Property Setter", "Event-main-force_re_route_to_default_view") + + frappe.get_doc( + { + "is_system_generated": 0, + "doctype_or_field": "DocType", + "doc_type": "Event", + "property": "default_view", + "property_type": "Select", + "value": view, + "doctype": "Property Setter", + } + ).insert() + + if force_reroute: + frappe.get_doc( + { + "is_system_generated": 0, + "doctype_or_field": "DocType", + "doc_type": "Event", + "property": "force_re_route_to_default_view", + "property_type": "Check", + "value": "1", + "doctype": "Property Setter", + } + ).insert() + + +@frappe.whitelist() +def create_note(): + if not frappe.db.exists("Note", "Routing Test"): + frappe.get_doc({"doctype": "Note", "title": "Routing Test"}).insert() + + +@frappe.whitelist() +def create_kanban(): + if not frappe.db.exists("Custom Field", "Note-kanban"): + frappe.get_doc( + { + "is_system_generated": 0, + "dt": "Note", + "label": "Kanban", + "fieldname": "kanban", + "insert_after": "seen_by", + "fieldtype": "Select", + "options": "Open\nClosed", + "doctype": "Custom Field", + } + ).insert() + + if not frappe.db.exists("Kanban Board", "_Note _Kanban"): + frappe.get_doc( + { + "doctype": "Kanban Board", + "name": "_Note _Kanban", + "kanban_board_name": "_Note _Kanban", + "reference_doctype": "Note", + "field_name": "kanban", + "private": 1, + "show_labels": 0, + "columns": [ + { + "column_name": "Open", + "status": "Active", + "indicator": "Gray", + }, + { + "column_name": "Closed", + "status": "Active", + "indicator": "Gray", + }, + ], + } + ).insert()