Merge branch 'rebrand-ui' of https://github.com/frappe/frappe into rebrand-ui

This commit is contained in:
prssanna 2020-12-11 11:48:50 +05:30
commit 9d01e4cdf3
58 changed files with 936 additions and 639 deletions

View file

@ -48,6 +48,7 @@ def get_bootinfo():
bootinfo.letter_heads = get_letter_heads()
bootinfo.active_domains = frappe.get_active_domains()
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")]
add_layouts(bootinfo)
bootinfo.module_app = frappe.local.module_app
bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})]
@ -309,6 +310,10 @@ def get_additional_filters_from_hooks():
return filter_config
def add_layouts(bootinfo):
# add routes for readable doctypes
bootinfo.doctype_layouts = frappe.get_all('DocType Layout', ['name', 'route', 'document_type'])
def get_desk_settings():
role_list = frappe.get_all('Role', fields=['*'], filters=dict(
name=['in', frappe.get_roles()]

View file

@ -18,7 +18,7 @@ global_cache_keys = ("app_hooks", "installed_apps",
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts',
'sitemap_routes', 'db_tables', 'doctype_name_map') + doctype_map_keys
'sitemap_routes', 'db_tables') + doctype_map_keys
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
"defaults", "user_permissions", "home_page", "linked_with",
@ -73,7 +73,7 @@ def clear_doctype_cache(doctype=None):
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
del frappe.local.meta_cache[doctype]
for key in ('is_table', 'doctype_modules', 'doctype_name_map', 'document_cache'):
for key in ('is_table', 'doctype_modules', 'document_cache'):
cache.delete_value(key)
frappe.local.document_cache = {}

View file

@ -32,7 +32,7 @@ frappe.ui.form.on('Data Import Legacy', {
frm.reload_doc();
}
if (data.progress) {
let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar");
let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar");
if (progress_bar) {
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
$(progress_bar).css("width", data.progress + "%");

View file

@ -132,7 +132,7 @@
"label": "Editable Grid"
},
{
"default": "1",
"default": "0",
"depends_on": "eval:!doc.istable && !doc.issingle",
"description": "Open a dialog with mandatory fields to create a new record quickly",
"fieldname": "quick_entry",
@ -427,7 +427,7 @@
"label": "Allow Guest to View"
},
{
"depends_on": "has_web_view",
"depends_on": "eval:!doc.istable",
"fieldname": "route",
"fieldtype": "Data",
"label": "Route"
@ -609,7 +609,7 @@
"link_fieldname": "reference_doctype"
}
],
"modified": "2020-09-24 13:13:58.227153",
"modified": "2020-12-10 15:10:09.227205",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
@ -637,6 +637,7 @@
"write": 1
}
],
"route": "doctype",
"search_fields": "module",
"show_name_in_global_search": 1,
"sort_field": "modified",

View file

@ -26,6 +26,7 @@ from frappe.database.schema import validate_column_name, validate_column_length
from frappe.model.docfield import supports_translation
from frappe.modules.import_file import get_file_path
from frappe.model.meta import Meta
from frappe.desk.utils import get_doctype_route
class InvalidFieldNameError(frappe.ValidationError): pass
@ -63,15 +64,7 @@ class DocType(Document):
self.validate_name()
if self.issingle:
self.allow_import = 0
self.is_submittable = 0
self.istable = 0
elif self.istable:
self.allow_import = 0
self.permissions = []
self.set_defaults_for_single_and_table()
self.scrub_field_names()
self.set_default_in_list_view()
self.set_default_translatable()
@ -79,10 +72,7 @@ class DocType(Document):
self.validate_document_type()
validate_fields(self)
if self.istable:
# no permission records for child table
self.permissions = []
else:
if not self.istable:
validate_permissions(self)
self.make_amendable()
@ -93,8 +83,6 @@ class DocType(Document):
if not self.is_new():
self.before_update = frappe.get_doc('DocType', self.name)
if not self.is_new():
self.setup_fields_to_fetch()
check_email_append_to(self)
@ -102,14 +90,20 @@ class DocType(Document):
if self.default_print_format and not self.custom:
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))
if frappe.conf.get('developer_mode'):
self.owner = 'Administrator'
self.modified_by = 'Administrator'
def after_insert(self):
# clear user cache so that on the next reload this doctype is included in boot
clear_user_cache(frappe.session.user)
def set_defaults_for_single_and_table(self):
if self.issingle:
self.allow_import = 0
self.is_submittable = 0
self.istable = 0
elif self.istable:
self.allow_import = 0
self.permissions = []
def set_default_in_list_view(self):
'''Set default in-list-view for first 4 mandatory fields'''
if not [d.fieldname for d in self.fields if d.in_list_view]:
@ -134,6 +128,10 @@ class DocType(Document):
if not frappe.conf.get("developer_mode") and not self.custom:
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError)
if frappe.conf.get('developer_mode'):
self.owner = 'Administrator'
self.modified_by = 'Administrator'
def setup_fields_to_fetch(self):
'''Setup query to update values for newly set fetch values'''
try:
@ -192,6 +190,12 @@ class DocType(Document):
def validate_website(self):
"""Ensure that website generator has field 'route'"""
if not self.istable and not self.route:
self.route = get_doctype_route(self.name)
if self.route:
self.route = self.route.strip('/')
if self.has_web_view:
# route field must be present
if not 'route' in [d.fieldname for d in self.fields]:

View file

@ -0,0 +1,7 @@
import frappe
from frappe.desk.utils import get_doctype_route
def execute():
for doctype in frappe.get_all('DocType', ['name', 'route'], dict(istable=0)):
if not doctype.route:
frappe.db.set_value('DocType', doctype.name, 'route', get_doctype_route(doctype.name), update_modified = False)

View file

@ -23,7 +23,7 @@ class NavbarSettings(Document):
if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)):
frappe.throw(_("Please hide the standard navbar items instead of deleting them"))
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def get_app_logo():
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo')
if not app_logo:

View file

@ -16,7 +16,7 @@
"two_factor_auth",
"navigation_settings_section",
"search_bar",
"notification",
"notifications",
"chat",
"list_settings_section",
"list_sidebar",
@ -84,12 +84,6 @@
"fieldtype": "Check",
"label": "Search Bar"
},
{
"default": "1",
"fieldname": "notification",
"fieldtype": "Check",
"label": "Notification"
},
{
"default": "1",
"fieldname": "chat",
@ -141,13 +135,19 @@
"fieldname": "view_switcher",
"fieldtype": "Check",
"label": "View Switcher"
},
{
"default": "1",
"fieldname": "notifications",
"fieldtype": "Check",
"label": "Notifications"
}
],
"icon": "fa fa-bookmark",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-11-11 17:29:13.149522",
"modified": "2020-12-03 14:08:38.181035",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",

View file

@ -6,7 +6,7 @@ import frappe
from frappe.model.document import Document
desk_properties = ("search_bar", "notification", "chat", "list_sidebar",
desk_properties = ("search_bar", "notifications", "chat", "list_sidebar",
"bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard")
class Role(Document):

View file

@ -55,7 +55,7 @@ class UserPermission(Document):
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def get_user_permissions(user=None):
'''Get all users permissions for the user as a dict of doctype'''
# if this is called from client-side,
@ -66,7 +66,7 @@ def get_user_permissions(user=None):
if not user:
user = frappe.session.user
if not user or user == "Administrator":
if not user or user in ("Administrator", "Guest"):
return {}
cached_user_permissions = frappe.cache().hget("user_permissions", user)

View file

@ -8,6 +8,7 @@
"engine": "InnoDB",
"field_order": [
"document_type",
"route",
"fields",
"client_script"
],
@ -31,11 +32,17 @@
"fieldname": "client_script",
"fieldtype": "Code",
"label": "Client Script"
},
{
"fieldname": "route",
"fieldtype": "Data",
"label": "Route",
"reqd": 1
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-11-17 15:49:49.669291",
"modified": "2020-12-10 15:01:04.352184",
"modified_by": "Administrator",
"module": "Custom",
"name": "DocType Layout",
@ -52,8 +59,13 @@
"role": "System Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Guest"
}
],
"route": "doctype-layout",
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1

View file

@ -7,6 +7,9 @@ from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.desk.utils import get_doctype_route
class DocTypeLayout(Document):
def validate(self):
frappe.cache().delete_value('doctype_name_map')
if not self.route:
self.route = get_doctype_route(self.name)

View file

@ -0,0 +1,13 @@
import frappe
def execute():
for web_form_name in frappe.db.get_all('Web Form', pluck='name'):
web_form = frappe.get_doc('Web Form', web_form_name)
doctype_layout = frappe.get_doc(dict(
doctype = 'DocType Layout',
document_type = web_form.doc_type,
name = web_form.title,
route = web_form.route,
fields = [dict(fieldname = d.fieldname, label=d.label) for d in web_form.web_form_fields if d.fieldname]
)).insert()
print(doctype_layout.name)

View file

@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed
from frappe import _
from six.moves.urllib.parse import quote
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def getdoc(doctype, name, user=None):
"""
Loads a doclist for a given document. This method is called directly from the client.
@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None):
frappe.response.docs.append(doc)
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def getdoctype(doctype, with_parent=False, cached_timestamp=None):
"""load doctype"""

View file

@ -202,13 +202,17 @@ class FormMeta(Meta):
self.load_kanban_column_fields()
def load_kanban_column_fields(self):
values = frappe.get_list(
'Kanban Board', fields=['field_name'],
filters={'reference_doctype': self.name})
try:
values = frappe.get_list(
'Kanban Board', fields=['field_name'],
filters={'reference_doctype': self.name})
fields = [x['field_name'] for x in values]
fields = list(set(fields))
self.set("__kanban_column_fields", fields, as_value=True)
fields = [x['field_name'] for x in values]
fields = list(set(fields))
self.set("__kanban_column_fields", fields, as_value=True)
except frappe.PermissionError:
# no access to kanban board
pass
def get_code_files_via_hooks(hook, name):
code_files = []

View file

@ -4,7 +4,7 @@ from __future__ import unicode_literals
import frappe
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def get_list_settings(doctype):
try:
return frappe.get_cached_doc("List View Settings", doctype)

View file

@ -14,7 +14,7 @@ from frappe.core.doctype.access_log.access_log import make_access_log
from frappe.utils import cstr, format_duration
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
@frappe.read_only()
def get():
args = get_form_params()

View file

@ -3,26 +3,5 @@
import frappe
@frappe.whitelist(allow_guest=True)
def get_doctype_name(name):
# translates the doctype name from url to name `sales-order` to `Sales Order`
# also supports document type layouts
# if with_layout is set: return the layout object too
def get_name_map():
name_map = {}
for d in frappe.get_all('DocType'):
name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.name)
for d in frappe.get_all('DocType Layout', fields = ['name', 'document_type']):
name_map[d.name.lower().replace(' ', '-')] = frappe._dict(doctype = d.document_type, doctype_layout = d.name)
return name_map
data = frappe._dict(name_map = frappe.cache().get_value('doctype_name_map', get_name_map).get(name, dict(doctype = name)))
if data.name_map.get('doctype_layout'):
# return the layout object
frappe.response.docs.append(frappe.get_doc('DocType Layout', data.name_map.get('doctype_layout')).as_dict())
return data
def get_doctype_route(name):
return name.lower().replace(' ', '-')

View file

@ -35,13 +35,9 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.717 7c2.29 0 4.211 1.859 4.494 4.266 1.272.152 2.289 1.31 2.289 2.742 0 1.523-1.13 2.742-2.543 2.742H8.043c-1.413 0-2.543-1.219-2.543-2.742 0-1.188.707-2.224 1.724-2.59C7.422 8.92 9.372 7 11.717 7zm.148 2.37a.499.499 0 0 0-.363.156l-1.556 1.555a.5.5 0 1 0 .708.707l.71-.711v3.097a.5.5 0 0 0 1 0v-3.098l.713.712a.5.5 0 1 0 .707-.707l-1.565-1.565a.498.498 0 0 0-.354-.146z"
fill="#fff"></path>
</symbol>
<symbol viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" id="icon-upload">
<path d="M10.2427 2.3999V11.7332" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.29004 5.35228L10.2424 2.3999L13.1948 5.35228" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
<mask id="path-3-inside-1" fill="white">
<path d="M13.9334 8.30469H16.8858V15.8999C16.8858 17.0045 15.9904 17.8999 14.8858 17.8999H5.6001C4.49553 17.8999 3.6001 17.0045 3.6001 15.8999V8.30469H6.55248"/>
</mask>
<path d="M16.8858 8.30469H17.8858C17.8858 7.7524 17.4381 7.30469 16.8858 7.30469V8.30469ZM3.6001 8.30469V7.30469C3.04781 7.30469 2.6001 7.7524 2.6001 8.30469H3.6001ZM13.9334 9.30469H16.8858V7.30469H13.9334V9.30469ZM15.8858 8.30469V15.8999H17.8858V8.30469H15.8858ZM14.8858 16.8999H5.6001V18.8999H14.8858V16.8999ZM4.6001 15.8999V8.30469H2.6001V15.8999H4.6001ZM3.6001 9.30469H6.55248V7.30469H3.6001V9.30469ZM5.6001 16.8999C5.04781 16.8999 4.6001 16.4522 4.6001 15.8999H2.6001C2.6001 17.5568 3.94325 18.8999 5.6001 18.8999V16.8999ZM15.8858 15.8999C15.8858 16.4522 15.4381 16.8999 14.8858 16.8999V18.8999C16.5427 18.8999 17.8858 17.5568 17.8858 15.8999H15.8858Z" fill="white" mask="url(#path-3-inside-1)"/>
<symbol viewBox="0 0 20 20" id="icon-upload" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M10.596 2.046a.5.5 0 0 0-.707 0L6.937 5a.5.5 0 0 0 .707.707l2.099-2.099v8.126a.5.5 0 1 0 1 0V3.607l2.098 2.099a.5.5 0 0 0 .708-.707l-2.953-2.953z"/>
<path d="M6.552 8.305v1H4.6V15.9a1 1 0 0 0 1 1h9.286a1 1 0 0 0 1-1V9.305h-1.953v-1h2.953V15.9a2 2 0 0 1-2 2H5.6a2 2 0 0 1-2-2V8.305h2.952z"/>
</symbol>
<symbol viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" id="icon-tag">
<path d="M12.6401 10.2571L10.107 12.7901C9.52125 13.3759 8.5718 13.3756 7.98601 12.7898L2.49654 7.30037C2.40278 7.2066 2.3501 7.07942 2.3501 6.94682L2.3501 3C2.3501 2.72386 2.57396 2.5 2.8501 2.5L6.79691 2.5C6.92952 2.5 7.0567 2.55268 7.15047 2.64645L12.6399 8.13591C13.2257 8.7217 13.2259 9.67131 12.6401 10.2571Z" stroke="var(--icon-stroke)" stroke-linecap="round" stroke-linejoin="round"/>
@ -660,4 +656,12 @@
<path d="M8.54541 9.74542L4.90908 9.74542" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="square"/>
<path d="M8.54541 12.6545L4.90908 12.6545" stroke="var(--icon-stroke)" stroke-miterlimit="10" stroke-linecap="square"/>
</symbol>
<symbol viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-sidebar-collapse">
<path d="M12 6L6 12L12 18" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18 6L12 12L18 18" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" id="icon-sidebar-expand">
<path d="M12 18L18 12L12 6" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 18L12 12L6 6" stroke="var(--icon-stroke)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View file

@ -2670,20 +2670,6 @@ frappe.chat.render = (render = true, force = false) =>
// With the assumption, that there's only one navbar.
const $placeholder = $('.navbar .frappe-chat-dropdown')
// Render if frappe-chat-toggle doesn't exist.
if ( frappe.utils.is_empty($placeholder.has('.frappe-chat-toggle')) ) {
const $template = $(`
<a class="dropdown-toggle frappe-chat-toggle" data-toggle="dropdown">
<div>
<i class="octicon octicon-comment-discussion"/>
</div>
</a>
`)
$placeholder.addClass('dropdown hidden')
$placeholder.html($template)
}
if ( render ) {
$placeholder.removeClass('hidden')
} else {

View file

@ -160,8 +160,6 @@ frappe.Application = Class.extend({
}, 600000); // check every 10 minutes
}
}
this.fetch_tags();
},
set_route() {
@ -170,7 +168,7 @@ frappe.Application = Class.extend({
localStorage.removeItem("session_last_route");
} else {
// route to home page
frappe.route();
frappe.router.route();
}
},
@ -263,6 +261,7 @@ frappe.Application = Class.extend({
this.check_metadata_cache_status();
this.set_globals();
this.sync_pages();
frappe.router.setup();
moment.locale("en");
moment.user_utc_offset = moment().utcOffset();
if(frappe.boot.timezone_info) {
@ -272,6 +271,7 @@ frappe.Application = Class.extend({
frappe.dom.set_style(frappe.boot.print_css, "print-style");
}
frappe.user.name = frappe.boot.user.name;
frappe.router.setup();
} else {
this.set_as_guest();
}
@ -294,6 +294,7 @@ frappe.Application = Class.extend({
set_globals: function() {
frappe.session.user = frappe.boot.user.name;
frappe.session.logged_in_user = frappe.boot.user.name;
frappe.session.user_email = frappe.boot.user.email;
frappe.session.user_fullname = frappe.user_info().fullname;
@ -599,10 +600,6 @@ frappe.Application = Class.extend({
frappe.show_alert(message);
});
},
fetch_tags() {
frappe.tags.utils.fetch_tags();
}
});
frappe.get_module = function(m, default_module) {

View file

@ -6,6 +6,8 @@
// add_fetches
import Awesomplete from 'awesomplete';
frappe.ui.form.recent_link_validations = {};
frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
make_input: function() {
var me = this;
@ -439,40 +441,44 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({
this.docname, value);
},
validate_link_and_fetch: function(df, doctype, docname, value) {
var me = this;
if(value) {
return new Promise((resolve) => {
var fetch = '';
if(this.frm && this.frm.fetch_dict[df.fieldname]) {
fetch = this.frm.fetch_dict[df.fieldname].columns.join(', ');
}
return frappe.call({
method:'frappe.desk.form.utils.validate_link',
type: "GET",
args: {
'value': value,
'options': doctype,
'fetch': fetch
},
no_spinner: true,
callback: function(r) {
if(r.message=='Ok') {
if(r.fetch_values && docname) {
me.set_fetch_values(df, docname, r.fetch_values);
}
resolve(r.valid_value);
} else {
resolve("");
}
}
});
// if default and no fetch, no need to validate
if (!fetch && df.__default_value && df.__default_value===value) return value;
this.fetch_and_validate_link(resolve, df, doctype, docname, value, fetch);
});
}
},
fetch_and_validate_link(resolve, df, doctype, docname, value, fetch) {
frappe.call({
method:'frappe.desk.form.utils.validate_link',
type: "GET",
args: {
'value': value,
'options': doctype,
'fetch': fetch
},
no_spinner: true,
callback: (r) => {
if(r.message=='Ok') {
if(r.fetch_values && docname) {
this.set_fetch_values(df, docname, r.fetch_values);
}
resolve(r.valid_value);
} else {
resolve("");
}
}
});
},
set_fetch_values: function(df, docname, fetch_values) {
var fl = this.frm.fetch_dict[df.fieldname].fields;
for(var i=0; i < fl.length; i++) {

View file

@ -1,92 +1,101 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
frappe.ui.form.Dashboard = Class.extend({
init: function(opts) {
frappe.ui.form.Dashboard = class FormDashboard {
constructor(opts) {
$.extend(this, opts);
this.section = this.frm.fields_dict._form_dashboard.wrapper;
this.parent = this.section.find('.section-body');
this.wrapper = $(frappe.render_template('form_dashboard',
{frm: this.frm})).appendTo(this.parent);
this.setup_dashboard_sections();
}
this.progress_area = this.wrapper.find(".progress-area");
this.heatmap_area = this.wrapper.find('.form-heatmap');
this.chart_area = this.wrapper.find('.form-graph');
this.stats_area = this.wrapper.find('.form-stats');
this.stats_area_row = this.stats_area.find('.row');
this.links_area = this.wrapper.find('.form-links');
this.transactions_area = this.links_area.find('.transactions');
setup_dashboard_sections() {
this.progress_area = new Section(this.parent, {
css_class: 'progress-area',
hidden: 1,
collapsible: 1
});
},
reset: function() {
this.heatmap_area = new Section(this.parent, {
title: __("Overview"),
css_class: 'form-heatmap',
hidden: 1,
collapsible: 1,
body_html: `
<div id="heatmap-${frappe.model.scrub(this.frm.doctype)}" class="heatmap"></div>
<div class="text-muted small heatmap-message hidden"></div>
`
});
this.chart_area = new Section(this.parent, {
title: __("Graph"),
css_class: 'form-graph',
hidden: 1,
collapsible: 1
});
this.stats_area_row = $(`<div class="row"></div>`);
this.stats_area = new Section(this.parent, {
title: __("Stats"),
css_class: 'form-stats',
hidden: 1,
collapsible: 1,
body_html: this.stats_area_row
});
this.transactions_area = $(`<div class="transactions"></div`);
this.links_area = new Section(this.parent, {
title: __("Documents Links"),
css_class: 'form-links',
hidden: 1,
collapsible: 1,
body_html: this.transactions_area
});
}
reset() {
this.hide();
this.clear_headline();
// clear progress
this.progress_area.empty().addClass('hidden');
this.progress_area.body.empty();
this.progress_area.hide();
// clear links
this.links_area.addClass('hidden');
this.links_area.find('.count, .open-notification').addClass('hidden');
this.links_area.body.find('.count, .open-notification').addClass('hidden');
this.links_area.hide();
// clear stats
this.stats_area.addClass('hidden')
this.stats_area_row.empty();
this.stats_area.hide();
// clear custom
this.wrapper.find('.custom').remove();
this.parent.find('.custom').remove();
this.hide();
},
set_headline: function(html, color) {
this.frm.layout.show_message(html, color);
},
clear_headline: function() {
this.frm.layout.show_message();
},
}
add_comment: function(text, alert_class, permanent) {
var me = this;
this.set_headline_alert(text, alert_class);
if(!permanent) {
setTimeout(function() {
me.clear_headline();
}, 10000);
}
},
add_section(body_html, title=null, css_class="custom", hidden=false) {
let options = {
title,
css_class,
hidden,
body_html,
make_card: true,
collapsible: 1
};
return new Section(this.parent, options).body;
}
clear_comment: function() {
this.clear_headline();
},
add_progress(title, percent, message) {
let progress_chart = this.make_progress_chart(title);
set_headline_alert: function(text, color) {
if(text) {
this.set_headline(`<div>${text}</div>`, color);
} else {
this.clear_headline();
}
},
add_section: function(html, section_head=null) {
let section = $(`<div class="form-dashboard-section custom"></div>`);
if (section_head) {
section.append(`<div class="section-head">${section_head}</div>`);
}
section.append(html);
section.appendTo(this.wrapper);
return section;
},
add_progress: function(title, percent, message) {
var progress_chart = this.make_progress_chart(title);
if(!$.isArray(percent)) {
if (!$.isArray(percent)) {
percent = this.format_percent(title, percent);
}
var progress = $('<div class="progress"></div>').appendTo(progress_chart);
let progress = $('<div class="progress"></div>').appendTo(progress_chart);
$.each(percent, function(i, opts) {
$(repl('<div class="progress-bar %(progress_class)s" style="width: %(width)s" \
title="%(title)s"></div>', opts)).appendTo(progress);
$(`<div class="progress-bar ${opts.progress_class}" style="width: ${opts.width}" title="${opts.title}"></div>`).appendTo(progress);
});
if (!message) message = '';
@ -95,9 +104,9 @@ frappe.ui.form.Dashboard = Class.extend({
this.show();
return progress_chart;
},
}
show_progress: function(title, percent, message) {
show_progress(title, percent, message) {
this._progress_map = this._progress_map || {};
let progress_chart = this._progress_map[title];
// create a new progress chart if it doesnt exist
@ -119,19 +128,19 @@ frappe.ui.form.Dashboard = Class.extend({
if (!message) message = '';
progress_chart.find('.progress-message').text(message);
},
}
hide_progress: function(title) {
if (title){
hide_progress(title) {
if (title) {
this._progress_map[title].remove();
delete this._progress_map[title];
} else {
this._progress_map = {};
this.progress_area.empty();
this.progress_area.hide();
}
},
}
format_percent: function(title, percent) {
format_percent(title, percent) {
const percentage = cint(percent);
const width = percentage < 0 ? 100 : percentage;
const progress_class = percentage < 0 ? "progress-bar-danger" : "progress-bar-success";
@ -141,28 +150,30 @@ frappe.ui.form.Dashboard = Class.extend({
width: width + '%',
progress_class: progress_class
}];
},
make_progress_chart: function(title) {
var progress_chart = $('<div class="progress-chart" title="'+(title || '')+'"></div>')
.appendTo(this.progress_area.removeClass('hidden'));
return progress_chart;
},
}
refresh: function() {
make_progress_chart(title) {
this.progress_area.show();
var progress_chart = $('<div class="progress-chart" title="'+(title || '')+'"></div>')
.appendTo(this.progress_area.body);
return progress_chart;
}
refresh() {
this.reset();
if(this.frm.doc.__islocal) {
if (this.frm.doc.__islocal || !frappe.boot.desk_settings.form_dashboard) {
return;
}
if(!this.data) {
if (!this.data) {
this.init_data();
}
var show = false;
if(this.data && ((this.data.transactions || []).length
if (this.data && ((this.data.transactions || []).length
|| (this.data.reports || []).length)) {
if(this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) {
if (this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) {
// limited docstatus
return;
}
@ -171,53 +182,53 @@ frappe.ui.form.Dashboard = Class.extend({
show = true;
}
if(this.data.heatmap) {
if (this.data.heatmap) {
this.render_heatmap();
show = true;
}
if(this.data.graph) {
if (this.data.graph) {
this.setup_graph();
// show = true;
}
if(show) {
if (show) {
this.show();
}
},
}
after_refresh: function() {
after_refresh() {
var me = this;
// show / hide new buttons (if allowed)
this.links_area.find('.btn-new').each(function() {
if(me.frm.can_create($(this).attr('data-doctype'))) {
this.links_area.body.find('.btn-new').each(function() {
if (me.frm.can_create($(this).attr('data-doctype'))) {
$(this).removeClass('hidden');
}
});
},
}
init_data: function() {
init_data() {
this.data = this.frm.meta.__dashboard || {};
if(!this.data.transactions) this.data.transactions = [];
if(!this.data.internal_links) this.data.internal_links = {};
if (!this.data.transactions) this.data.transactions = [];
if (!this.data.internal_links) this.data.internal_links = {};
this.filter_permissions();
},
}
add_transactions: function(opts) {
add_transactions(opts) {
// add additional data on dashboard
let group_added = [];
if(!Array.isArray(opts)) opts=[opts];
if (!Array.isArray(opts)) opts=[opts];
if(!this.data) {
if (!this.data) {
this.init_data();
}
if(this.data && (this.data.transactions || []).length) {
if (this.data && (this.data.transactions || []).length) {
// check if label already exists, add items to it
this.data.transactions.map(group => {
opts.map(d => {
if(d.label == group.label) {
if (d.label == group.label) {
group_added.push(d.label);
group.items.push(...d.items);
}
@ -226,80 +237,81 @@ frappe.ui.form.Dashboard = Class.extend({
// if label not already present, add new label and items under it
opts.map(d => {
if(!group_added.includes(d.label)) {
if (!group_added.includes(d.label)) {
this.data.transactions.push(d);
}
});
this.filter_permissions();
}
},
}
filter_permissions: function() {
filter_permissions() {
// filter out transactions for which the user
// does not have permission
var transactions = [];
let transactions = [];
(this.data.transactions || []).forEach(function(group) {
var items = [];
let items = [];
group.items.forEach(function(doctype) {
if(frappe.model.can_read(doctype)) {
if (frappe.model.can_read(doctype)) {
items.push(doctype);
}
});
// only add thie group, if there is atleast
// only add this group, if there is at-least
// one item with permission
if(items.length) {
if (items.length) {
group.items = items;
transactions.push(group);
}
});
this.data.transactions = transactions;
},
render_links: function() {
}
render_links() {
var me = this;
this.links_area.removeClass('hidden');
this.links_area.find('.btn-new').addClass('hidden');
if(this.data_rendered) {
this.links_area.show();
this.links_area.body.find('.btn-new').addClass('hidden');
if (this.data_rendered) {
return;
}
//this.transactions_area.empty();
this.data.frm = this.frm;
let transactions_area_body = this.transactions_area;
$(frappe.render_template('form_links', this.data))
.appendTo(this.transactions_area)
.appendTo(transactions_area_body);
if (this.data.reports && this.data.reports.length) {
$(frappe.render_template('report_links', this.data))
.appendTo(this.transactions_area)
.appendTo(transactions_area_body);
}
// bind links
this.transactions_area.find(".badge-link").on('click', function() {
transactions_area_body.find(".badge-link").on('click', function() {
me.open_document_list($(this).parent());
});
// bind reports
this.transactions_area.find(".report-link").on('click', function() {
transactions_area_body.find(".report-link").on('click', function() {
me.open_report($(this).parent());
});
// bind open notifications
this.transactions_area.find('.open-notification').on('click', function() {
transactions_area_body.find('.open-notification').on('click', function() {
me.open_document_list($(this).parent(), true);
});
// bind new
this.transactions_area.find('.btn-new').on('click', function() {
transactions_area_body.find('.btn-new').on('click', function() {
me.frm.make_new($(this).attr('data-doctype'));
});
this.data_rendered = true;
},
open_report: function($link) {
}
open_report($link) {
let report = $link.attr('data-report');
let fieldname = this.data.non_standard_fieldnames
@ -308,28 +320,30 @@ frappe.ui.form.Dashboard = Class.extend({
frappe.route_options[fieldname] = this.frm.doc.name;
frappe.set_route("query-report", report);
},
open_document_list: function($link, show_open) {
}
open_document_list($link, show_open) {
// show document list with filters
var doctype = $link.attr('data-doctype'),
names = $link.attr('data-names') || [];
if(this.data.internal_links[doctype]) {
if(names.length) {
if (this.data.internal_links[doctype]) {
if (names.length) {
frappe.route_options = {'name': ['in', names]};
} else {
return false;
}
} else if(this.data.fieldname) {
} else if (this.data.fieldname) {
frappe.route_options = this.get_document_filter(doctype);
if(show_open) {
if (show_open) {
frappe.ui.notifications.show_open_count_list(doctype);
}
}
frappe.set_route("List", doctype, "List");
},
get_document_filter: function(doctype) {
}
get_document_filter(doctype) {
// return the default filter for the given document
// like {"customer": frm.doc.name}
var filter = {};
@ -344,9 +358,10 @@ frappe.ui.form.Dashboard = Class.extend({
filter[fieldname] = this.frm.doc.name;
return filter;
},
set_open_count: function() {
if(!this.data.transactions || !this.data.fieldname) {
}
set_open_count() {
if (!this.data.transactions || !this.data.fieldname) {
return;
}
@ -355,7 +370,9 @@ frappe.ui.form.Dashboard = Class.extend({
me = this;
this.data.transactions.forEach(function(group) {
group.items.forEach(function(item) { items.push(item); });
group.items.forEach(function(item) {
items.push(item);
});
});
var method = this.data.method || 'frappe.desk.notifications.get_open_count';
@ -368,7 +385,7 @@ frappe.ui.form.Dashboard = Class.extend({
items: items
},
callback: function(r) {
if(r.message.timeline_data) {
if (r.message.timeline_data) {
me.update_heatmap(r.message.timeline_data);
}
@ -404,12 +421,13 @@ frappe.ui.form.Dashboard = Class.extend({
}
});
},
set_badge_count: function(doctype, open_count, count, names) {
}
set_badge_count(doctype, open_count, count, names) {
var $link = $(this.transactions_area)
.find('.document-link[data-doctype="'+doctype+'"]');
if(open_count) {
if (open_count) {
$link.find('.open-notification')
.removeClass('hidden')
.html((open_count > 99) ? '99+' : open_count);
@ -421,24 +439,24 @@ frappe.ui.form.Dashboard = Class.extend({
.text((count > 99) ? '99+' : count);
}
if(this.data.internal_links[doctype]) {
if(names && names.length) {
if (this.data.internal_links[doctype]) {
if (names && names.length) {
$link.attr('data-names', names ? names.join(',') : '');
} else {
$link.find('a').attr('disabled', true);
}
}
},
}
update_heatmap: function(data) {
if(this.heatmap) {
update_heatmap(data) {
if (this.heatmap) {
this.heatmap.update({dataPoints: data});
}
},
}
// heatmap
render_heatmap: function() {
if(!this.heatmap) {
render_heatmap() {
if (!this.heatmap) {
this.heatmap = new frappe.Chart("#heatmap-" + frappe.model.scrub(this.frm.doctype), {
type: 'heatmap',
start: new Date(moment().subtract(1, 'year').toDate()),
@ -449,32 +467,36 @@ frappe.ui.form.Dashboard = Class.extend({
});
// center the heatmap
this.heatmap_area.removeClass('hidden').find('svg').css({'margin': 'auto'});
this.heatmap_area.show();
this.heatmap_area.body.find('svg').css({'margin': 'auto'});
// message
var heatmap_message = this.heatmap_area.find('.heatmap-message');
if(this.data.heatmap_message) {
var heatmap_message = this.heatmap_area.body.find('.heatmap-message');
if (this.data.heatmap_message) {
heatmap_message.removeClass('hidden').html(this.data.heatmap_message);
} else {
heatmap_message.addClass('hidden');
}
}
},
}
add_indicator: function(label, color) {
add_indicator(label, color) {
this.show();
this.stats_area.removeClass('hidden');
this.stats_area.show();
// set colspan
var indicators = this.stats_area_row.find('.indicator-column');
var n_indicators = indicators.length + 1;
var colspan;
if(n_indicators > 4) { colspan = 3 }
else { colspan = 12 / n_indicators; }
if (n_indicators > 4) {
colspan = 3;
} else {
colspan = 12 / n_indicators;
}
// reset classes in existing indicators
if(indicators.length) {
if (indicators.length) {
indicators.removeClass().addClass('col-sm-'+colspan).addClass('indicator-column');
}
@ -482,10 +504,10 @@ frappe.ui.form.Dashboard = Class.extend({
+label+'</span></div>').appendTo(this.stats_area_row);
return indicator;
},
}
// graphs
setup_graph: function() {
setup_graph() {
var me = this;
var method = this.data.graph_method;
var args = {
@ -500,7 +522,7 @@ frappe.ui.form.Dashboard = Class.extend({
args: args,
callback: function(r) {
if(r.message) {
if (r.message) {
me.render_graph(r.message);
me.show();
} else {
@ -508,11 +530,11 @@ frappe.ui.form.Dashboard = Class.extend({
}
}
});
},
}
render_graph: function(args) {
var me = this;
this.chart_area.empty().removeClass('hidden');
render_graph(args) {
this.chart_area.show();
this.chart_area.body.empty();
$.extend(args, {
type: 'line',
colors: ['green'],
@ -524,21 +546,151 @@ frappe.ui.form.Dashboard = Class.extend({
this.show();
this.chart = new frappe.Chart('.form-graph', args);
if(!this.chart) {
if (!this.chart) {
this.hide();
}
},
}
show: function() {
show() {
this.toggle_visibility(true);
},
}
hide: function() {
hide() {
this.toggle_visibility(false);
},
}
toggle_visibility(show) {
this.section.toggleClass('visible-section', show);
this.section.toggleClass('empty-section', !show);
this.parent.toggleClass('visible-section', show);
this.parent.toggleClass('empty-section', !show);
}
});
// TODO: Review! code related to headline should be the part of layout/form
set_headline(html, color) {
this.frm.layout.show_message(html, color);
}
clear_headline() {
this.frm.layout.show_message();
}
add_comment(text, alert_class, permanent) {
var me = this;
this.set_headline_alert(text, alert_class);
if (!permanent) {
setTimeout(function() {
me.clear_headline();
}, 10000);
}
}
clear_comment() {
this.clear_headline();
}
set_headline_alert(text, color) {
if (text) {
this.set_headline(`<div>${text}</div>`, color);
} else {
this.clear_headline();
}
}
};
class Section {
constructor(parent, options) {
this.parent = parent;
this.df = options || {};
this.make();
if (this.df.title && this.df.collapsible) {
this.collapse();
}
this.refresh();
}
make() {
this.wrapper = $(`<div class="form-dashboard-section ${ this.df.make_card ? "card-section" : "" }">`)
.appendTo(this.parent);
if (this.df) {
if (this.df.title) {
this.make_head();
}
if (this.df.description) {
this.description_wrapper = $(
`<div class="col-sm-12 form-section-description">
${__(this.df.description)}
</div>`
);
this.wrapper.append(this.description_wrapper);
}
if (this.df.css_class) {
this.wrapper.addClass(this.df.css_class);
}
if (this.df.hide_border) {
this.wrapper.toggleClass("hide-border", true);
}
}
this.body = $('<div class="section-body">').appendTo(this.wrapper);
if (this.df.body_html) {
this.body.append(this.df.body_html);
}
}
make_head() {
this.head = $(`
<div class="section-head">
${__(this.df.title)}
<span class="ml-2 collapse-indicator mb-1"></span>
</div>
`);
this.head.appendTo(this.wrapper);
this.indicator = this.head.find('.collapse-indicator');
this.indicator.hide();
if (this.df.collapsible) {
// show / hide based on status
this.collapse_link = this.head.on("click", () => {
this.collapse();
});
this.indicator.show();
}
}
refresh() {
if (!this.df) return;
// hide if explicitly hidden
let hide = this.df.hidden;
this.wrapper.toggle(!hide);
}
collapse(hide) {
if (hide === undefined) {
hide = !this.body.hasClass("hide");
}
this.body.toggleClass("hide", hide);
this.head && this.head.toggleClass("collapsed", hide);
let indicator_icon = hide ? 'down' : 'up-line';
this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1'));
}
is_collapsed() {
return this.body.hasClass('hide');
}
hide() {
this.wrapper.hide();
}
show() {
this.wrapper.show();
}
}

View file

@ -48,7 +48,7 @@ frappe.ui.form.Footer = Class.extend({
});
},
get_names_for_mentions() {
let names_for_mentions = Object.keys(frappe.boot.user_info)
let names_for_mentions = Object.keys(frappe.boot.user_info || [])
.filter(user => {
return !["Administrator", "Guest"].includes(user)
&& frappe.boot.user_info[user].allowed_in_mentions;

View file

@ -1,4 +1,5 @@
frappe.provide('frappe.ui.form');
frappe.provide('frappe.model.docinfo');
import './quick_entry';
import './toolbar';
@ -23,13 +24,10 @@ frappe.ui.form.Form = class FrappeForm {
this.docname = '';
this.doctype = doctype;
this.doctype_layout_name = doctype_layout_name;
if (doctype_layout_name) {
this.doctype_layout = frappe.get_doc('DocType Layout', doctype_layout_name);
}
this.in_form = in_form ? true : false;
this.hidden = false;
this.refresh_if_stale_for = 120;
var me = this;
this.opendocs = {};
this.custom_buttons = {};
this.sections = [];
@ -39,17 +37,8 @@ frappe.ui.form.Form = class FrappeForm {
this.pformat = {};
this.fetch_dict = {};
this.parent = parent;
this.doctype_layout = frappe.get_doc('DocType Layout', doctype_layout_name);
this.setup_meta(doctype);
// show in form instead of in dialog, when called using url (router.js)
this.in_form = in_form ? true : false;
// notify on rename
$(document).on('rename', function(event, dt, old_name, new_name) {
if(dt==me.doctype)
me.rename_notify(dt, old_name, new_name);
});
}
setup_meta() {
@ -107,7 +96,7 @@ frappe.ui.form.Form = class FrappeForm {
this.script_manager.setup();
this.watch_model_updates();
if(!this.meta.hide_toolbar) {
if(!this.meta.hide_toolbar && frappe.boot.desk_settings.timeline) {
this.footer = new frappe.ui.form.Footer({
frm: this,
parent: $('<div>').appendTo(this.page.main.parent())
@ -117,6 +106,7 @@ frappe.ui.form.Form = class FrappeForm {
this.setup_file_drop();
this.setup_doctype_actions();
this.setup_docinfo_change_listener();
this.setup_notify_on_rename();
this.setup_done = true;
}
@ -175,6 +165,7 @@ frappe.ui.form.Form = class FrappeForm {
this.dashboard = new frappe.ui.form.Dashboard({
frm: this,
parent: $('<div class="form-dashboard">').insertAfter(this.layout.wrapper.find('.form-message'))
});
// workflow state
@ -222,6 +213,13 @@ frappe.ui.form.Form = class FrappeForm {
});
}
setup_notify_on_rename() {
$(document).on('rename', (ev, dt, old_name, new_name) => {
if(dt==this.doctype)
this.rename_notify(dt, old_name, new_name);
});
}
setup_file_drop() {
var me = this;
this.$wrapper.on('dragenter dragover', false)
@ -445,11 +443,13 @@ frappe.ui.form.Form = class FrappeForm {
this.layout.doc = this.doc;
this.layout.attach_doc_and_docfields();
this.sidebar = new frappe.ui.form.Sidebar({
frm: this,
page: this.page
});
this.sidebar.make();
if (frappe.boot.desk_settings.form_sidebar) {
this.sidebar = new frappe.ui.form.Sidebar({
frm: this,
page: this.page
});
this.sidebar.make();
}
// clear layout message
this.layout.show_message();
@ -1665,7 +1665,7 @@ frappe.ui.form.Form = class FrappeForm {
});
driver.defineSteps(steps);
frappe.route.on('change', () => driver.reset());
frappe.router.on('change', () => driver.reset());
driver.start();
}

View file

@ -121,7 +121,7 @@ frappe.form.formatters = {
{onclick: docfield.link_onclick.replace(/"/g, '&quot;'), value:value});
} else if(docfield && doctype) {
return `<a
href="/app/form/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
href="/app/${encodeURIComponent(frappe.router.slug(doctype))}/${encodeURIComponent(original_value)}"
data-doctype="${doctype}"
data-name="${original_value}">
${__(options && options.label || value)}</a>`

View file

@ -127,14 +127,6 @@ frappe.ui.form.Layout = Class.extend({
if (this.no_opening_section()) {
this.fields.unshift({fieldtype: 'Section Break'});
}
this.fields.unshift({
fieldtype: 'Section Break',
fieldname: '_form_dashboard',
cssClass: 'form-dashboard',
collapsible: 1,
// hidden: 1
});
},
replace_field: function(fieldname, df, render) {
@ -312,10 +304,6 @@ frappe.ui.form.Layout = Class.extend({
collapse = false;
}
if (df.fieldname === '_form_dashboard') {
collapse = localStorage.getItem('collapseFormDashboard')==='yes' ? true : false;
}
section.collapse(collapse);
}
}
@ -587,17 +575,13 @@ frappe.ui.form.Section = Class.extend({
wrapper: this.wrapper
};
if (this.df.collapsible && this.df.fieldname !== '_form_dashboard') {
this.collapse(true);
}
this.refresh();
},
make: function() {
if (!this.layout.page) {
this.layout.page = $('<div class="form-page"></div>').appendTo(this.layout.wrapper);
}
let make_card = this.layout.card_layout && this.df.fieldname !== '_form_dashboard';
let make_card = this.layout.card_layout;
this.wrapper = $(`<div class="row form-section ${ make_card ? "card-section" : "" }">`)
.appendTo(this.layout.page);
this.layout.sections.push(this);
@ -664,18 +648,12 @@ frappe.ui.form.Section = Class.extend({
hide = !this.body.hasClass("hide");
}
if (this.df.fieldname==='_form_dashboard') {
localStorage.setItem('collapseFormDashboard', hide ? 'yes' : 'no');
}
this.body.toggleClass("hide", hide);
this.head.toggleClass("collapsed", hide);
let indicator_icon = hide ? 'down' : 'up-line';
this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1'));
// this.indicator && this.indicator.toggleClass("octicon-chevron-down", hide);
// this.indicator && this.indicator.toggleClass("octicon-chevron-up", !hide);
// refresh signature fields
this.fields_list.forEach((f) => {

View file

@ -29,7 +29,9 @@ frappe.ui.form.Toolbar = Class.extend({
}
},
set_title: function() {
if(this.frm.meta.title_field) {
if (this.frm.is_new()) {
var title = __('New {0}', [this.frm.meta.name]);
} else if (this.frm.meta.title_field) {
let title_field = (this.frm.doc[this.frm.meta.title_field] || "").toString().trim();
var title = strip_html(title_field || this.frm.docname);
if(this.frm.doc.__islocal || title === this.frm.docname || this.frm.meta.autoname==="hash") {
@ -198,30 +200,40 @@ frappe.ui.form.Toolbar = Class.extend({
make_menu: function() {
this.page.clear_icons();
this.page.clear_menu();
var me = this;
var p = this.frm.perm[0];
var docstatus = cint(this.frm.doc.docstatus);
var is_submittable = frappe.model.is_submittable(this.frm.doc.doctype)
var issingle = this.frm.meta.issingle;
var print_settings = frappe.model.get_doc(":Print Settings", "Print Settings")
var allow_print_for_draft = cint(print_settings.allow_print_for_draft);
var allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled);
// Navigate
if (!this.frm.is_new() && !issingle) {
this.page.add_action_icon("left", function() {
me.frm.navigate_records(1);
}, 'prev-doc', __("Previous Document"));
this.page.add_action_icon("right", function() {
me.frm.navigate_records(0);
}, 'next-doc', __("Next Document"));
if (frappe.boot.desk_settings.form_sidebar) {
this.make_navigation();
this.make_menu_items();
}
},
make_navigation() {
// Navigate
if (!this.frm.is_new() && !this.frm.meta.issingle) {
this.page.add_action_icon("left", () => {
this.frm.navigate_records(1);
}, 'prev-doc');
this.page.add_action_icon("right", ()=> {
this.frm.navigate_records(0);
}, 'next-doc');
}
},
make_menu_items() {
// Print
const me = this;
const p = this.frm.perm[0];
const docstatus = cint(this.frm.doc.docstatus);
const is_submittable = frappe.model.is_submittable(this.frm.doc.doctype)
const print_settings = frappe.model.get_doc(":Print Settings", "Print Settings")
const allow_print_for_draft = cint(print_settings.allow_print_for_draft);
const allow_print_for_cancelled = cint(print_settings.allow_print_for_cancelled);
if (!is_submittable || docstatus == 1 ||
(allow_print_for_cancelled && docstatus == 2)||
(allow_print_for_draft && docstatus == 0)) {
if (frappe.model.can_print(null, me.frm) && !issingle) {
if (frappe.model.can_print(null, me.frm) && !this.frm.meta.issingle) {
this.page.add_menu_item(__("Print"), function() {
me.frm.print_doc();
}, true);
@ -283,30 +295,7 @@ frappe.ui.form.Toolbar = Class.extend({
});
}
if (frappe.user_roles.includes("System Manager")) {
let is_doctype_form = me.frm.doctype === 'DocType';
let doctype = is_doctype_form ? me.frm.docname : me.frm.doctype;
let is_doctype_custom = is_doctype_form ? me.frm.doc.custom : false;
if (doctype != 'DocType' && !is_doctype_custom && me.frm.meta.issingle === 0) {
this.page.add_menu_item(__("Customize"), function() {
if (me.frm.meta && me.frm.meta.custom) {
frappe.set_route('Form', 'DocType', doctype);
} else {
frappe.set_route('Form', 'Customize Form', {
doc_type: doctype
});
}
}, true);
}
if (frappe.boot.developer_mode===1 && !is_doctype_form) {
// edit doctype
this.page.add_menu_item(__("Edit DocType"), function() {
frappe.set_route('Form', 'DocType', me.frm.doctype);
}, true);
}
}
this.make_customize_buttons();
// Auto Repeat
if(this.can_repeat()) {
@ -325,6 +314,35 @@ frappe.ui.form.Toolbar = Class.extend({
});
}
},
make_customize_buttons() {
if (frappe.user_roles.includes("System Manager")) {
let is_doctype_form = this.frm.doctype === 'DocType';
let doctype = is_doctype_form ? this.frm.docname : this.frm.doctype;
let is_doctype_custom = is_doctype_form ? this.frm.doc.custom : false;
if (doctype != 'DocType' && !is_doctype_custom && this.frm.meta.issingle === 0) {
this.page.add_menu_item(__("Customize"), () => {
if (this.frm.meta && this.frm.meta.custom) {
frappe.set_route('Form', 'DocType', doctype);
} else {
frappe.set_route('Form', 'Customize Form', {
doc_type: doctype
});
}
}, true);
}
if (frappe.boot.developer_mode===1 && !is_doctype_form) {
// edit doctype
this.page.add_menu_item(__("Edit DocType"), () => {
frappe.set_route('Form', 'DocType', this.frm.doctype);
}, true);
}
}
},
can_repeat: function() {
return this.frm.meta.allow_auto_repeat
&& !this.frm.is_new()

View file

@ -182,15 +182,17 @@ frappe.views.BaseList = class BaseList {
'Dashboard': 'dashboard'
}
this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list');
this.views_list = new frappe.views.Views({
doctype: this.doctype,
parent: this.views_menu,
page: this.page,
list_view: this,
sidebar: this.list_sidebar,
icon_map: icon_map
});
if (frappe.boot.desk_settings.view_switcher) {
this.views_menu = this.page.add_custom_button_group(__(`{0} View`, [this.view_name]), icon_map[this.view_name] || 'list');
this.views_list = new frappe.views.Views({
doctype: this.doctype,
parent: this.views_menu,
page: this.page,
list_view: this,
sidebar: this.list_sidebar,
icon_map: icon_map
});
}
}
set_default_secondary_action() {
@ -236,7 +238,7 @@ frappe.views.BaseList = class BaseList {
}
setup_side_bar() {
if (this.hide_sidebar) return;
if (this.hide_sidebar || !frappe.boot.desk_settings.list_sidebar) return;
this.list_sidebar = new frappe.views.ListSidebar({
doctype: this.doctype,
stats: this.stats,

View file

@ -167,6 +167,7 @@ export default class ListFilter {
}
get_list_filters() {
if (frappe.session.user === 'Guest') return Promise.resolve();
return frappe.db
.get_list('List Filter', {
fields: ['name', 'filter_name', 'for_user', 'filters'],

View file

@ -9,7 +9,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
const doctype = route[1];
if (route.length === 2) {
// List/{doctype} => List/{doctype}/{last_view} or List
const user_settings = frappe.get_user_settings(doctype);
const last_view = user_settings.last_view;
frappe.set_route(
@ -871,7 +870,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
? encodeURIComponent(doc.name)
: doc.name;
return "/app/form/" + frappe.router.slug(frappe.router.doctype_layout || this.doctype) + "/" + docname;
return `/app/${frappe.router.slug(frappe.router.doctype_layout || this.doctype)}/${docname}`;
}
get_seen_class(doc) {
@ -905,6 +904,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
let escaped_subject = frappe.utils.escape_html(subject);
const seen = this.get_seen_class(doc);
console.log(this.get_form_link(doc));
let subject_html = `
<input class="level-item list-row-checkbox hidden-xs" type="checkbox"

View file

@ -35,7 +35,7 @@ frappe.views.Views = class Views {
}
set_route(view, calendar_name) {
const route = ['list', frappe.router.doctype_layout || this.doctype, view];
const route = [this.get_doctype_route(), 'view', view];
if (calendar_name) route.push(calendar_name);
frappe.set_route(route);
}
@ -156,7 +156,7 @@ frappe.views.Views = class Views {
if (item.name == frappe.utils.to_title_case(frappe.get_route().slice(-1)[0] || '')) {
placeholder = item.name;
}
html += `<li><a class="dropdown-item" href="#${item.route}">${item.name}</a></li>`;
html += `<li><a class="dropdown-item" href="/app/${item.route}">${item.name}</a></li>`;
});
}
@ -181,7 +181,7 @@ frappe.views.Views = class Views {
reports.map((r) => {
if (!r.ref_doctype || r.ref_doctype == this.doctype) {
const report_type = r.report_type === 'Report Builder' ?
`list/${r.ref_doctype}/report` : 'query-report';
`/app/list/${r.ref_doctype}/report` : 'query-report';
const route = r.route || report_type + '/' + (r.title || r.name);
@ -233,11 +233,11 @@ frappe.views.Views = class Views {
// has standard calendar view
calendars.push({
name: 'Default',
route: `list/${this.doctype}/calendar/default`
route: `/app/${this.get_doctype_route()}/view/calendar/default`
});
}
result.map(calendar => {
calendars.push({name: calendar.name, route: `list/${doctype}/calendar/${calendar.name}`});
calendars.push({name: calendar.name, route: `/app/${this.get_doctype_route()}/view/calendar/${calendar.name}`});
});
return calendars;
@ -249,7 +249,7 @@ frappe.views.Views = class Views {
let accounts = frappe.boot.email_accounts;
accounts.forEach(account => {
let email_account = (account.email_id == "All Accounts") ? "All Accounts" : account.email_account;
let route = ["List", "Communication", "Inbox", email_account].join('/');
let route = `/app/communication/inbox/${email_account}`;
let display_name = ["All Accounts", "Sent Mail", "Spam", "Trash"].includes(account.email_id)
? __(account.email_id)
: account.email_account;
@ -262,4 +262,8 @@ frappe.views.Views = class Views {
return accounts_to_add;
}
get_doctype_route() {
return frappe.router.slug(frappe.router.doctype_layout || this.doctype);
}
}

View file

@ -126,6 +126,7 @@ $.extend(frappe.model, {
var user_permissions = frappe.defaults.get_user_permissions();
let allowed_records = [];
let default_doc = null;
let value = null;
if(user_permissions) {
({allowed_records, default_doc} = frappe.perm.filter_allowed_docs_for_doctype(user_permissions[df.options], doc.doctype));
}
@ -139,71 +140,79 @@ $.extend(frappe.model, {
if (df.fieldtype==="Link" && df.options!=="User") {
// If user permission has Is Default enabled or single-user permission has found against respective doctype.
if (has_user_permissions && default_doc) {
return default_doc;
}
if(!df.ignore_user_permissions) {
value = default_doc;
} else {
// 2 - look in user defaults
var user_defaults = frappe.defaults.get_user_defaults(df.options);
if (user_defaults && user_defaults.length===1) {
// Use User Permission value when only when it has a single value
user_default = user_defaults[0];
if(!df.ignore_user_permissions) {
var user_defaults = frappe.defaults.get_user_defaults(df.options);
if (user_defaults && user_defaults.length===1) {
// Use User Permission value when only when it has a single value
user_default = user_defaults[0];
}
}
else if (!user_default) {
user_default = frappe.defaults.get_user_default(df.fieldname);
}
else if(!user_default && df.remember_last_selected_value && frappe.boot.user.last_selected_values) {
user_default = frappe.boot.user.last_selected_values[df.options];
}
var is_allowed_user_default = user_default &&
(!has_user_permissions || allowed_records.includes(user_default));
// is this user default also allowed as per user permissions?
if (is_allowed_user_default) {
value = user_default;
}
}
if (!user_default) {
user_default = frappe.defaults.get_user_default(df.fieldname);
}
if(!user_default && df.remember_last_selected_value && frappe.boot.user.last_selected_values) {
user_default = frappe.boot.user.last_selected_values[df.options];
}
var is_allowed_user_default = user_default &&
(!has_user_permissions || allowed_records.includes(user_default));
// is this user default also allowed as per user permissions?
if (is_allowed_user_default) {
return user_default;
}
}
// 3 - look in default of docfield
if (df['default']) {
if (!value || df['default']) {
const default_val = String(df['default']);
if (default_val == "__user" || default_val.toLowerCase() == "user") {
return frappe.session.user;
value = frappe.session.user;
} else if (default_val == "user_fullname") {
return frappe.session.user_fullname;
value = frappe.session.user_fullname;
} else if (default_val == "Today") {
return frappe.datetime.get_today();
value = frappe.datetime.get_today();
} else if ((default_val || "").toLowerCase() === "now") {
return frappe.datetime.now_datetime();
value = frappe.datetime.now_datetime();
} else if (default_val[0]===":") {
var boot_doc = frappe.model.get_default_from_boot_docs(df, doc, parent_doc);
var is_allowed_boot_doc = !has_user_permissions || allowed_records.includes(boot_doc);
if (is_allowed_boot_doc) {
return boot_doc;
value = boot_doc;
}
} else if (df.fieldname===meta.title_field) {
// ignore defaults for title field
return "";
value = "";
} else {
// is this default value is also allowed as per user permissions?
var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default);
if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) {
value = df["default"];
}
}
// is this default value is also allowed as per user permissions?
var is_allowed_default = !has_user_permissions || allowed_records.includes(df.default);
if (df.fieldtype!=="Link" || df.options==="User" || is_allowed_default) {
return df["default"];
}
} else if (df.fieldtype=="Time") {
return frappe.datetime.now_time();
value = frappe.datetime.now_time();
}
// set it here so we know it was set as a default
df.__default_value = value;
return value;
},
get_default_from_boot_docs: function(df, doc, parent_doc) {

View file

@ -6,6 +6,8 @@ $.extend(frappe.model.user_settings, {
.then(r => JSON.parse(r.message || '{}'));
},
save: function(doctype, key, value) {
if (frappe.session.user === 'Guest') return Promise.resolve();
const old_user_settings = frappe.model.user_settings[doctype] || {};
const new_user_settings = $.extend(true, {}, old_user_settings); // deep copy
@ -31,6 +33,7 @@ $.extend(frappe.model.user_settings, {
return this.update(doctype, user_settings);
},
update: function(doctype, user_settings) {
if (frappe.session.user === 'Guest') return Promise.resolve();
return frappe.call({
method: 'frappe.model.utils.user_settings.save',
args: {

View file

@ -8,6 +8,7 @@ frappe.provide('frappe.request.error_handlers');
frappe.request.url = '/';
frappe.request.ajax_count = 0;
frappe.request.waiting_for_ajax = [];
frappe.request.logs = {}
frappe.xcall = function(method, params) {
return new Promise((resolve, reject) => {
@ -89,6 +90,11 @@ frappe.call = function(opts) {
delete args.cmd;
}
// debouce if required
if (opts.debounce && frappe.request.is_fresh(args, opts.debounce)) {
return Promise.resolve();
}
return frappe.request.call({
type: opts.type || "POST",
args: args,
@ -127,7 +133,7 @@ frappe.request.call = function(opts) {
message: __('The resource you are looking for is not available')});
},
403: function(xhr) {
if (frappe.session.user === 'Guest') {
if (frappe.session.logged_in_user !== 'Guest') {
// session expired
frappe.app.handle_session_expired();
}
@ -239,7 +245,7 @@ frappe.request.call = function(opts) {
status_code_handler(data, xhr);
}
} catch(e) {
console.log("Unable to handle success response"); // eslint-disable-line
console.log("Unable to handle success response", data); // eslint-disable-line
console.trace(e); // eslint-disable-line
}
@ -278,6 +284,26 @@ frappe.request.call = function(opts) {
});
}
frappe.request.is_fresh = function(args, threshold) {
// return true if a request with similar args has been sent recently
if (!frappe.request.logs[args.cmd]) {
frappe.request.logs[args.cmd] = [];
}
for (let past_request of frappe.request.logs[args.cmd]) {
// check if request has same args and was made recently
if ((new Date() - past_request.timestamp) < threshold
&& frappe.utils.deep_equal(args, past_request.args)) {
console.log('throttled');
return true;
}
}
// log the request
frappe.request.logs[args.cmd].push({args: args, timestamp: new Date()});
return false;
}
// call execute serverside request
frappe.request.prepare = function(opts) {
$("body").attr("data-ajax-state", "triggered");
@ -322,7 +348,8 @@ frappe.request.cleanup = function(opts, r) {
if(r) {
// session expired? - Guest has no business here!
if (r.session_expired || frappe.session.user === "Guest") {
if (r.session_expired ||
(frappe.session.user === 'Guest' && frappe.session.logged_in_user !== "Guest")) {
frappe.app.handle_session_expired();
return;
}

View file

@ -20,12 +20,13 @@ $(window).on('hashchange', function() {
let sub_path = frappe.router.get_sub_path(window.location.hash);
window.location.hash = '';
frappe.router.push_state(sub_path);
return false;
}
});
window.addEventListener('popstate', () => {
// forward-back button, just re-render based on current route
frappe.route();
frappe.router.route();
});
// routing v2, capture all clicks so that the target is managed with push-state
@ -52,18 +53,40 @@ $('body').on('click', 'a', function(e) {
}
// target has "/app, this is a v2 style route.
if (e.currentTarget.pathname &&
(e.currentTarget.pathname.startsWith('/app') || e.currentTarget.pathname.startsWith('app'))) {
if (e.currentTarget.pathname && frappe.router.is_app_route()) {
return override(e, e.currentTarget.pathname);
}
});
frappe.router = {
current_route: null,
doctype_names: {},
factory_views: ['form', 'list', 'report', 'tree', 'print'],
routes: {},
factory_views: ['form', 'list', 'report', 'tree', 'print', 'dashboard'],
list_views: ['list', 'kanban', 'report', 'calendar', 'tree', 'gantt', 'dashboard', 'image', 'inbox'],
layout_mapped: {},
is_app_route() {
// desk paths must begin with /app or doctype route
let path = window.location.pathname;
if (path.substr(0, 1) === '/') path = path.substr(1);
path = path.split('/');
if (path[0]) {
return path[0]==='app';
}
},
setup() {
// setup the route names by forming slugs of the given doctypes
for(let doctype of frappe.boot.user.can_read) {
this.routes[this.slug(doctype)] = {doctype: doctype};
}
if (frappe.boot.doctype_layouts) {
for (let doctype_layout of frappe.boot.doctype_layouts) {
this.routes[this.slug(doctype_layout.name)] = {doctype: doctype_layout.document_type, doctype_layout: doctype_layout.name };
}
}
},
route() {
// resolve the route from the URL or hash
// translate it so the objects are well defined
@ -71,64 +94,81 @@ frappe.router = {
if (!frappe.app) return;
let sub_path = frappe.router.get_sub_path();
if (frappe.router.re_route(sub_path)) return;
let sub_path = this.get_sub_path();
if (this.re_route(sub_path)) return;
frappe.router.translate_doctype_name().then(() => {
frappe.router.set_history(sub_path);
if (frappe.router.current_route[0]) {
frappe.router.render_page();
} else {
// Show home
frappe.views.pageview.show('');
}
frappe.router.set_title();
frappe.route.trigger('change');
});
this.current_route = this.parse();
this.set_history(sub_path);
this.render();
this.set_title();
this.trigger('change');
},
translate_doctype_name() {
return new Promise((resolve) => {
const route = frappe.router.current_route = frappe.router.parse();
const factory = route[0].toLowerCase();
const set_name = () => {
const d = frappe.router.doctype_names[route[1]];
route[1] = d.doctype;
frappe.router.doctype_layout = d.doctype_layout;
resolve();
};
parse(route) {
route = this.get_sub_path_string(route).split('/');
route = $.map(route, this.decode_component);
this.set_route_options_from_url(route);
return this.convert_to_standard_route(route);
},
if (frappe.router.factory_views.includes(factory)) {
// translate the doctype to its original name
if (frappe.router.doctype_names[route[1]]) {
set_name();
convert_to_standard_route(route) {
// /app/user = ["List", "User"]
// /app/user/view/report = ["List", "User", "Report"]
// /app/user/view/tree = ["Tree", "User"]
// /app/user/user-001 = ["Form", "User", "user-001"]
// /app/user/user-001 = ["Form", "User", "user-001"]
let standard_route = route;
let doctype_route = this.routes[route[0]];
if (doctype_route) {
// doctype route
if (route[1]) {
if (route[2] && route[1]==='view') {
if (route[2].toLowerCase()==='tree') {
standard_route = ['Tree', doctype_route.doctype];
} else {
standard_route = ['List', doctype_route.doctype, frappe.utils.to_title_case(route[2])];
}
} else {
frappe.xcall('frappe.desk.utils.get_doctype_name', {name: route[1]}).then((data) => {
frappe.router.doctype_names[route[1]] = data.name_map;
set_name();
});
standard_route = ['Form', doctype_route.doctype, route[1]];
}
} else if (frappe.model.is_single(doctype_route.doctype)) {
standard_route = ['Form', doctype_route.doctype, doctype_route.doctype];
} else {
resolve();
standard_route = ['List', doctype_route.doctype, 'List'];
}
});
if (doctype_route.doctype_layout) {
// set the layout
this.doctype_layout = doctype_route.doctype_layout;
}
}
return standard_route;
},
set_history(sub_path) {
frappe.route_history.push(frappe.router.current_route);
frappe.route_history.push(this.current_route);
frappe.route_titles[sub_path] = frappe._original_title || document.title;
frappe.ui.hide_open_dialog();
},
render() {
if (this.current_route[0]) {
this.render_page();
} else {
// Show home
frappe.views.pageview.show('');
}
},
render_page() {
// create the page generator (factory) object and call `show`
// if there is no generator, render the `Page` object
// first the router needs to know if its a "page", "doctype", "workspace"
const route = frappe.router.current_route;
const route = this.current_route;
const factory = frappe.utils.to_title_case(route[0]);
if (factory === 'Workspace') {
frappe.views.pageview.show('');
@ -155,12 +195,12 @@ frappe.router = {
re_route(sub_path) {
if (frappe.re_route[sub_path] !== undefined) {
// after saving a doc, for example,
// "New DocType 1" and the renamed "TestDocType", both exist in history
// "new-doctype-1" and the renamed "TestDocType", both exist in history
// now if we try to go back,
// it doesn't allow us to go back to the one prior to "New DocType 1"
// it doesn't allow us to go back to the one prior to "new-doctype-1"
// Hence if this check is true, instead of changing location hash,
// we just do a back to go to the doc previous to the "New DocType 1"
var re_route_val = frappe.router.get_sub_path(frappe.re_route[sub_path]);
// we just do a back to go to the doc previous to the "new-doctype-1"
var re_route_val = this.get_sub_path(frappe.re_route[sub_path]);
if (decodeURIComponent(re_route_val) === decodeURIComponent(sub_path)) {
window.history.back();
return true;
@ -185,32 +225,14 @@ frappe.router = {
// set the route (push state) with given arguments
// example 1: frappe.set_route('a', 'b', 'c');
// example 2: frappe.set_route(['a', 'b', 'c']);
// example 3: frappe.set_route('a/b/c');
// example 3: frappe.set_route('a/b/c');
let route = arguments;
return new Promise(resolve => {
var route = arguments;
if (route.length===1 && $.isArray(route[0])) {
// called as frappe.set_route(['a', 'b', 'c']);
route = route[0];
}
if (route.length===1 && route[0].includes('/')) {
// called as frappe.set_route('a/b/c')
route = $.map(route[0].split('/'), frappe.router.decode_component);
}
if (route && route[0] == '') {
route.shift();
}
if (route && ['desk', 'app'].includes(route[0])) {
// we only need subpath, remove "app" (or "desk")
route.shift();
}
frappe.router.slug_parts(route);
const sub_path = frappe.router.make_url_from_list(route);
frappe.router.push_state(sub_path);
route = this.get_route_from_arguments(route);
route = this.convert_from_standard_route(route);
const sub_path = this.make_url(route);
this.push_state(sub_path);
setTimeout(() => {
frappe.after_ajax && frappe.after_ajax(() => {
@ -220,19 +242,67 @@ frappe.router = {
});
},
slug_parts(route) {
// slug doctype
get_route_from_arguments(route) {
if (route.length===1 && $.isArray(route[0])) {
// called as frappe.set_route(['a', 'b', 'c']);
route = route[0];
}
// if app is part of the route, then first 2 elements are "" and "app"
if (route[0] && frappe.router.factory_views.includes(route[0].toLowerCase())) {
route[0] = route[0].toLowerCase();
route[1] = frappe.router.slug(route[1]);
if (route.length===1 && route[0].includes('/')) {
// called as frappe.set_route('a/b/c')
route = $.map(route[0].split('/'), this.decode_component);
}
if (route && route[0] == '') {
route.shift();
}
if (route && ['desk', 'app'].includes(route[0])) {
// we only need subpath, remove "app" (or "desk")
route.shift();
}
return route;
},
convert_from_standard_route(route) {
// ["List", "Sales Order"] => /sales-order
// ["Form", "Sales Order", "SO-0001"] => /sales-order/SO-0001
// ["Tree", "Account"] = /account/view/tree
const view = route[0].toLowerCase();
if (view === 'list') {
if (route[2] && route[2] !== 'list') {
const new_route = [this.slug(route[1]), 'view', route[2].toLowerCase()];
// calendar / inbox
if (route[3]) new_route.push(route[3]);
return new_route;
} else {
return [this.slug(route[1])]
}
} else if (view === 'form') {
return [this.slug(route[1]), route[2]]
} else if (view === 'tree') {
return [this.slug(route[1]), 'view', 'tree'];
}
return route;
},
make_url_from_list(params) {
return $.map(params, function(a) {
slug_parts(route) {
// slug doctype
// if app is part of the route, then first 2 elements are "" and "app"
if (route[0] && this.factory_views.includes(route[0].toLowerCase())) {
route[0] = route[0].toLowerCase();
route[1] = this.slug(route[1]);
}
return route;
},
make_url(params) {
return '/app/' + $.map(params, function(a) {
if ($.isPlainObject(a)) {
frappe.route_options = a;
return null;
@ -247,10 +317,8 @@ frappe.router = {
}).join('/');
},
push_state(sub_path) {
push_state(url) {
// change the URL and call the router
const url = `/app/${sub_path}`;
if (window.location.pathname !== url) {
// cleanup any remenants of v1 routing
window.location.hash = '';
@ -259,19 +327,10 @@ frappe.router = {
history.pushState(null, null, url);
// now process the route
frappe.router.route();
this.route();
}
},
parse(route) {
route = frappe.router.get_sub_path_string(route).split('/');
route = $.map(route, frappe.router.decode_component);
frappe.router.set_route_options_from_url(route);
return route;
},
get_sub_path_string(route) {
// return clean sub_path from hash or url
// supports both v1 and v2 routing
@ -279,7 +338,7 @@ frappe.router = {
route = window.location.hash || window.location.pathname;
}
return frappe.router.strip_prefix(route);
return this.strip_prefix(route);
},
strip_prefix(route) {
@ -292,8 +351,8 @@ frappe.router = {
},
get_sub_path(route) {
var sub_path = frappe.router.get_sub_path_string(route);
route = $.map(sub_path.split('/'), frappe.router.decode_component).join('/');
var sub_path = this.get_sub_path_string(route);
route = $.map(sub_path.split('/'), this.decode_component).join('/');
return route;
},
@ -333,10 +392,9 @@ frappe.router = {
};
// global functions for backward compatibility
frappe.route = frappe.router.route;
frappe.get_route = () => frappe.router.current_route;
frappe.get_route_str = () => frappe.router.current_route.join('/');
frappe.set_route = frappe.router.set_route;
frappe.set_route = function() { return frappe.router.set_route.apply(frappe.router, arguments) };
frappe.get_prev_route = function() {
if (frappe.route_history && frappe.route_history.length > 1) {
@ -356,4 +414,4 @@ frappe.has_route_options = function() {
return Boolean(Object.keys(frappe.route_options || {}).length);
};
frappe.utils.make_event_emitter(frappe.route);
frappe.utils.make_event_emitter(frappe.router);

View file

@ -1,19 +1,21 @@
frappe.provide('frappe.route');
frappe.route_history_queue = [];
const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder'];
const save_routes = frappe.utils.debounce(() => {
if (frappe.session.user === 'Guest') return;
const routes = frappe.route_history_queue;
frappe.route_history_queue = [];
frappe.xcall('frappe.deferred_insert.deferred_insert', {
'doctype': 'Route History',
'records': routes
}).catch(() => {
frappe.route_history_queue.concat(routes);
});
});
}, 10000);
frappe.route.on('change', () => {
frappe.router.on('change', () => {
const route = frappe.get_route();
if (is_route_useful(route)) {
frappe.route_history_queue.push({

View file

@ -12,8 +12,7 @@ frappe.ui.Notifications = class Notifications {
}
make() {
this.dropdown = $('.navbar').find('.dropdown-notifications');
this.dropdown.removeClass("hidden")
this.dropdown = $('.navbar').find('.dropdown-notifications').removeClass('hidden');
this.dropdown_list = this.dropdown.find('.notifications-list');
this.header_items = this.dropdown_list.find('.header-items');
this.header_actions = this.dropdown_list.find('.header-actions');

View file

@ -20,7 +20,7 @@ frappe.ui.Tags = class {
setup(parent, placeholder) {
this.$ul = parent;
this.$input = $(`<input class="tags-input"></input>`);
this.$input = $(`<input class="tags-input form-control"></input>`);
this.$inputWrapper = this.get_list_element(this.$input);
this.$placeholder = this.get_list_element($(`<span class="tags-placeholder text-muted">${placeholder}</span>`));

View file

@ -7,6 +7,7 @@ frappe.search.AwesomeBar = Class.extend({
setup: function(element) {
var me = this;
$('.search-bar').removeClass('hidden');
var $input = $(element);
var input = $input.get(0);
@ -122,6 +123,7 @@ frappe.search.AwesomeBar = Class.extend({
$input.blur();
});
frappe.search.utils.setup_recent();
frappe.tags.utils.fetch_tags();
},
add_help: function() {

View file

@ -6,7 +6,7 @@
<ul class="nav navbar-nav d-none d-sm-flex" id="navbar-breadcrumbs"></ul>
<div class="collapse navbar-collapse justify-content-end">
<form class="form-inline fill-width justify-content-end" role="search" onsubmit="return false;">
<div class="input-group search-bar text-muted">
<div class="input-group search-bar text-muted hidden">
<input
id="navbar-search"
type="text"

View file

@ -402,7 +402,7 @@ frappe.search.SearchDialog = class {
frappe.set_route(result.route);
// hashchange didn't fire!
if (window.location.hash == previous_hash) {
frappe.route();
frappe.router.route();
}
}
});

View file

@ -14,15 +14,12 @@ frappe.ui.toolbar.Toolbar = class {
this.setup_awesomebar();
this.setup_notifications();
this.setup_help();
this.make();
}
make () {
// this.setup_sidebar();
this.setup_help();
this.bind_events();
$(document).trigger('toolbar_setup');
}
@ -46,6 +43,12 @@ frappe.ui.toolbar.Toolbar = class {
setup_help () {
if (!frappe.boot.desk_settings.notifications) {
// hide the help section
$('.navbar .vertical-bar').removeClass('d-sm-block');
$('.dropdown-help').removeClass('d-lg-block');
return;
}
frappe.provide('frappe.help');
frappe.help.show_results = show_results;

View file

@ -816,7 +816,7 @@ Object.assign(frappe.utils, {
display_text = display_text || name;
doctype = encodeURIComponent(doctype);
name = encodeURIComponent(name);
const route = ['/app/Form', doctype, name].join('/');
const route = `/app/${frappe.router.slug(doctype)}/${name}`;
if (html) {
return `<a href="${route}">${display_text}</a>`;
}
@ -1127,7 +1127,7 @@ Object.assign(frappe.utils, {
let doctype_slug = frappe.router.slug(item.doctype);
if (frappe.model.is_single(item.doctype)) {
route = "form/" + doctype_slug;
route = doctype_slug;
} else {
if (!item.doc_view) {
if (frappe.model.is_tree(item.doctype)) {
@ -1142,22 +1142,22 @@ Object.assign(frappe.utils, {
if (item.filters) {
frappe.route_options = item.filters;
}
route = "list/" + doctype_slug;
route = doctype_slug;
break;
case "Tree":
route = "tree/" + doctype_slug;
route = `${doctype_slug}/view/tree`;
break;
case "Report Builder":
route = "list/" + doctype_slug + "/Report";
route = `${doctype_slug}/view/report`;
break;
case "Dashboard":
route = "list/" + doctype_slug + "/Dashboard";
route = `${doctype_slug}/view/dashboard`;
break;
case "New":
route = "form/" + doctype_slug + "/New " + item.doctype;
route = `${doctype_slug}/new`;
break;
case "Calendar":
route = "list/" + doctype_slug + "/Calendar/Default";
route = `${doctype_slug}/view/calendar/Default`;
break;
default:
frappe.throw({ message: __("Not a valid DocType view:") + item.doc_view, title: __("Unknown View") });
@ -1167,7 +1167,7 @@ Object.assign(frappe.utils, {
} else if (type === "report" && item.is_query_report) {
route = "query-report/" + item.name;
} else if (type === "report") {
route = "List/" + frappe.router.slug(item.doctype) + "/Report/" + item.name;
route = frappe.router.slug(item.doctype) + "/view/report/" + item.name;
} else if (type === "page") {
route = item.name;
} else if (type === "dashboard") {

View file

@ -114,11 +114,12 @@ frappe.breadcrumbs = {
// no user listview for non-system managers and single doctypes
} else {
let route;
const doctype_route = frappe.router.slug(frappe.router.doctype_layout || breadcrumbs.doctype);
if (frappe.boot.treeviews.indexOf(breadcrumbs.doctype) !== -1) {
let view = frappe.model.user_settings[breadcrumbs.doctype].last_view || 'Tree';
route = view + '/' + breadcrumbs.doctype;
route = `${doctype_route}/view/${view}`;
} else {
route = 'list/' + (frappe.router.doctype_layout || breadcrumbs.doctype);
route = doctype_route;
}
$(`<li><a href="/app/${route}">${doctype}</a></li>`)
.appendTo($breadcrumbs)
@ -132,7 +133,7 @@ frappe.breadcrumbs = {
if (breadcrumbs.doctype && frappe.get_route()[0] === "print") {
set_list_breadcrumb(breadcrumbs.doctype);
let docname = frappe.get_route()[2];
let form_route = `/app/form/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`;
let form_route = `/app/${frappe.router.slug(breadcrumbs.doctype)}/${docname}`;
$(`<li><a href="${form_route}">${docname}</a></li>`)
.appendTo($breadcrumbs);
}

View file

@ -12,8 +12,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
frappe.model.with_doctype(doctype, () => {
this.page = frappe.container.add_page("form/" + doctype_layout);
frappe.views.formview[doctype_layout] = this.page;
this.page.frm = new frappe.ui.form.Form(doctype, this.page, true, frappe.router.doctype_layout);
this.show_doc(route);
this.make_and_show(doctype, route);
});
} else {
this.show_doc(route);
@ -22,6 +21,22 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
this.setup_events();
}
make_and_show(doctype, route) {
if (frappe.router.doctype_layout) {
frappe.model.with_doc('DocType Layout', frappe.router.doctype_layout, () => {
this.make_form(doctype);
this.show_doc(route);
})
} else {
this.make_form(doctype);
this.show_doc(route);
}
}
make_form(doctype) {
this.page.frm = new frappe.ui.form.Form(doctype, this.page, true, frappe.router.doctype_layout);
}
setup_events() {
if (!this.initialized) {
$(document).on("page-change", function() {
@ -54,7 +69,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
}
const doc = frappe.get_doc(doctype, name);
if (doc && (doc.__islocal || frappe.model.is_fresh(doc))) {
if (doc && frappe.model.get_docinfo(doctype, name) && (doc.__islocal || frappe.model.is_fresh(doc))) {
// is document available and recent?
this.render(doctype_layout, name);
} else {
@ -67,7 +82,7 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
if (r && r['403']) return; // not permitted
if (!(locals[doctype] && locals[doctype][name])) {
if (name && name==='new') {
if (name && name.substr(0,3)==='new') {
this.render_new_doc(doctype, name, doctype_layout);
} else {
frappe.show_not_found(route);

View file

@ -205,7 +205,7 @@ frappe.views.KanbanView.get_kanbans = function (doctype) {
.then((kanban_boards) => {
if (kanban_boards) {
kanban_boards.forEach(board => {
let route = ['List', board.reference_doctype, 'Kanban', board.name].join('/');
let route = `/app/${frappe.router.slug(board.reference_doctype)}/kanban/${board.name}`;
kanbans.push({ name: board.name, route: route });
});
}

View file

@ -44,16 +44,6 @@ details > summary:focus {
color: @border-color !important;
}
// transition
a,
.badge {
.transition(.2s);
}
.btn {
.transition(background-color .2s);
}
a.disabled,
a.disabled:hover {
color: #888;

View file

@ -123,24 +123,6 @@
}
}
.progress-area {
padding-top: 15px;
padding-bottom: 15px;
.progress-chart {
padding-top: 15px;
}
.progress {
margin-bottom: 5px;
}
.progress-message {
font-feature-settings: "tnum" 1;
margin-top: 0px;
}
}
.form-section {
margin: 0px;
// padding: 15px;
@ -173,21 +155,10 @@
}
}
.hide-border {
border-top: none !important;
padding-top: 0px;
}
.empty-section {
display: none !important;
}
.modal {
.hide-border {
padding-top: 0;
}
}
.help ol {
padding-left: 19px;
}

View file

@ -3,11 +3,13 @@
font-size: var(--text-md);
}
.form-section {
.form-section, .form-dashboard-section {
margin: 0px;
.form-section-description {
margin-bottom: 10px;
font-size: var(--text-xs);
color: var(--text-muted);
}
.section-head {
@ -59,14 +61,11 @@
.section-body:first-child {
margin-top: 0;
}
.section-head {
padding-left: 0;
padding-right: 0;
}
.form-dashboard-section {
padding-left: calc(var(--padding-lg) + var(--padding-xs));
padding-right: calc(var(--padding-lg) + var(--padding-xs));
padding-bottom: var(--padding-lg);
.form-dashboard-section .section-body {
display: block;
padding-left: var(--padding-md);
padding-right: var(--padding-md);
padding-bottom: var(--padding-md);
}
}
@ -272,3 +271,21 @@
right: 0;
}
}
.progress-area {
padding-top: var(--padding-md);
padding-bottom: var(--padding-md);
.progress-chart {
padding-top: var(--padding-lg);
}
.progress {
margin-bottom: var(--margin-xs);
}
.progress-message {
font-feature-settings: "tnum" 1;
margin-top: 0px;
}
}

View file

@ -2,6 +2,21 @@ html {
height: 100%;
}
// transition
* {
transition: background-color 0.5s, background 0.5s;
}
a,
.badge {
transition: 0.2s,
}
.btn {
transition: background-color 0.2s, background 0.2s;
}
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@ -264,26 +279,25 @@ select.input-xs {
/* dropdowns */
.dropdown-backdrop {
display: none;
}
.dropdown-item {
border-radius: 4px;
}
.dropdown-menu {
min-width: 200px;
padding: 4px;
font-size: var(--text-md);
max-height: 500px;
overflow: auto;
color: $text-color;
border-radius: 6px;
.dropdown-divider {
margin: 4px 0;
}
a:not([href]):hover:active {
color: $component-active-color ;
}
a {
cursor: pointer;
}
}
.dropdown-menu .divider {
@ -428,10 +442,10 @@ kbd {
cursor: default;
}
.dropdown-menu {
margin-top: var(--margin-xs);
box-shadow: var(--shadow-sm);
}
// .dropdown-menu {
// margin-top: var(--margin-xs);
// box-shadow: var(--shadow-sm);
// }
.btn-link {
box-shadow: none !important;

View file

@ -382,6 +382,13 @@ body[data-route^="Module"] .main-menu {
max-width: 100%;
}
.attachment-row, .form-tag-row {
.data-pill {
background-color: var(--bg-gray);
box-shadow: none;
}
}
.form-tag-row {
margin-right: var(--margin-xs);
display: inline-flex;
@ -492,7 +499,7 @@ button.data-pill {
.tags-input {
margin-bottom: var(--margin-sm);
font-size: var(--text-md);
font-size: var(--text-xs);
background: inherit;
border: none;
outline: none;

View file

@ -2,10 +2,6 @@ html, body {
font-size: 16px;
}
* {
transition: background-color 0.5s, background 0.5s;
}
// colors
$gray-900: #192734;
$gray-800: #313B44;
@ -34,6 +30,8 @@ $border-radius: var(--border-radius);
$border-radius-sm: var(--border-radius-sm);
$border-radius-lg: var(--border-radius-lg);
$nav-divider-margin-y: 4px;
$container-max-widths: (
sm: 540px,
md: 840px,
@ -69,6 +67,7 @@ $input-focus-border-color: var(--gray-500);
$input-border-radius: var(--border-radius);
// dropdown
$dropdown-color: var(--text-color);
$dropdown-bg: var(--fg-color);
$dropdown-border-color: var(--dark-border-color);
$dropdown-link-color: var(--text-color);
@ -77,6 +76,7 @@ $dropdown-link-hover-bg: var(--fg-hover-color);
$dropdown-link-disabled-color: var(--gray-600);
$dropdown-header-color: var(--gray-600);
$dropdown-divider-bg: var(--border-color);
$dropdown-min-width: 200px;
// button
$btn-padding-y-lg: 1rem !default;

View file

@ -21,7 +21,7 @@ from six.moves.urllib.parse import unquote
from six import text_type
from frappe.cache_manager import clear_user_cache
@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def clear(user=None):
frappe.local.session_obj.update(force=True)
frappe.local.db.commit()

View file

@ -200,7 +200,7 @@
"is_published_field": "published",
"links": [],
"max_attachments": 5,
"modified": "2020-08-31 21:01:51.100349",
"modified": "2020-12-07 23:27:23.644512",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Post",
@ -229,7 +229,7 @@
"write": 1
}
],
"route": "/blog",
"route": "blog-post",
"sort_field": "modified",
"sort_order": "ASC",
"title_field": "title",

View file

@ -14,6 +14,7 @@ from frappe.website.utils import (find_first_image, get_html_content_based_on_ty
class BlogPost(WebsiteGenerator):
website = frappe._dict(
route = 'blog',
order_by = "published_on desc"
)

View file

@ -12,8 +12,8 @@ from frappe import _
import frappe.sessions
def get_context(context):
if frappe.session.user == "Guest":
frappe.throw(_("Log in to access this page."), frappe.PermissionError)
# if frappe.session.user == "Guest":
# frappe.throw(_("Log in to access this page."), frappe.PermissionError)
# elif frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User":
# frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError)

View file

@ -25,8 +25,10 @@ def get_context(context):
redirect_to = get_home_page()
else:
redirect_to = "/app"
frappe.local.flags.redirect_location = redirect_to
raise frappe.Redirect
if redirect_to != 'login':
frappe.local.flags.redirect_location = redirect_to
raise frappe.Redirect
# get settings from site config
context.no_header = True