fix: Added Onboarding Block
This commit is contained in:
parent
4a05697cfe
commit
8739b5e3ad
9 changed files with 254 additions and 25 deletions
|
|
@ -51,8 +51,9 @@ class Workspace:
|
|||
self.allowed_reports = get_allowed_reports(cache=True)
|
||||
|
||||
if not minimal:
|
||||
self.onboarding_doc = self.get_onboarding_doc()
|
||||
self.onboarding = None
|
||||
if self.doc.content:
|
||||
self.onboarding_list = [x['data']['onboarding_name'] for x in loads(self.doc.content) if x['type'] == 'onboarding']
|
||||
self.onboardings = []
|
||||
|
||||
self.table_counts = get_table_with_counts()
|
||||
self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache()
|
||||
|
|
@ -125,18 +126,18 @@ class Workspace:
|
|||
|
||||
return self.user.allow_modules
|
||||
|
||||
def get_onboarding_doc(self):
|
||||
def get_onboarding_doc(self, onboarding):
|
||||
# Check if onboarding is enabled
|
||||
if not frappe.get_system_settings("enable_onboarding"):
|
||||
return None
|
||||
|
||||
if not self.doc.onboarding:
|
||||
if not self.onboarding_list:
|
||||
return None
|
||||
|
||||
if frappe.db.get_value("Module Onboarding", self.doc.onboarding, "is_complete"):
|
||||
if frappe.db.get_value("Module Onboarding", onboarding, "is_complete"):
|
||||
return None
|
||||
|
||||
doc = frappe.get_doc("Module Onboarding", self.doc.onboarding)
|
||||
doc = frappe.get_doc("Module Onboarding", onboarding)
|
||||
|
||||
# Check if user is allowed
|
||||
allowed_roles = set(doc.get_allowed_roles())
|
||||
|
|
@ -200,14 +201,9 @@ class Workspace:
|
|||
'items': self.get_shortcuts()
|
||||
}
|
||||
|
||||
if self.onboarding_doc:
|
||||
self.onboarding = {
|
||||
'label': _(self.onboarding_doc.title),
|
||||
'subtitle': _(self.onboarding_doc.subtitle),
|
||||
'success': _(self.onboarding_doc.success_message),
|
||||
'docs_url': self.onboarding_doc.documentation_url,
|
||||
'items': self.get_onboarding_steps()
|
||||
}
|
||||
self.onboardings = {
|
||||
'items': self.get_onboardings()
|
||||
}
|
||||
|
||||
def _doctype_contains_a_record(self, name):
|
||||
exists = self.table_counts.get(name, False)
|
||||
|
|
@ -336,9 +332,26 @@ class Workspace:
|
|||
return items
|
||||
|
||||
@handle_not_exist
|
||||
def get_onboarding_steps(self):
|
||||
def get_onboardings(self):
|
||||
if self.onboarding_list:
|
||||
for onboarding in self.onboarding_list:
|
||||
onboarding_doc = self.get_onboarding_doc(onboarding)
|
||||
if onboarding_doc:
|
||||
item = {
|
||||
'label': _(onboarding),
|
||||
'title': _(onboarding_doc.title),
|
||||
'subtitle': _(onboarding_doc.subtitle),
|
||||
'success': _(onboarding_doc.success_message),
|
||||
'docs_url': onboarding_doc.documentation_url,
|
||||
'items': self.get_onboarding_steps(onboarding_doc)
|
||||
}
|
||||
self.onboardings.append(item)
|
||||
return self.onboardings
|
||||
|
||||
@handle_not_exist
|
||||
def get_onboarding_steps(self, onboarding_doc):
|
||||
steps = []
|
||||
for doc in self.onboarding_doc.get_steps():
|
||||
for doc in onboarding_doc.get_steps():
|
||||
step = doc.as_dict().copy()
|
||||
step.label = _(doc.title)
|
||||
if step.action == "Create Entry":
|
||||
|
|
@ -367,7 +380,7 @@ def get_desktop_page(page):
|
|||
'charts': wspace.charts,
|
||||
'shortcuts': wspace.shortcuts,
|
||||
'cards': wspace.cards,
|
||||
'onboarding': wspace.onboarding,
|
||||
'onboardings': wspace.onboardings,
|
||||
'allow_customization': not wspace.doc.disable_user_customization
|
||||
}
|
||||
except DoesNotExistError:
|
||||
|
|
|
|||
|
|
@ -2,10 +2,26 @@
|
|||
# Copyright (c) 2020, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe import _
|
||||
import json
|
||||
from frappe.model.document import Document
|
||||
|
||||
class OnboardingStep(Document):
|
||||
def before_export(self, doc):
|
||||
doc.is_complete = 0
|
||||
doc.is_skipped = 0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_onboarding_steps(ob_steps):
|
||||
steps = []
|
||||
for s in json.loads(ob_steps):
|
||||
doc = frappe.get_doc('Onboarding Step', s.get('step'))
|
||||
step = doc.as_dict().copy()
|
||||
step.label = _(doc.title)
|
||||
if step.action == "Create Entry":
|
||||
step.is_submittable = frappe.db.get_value("DocType", step.reference_document, 'is_submittable', cache=True)
|
||||
steps.append(step)
|
||||
|
||||
return steps
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ def execute():
|
|||
|
||||
def create_content(doc):
|
||||
content = []
|
||||
if doc.onboarding:
|
||||
content.append({"type":"onboarding","data":{"onboarding_name":doc.onboarding,"col":12,"pt":0,"pr":0,"pb":0,"pl":0}})
|
||||
if doc.charts:
|
||||
invalid_links = []
|
||||
for c in doc.charts:
|
||||
|
|
@ -55,7 +57,6 @@ def update_wspace(doc, seq, content):
|
|||
doc.title = doc.extends
|
||||
doc.extends = ''
|
||||
doc.category = ''
|
||||
doc.restrict_to_domain = ''
|
||||
doc.onboarding = ''
|
||||
doc.extends_another_page = 0
|
||||
doc.is_default = 0
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Card from "./card";
|
|||
import Chart from "./chart";
|
||||
import Shortcut from "./shortcut";
|
||||
import Spacer from "./spacer";
|
||||
import Onboarding from "./onboarding";
|
||||
|
||||
// import tunes
|
||||
import SpacingTune from "./spacing_tune";
|
||||
|
|
@ -18,6 +19,7 @@ frappe.wspace_block.blocks = {
|
|||
chart: Chart,
|
||||
shortcut: Shortcut,
|
||||
spacer: Spacer,
|
||||
onboarding: Onboarding,
|
||||
};
|
||||
|
||||
frappe.wspace_block.tunes = {
|
||||
|
|
|
|||
128
frappe/public/js/frappe/views/workspace/blocks/onboarding.js
Normal file
128
frappe/public/js/frappe/views/workspace/blocks/onboarding.js
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import get_dialog_constructor from "../../../widgets/widget_dialog.js";
|
||||
import Block from "./block.js";
|
||||
export default class Onboarding extends Block {
|
||||
static get toolbox() {
|
||||
return {
|
||||
title: 'Onboarding',
|
||||
icon: '<svg width="24" height="24" fill="none"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10zM12 11.09v5.455" stroke="#1F272E" fill="none"/><path d="M12.41 7.455a.41.41 0 11-.82 0 .41.41 0 01.82 0z" stroke="#1F272E"/></svg>'
|
||||
};
|
||||
}
|
||||
|
||||
static get isReadOnlySupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
constructor({ data, api, config, readOnly, block }) {
|
||||
super({ data, api, config, readOnly, block });
|
||||
this.col = this.data.col ? this.data.col : "12";
|
||||
this.pt = this.data.pt ? this.data.pt : "0";
|
||||
this.pr = this.data.pr ? this.data.pr : "0";
|
||||
this.pb = this.data.pb ? this.data.pb : "0";
|
||||
this.pl = this.data.pl ? this.data.pl : "0";
|
||||
this.allow_customization = !this.readOnly;
|
||||
this.options = {
|
||||
allow_sorting: this.allow_customization,
|
||||
allow_create: this.allow_customization,
|
||||
allow_delete: this.allow_customization,
|
||||
allow_hiding: false,
|
||||
allow_edit: true
|
||||
};
|
||||
}
|
||||
|
||||
new(block, widget_type = block) {
|
||||
const dialog_class = get_dialog_constructor(widget_type);
|
||||
let block_name = block+'_name';
|
||||
this.dialog = new dialog_class({
|
||||
label: this.label,
|
||||
type: widget_type,
|
||||
primary_action: (widget) => {
|
||||
widget.in_customize_mode = 1;
|
||||
this.block_widget = frappe.widget.make_widget({
|
||||
...widget,
|
||||
widget_type: widget_type,
|
||||
container: this.wrapper,
|
||||
options: {
|
||||
...this.options,
|
||||
on_delete: () => this.api.blocks.delete(),
|
||||
on_edit: () => this.on_edit(this.block_widget)
|
||||
},
|
||||
new: true
|
||||
});
|
||||
this.block_widget.customize(this.options);
|
||||
this.wrapper.setAttribute(block_name, this.block_widget.label || this.block_widget.onboarding_name);
|
||||
this.new_block_widget = this.block_widget.get_config();
|
||||
this.add_tune_button();
|
||||
},
|
||||
});
|
||||
|
||||
if (!this.readOnly && this.data && !this.data[block_name]) {
|
||||
this.dialog.make();
|
||||
}
|
||||
}
|
||||
|
||||
make(block, block_name) {
|
||||
let block_data = this.config.page_data['onboardings'].items.find(obj => {
|
||||
return obj.label == block_name;
|
||||
});
|
||||
if (!block_data) return false;
|
||||
this.wrapper.innerHTML = '';
|
||||
block_data.in_customize_mode = !this.readOnly;
|
||||
this.block_widget = frappe.widget.make_widget({
|
||||
container: this.wrapper,
|
||||
widget_type: 'onboarding',
|
||||
in_customize_mode: block_data.in_customize_mode,
|
||||
options: {
|
||||
...this.options,
|
||||
on_delete: () => this.api.blocks.delete(),
|
||||
on_edit: () => this.on_edit(this.block_widget)
|
||||
},
|
||||
label: block_data.label,
|
||||
title: block_data.title || __("Let's Get Started"),
|
||||
subtitle: block_data.subtitle,
|
||||
steps: block_data.items,
|
||||
success: block_data.success,
|
||||
docs_url: block_data.docs_url,
|
||||
user_can_dismiss: block_data.user_can_dismiss,
|
||||
});
|
||||
this.wrapper.setAttribute(block+'_name', block_name);
|
||||
if (!this.readOnly) {
|
||||
this.block_widget.customize(this.options);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
render() {
|
||||
this.wrapper = document.createElement('div');
|
||||
this.new('onboarding');
|
||||
|
||||
if (this.data && this.data.onboarding_name) {
|
||||
let has_data = this.make('onboarding', this.data.onboarding_name);
|
||||
if (!has_data) return;
|
||||
}
|
||||
|
||||
if (!this.readOnly) {
|
||||
this.add_tune_button();
|
||||
}
|
||||
return this.wrapper;
|
||||
}
|
||||
|
||||
validate(savedData) {
|
||||
if (!savedData.onboarding_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
save(blockContent) {
|
||||
return {
|
||||
onboarding_name: blockContent.getAttribute('onboarding_name'),
|
||||
col: this.get_col(),
|
||||
pt: this.get_padding("t"),
|
||||
pr: this.get_padding("r"),
|
||||
pb: this.get_padding("b"),
|
||||
pl: this.get_padding("l"),
|
||||
new: this.new_block_widget
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -61,6 +61,12 @@ frappe.views.Workspace = class Workspace {
|
|||
page_data: this.page_data || []
|
||||
}
|
||||
},
|
||||
onboarding: {
|
||||
class: frappe.wspace_block.blocks['onboarding'],
|
||||
config: {
|
||||
page_data: this.page_data || []
|
||||
}
|
||||
},
|
||||
spacer: frappe.wspace_block.blocks['spacer'],
|
||||
spacingTune: frappe.wspace_block.tunes['spacing_tune'],
|
||||
};
|
||||
|
|
@ -323,6 +329,7 @@ frappe.views.Workspace = class Workspace {
|
|||
this.editor.configuration.tools.chart.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.shortcut.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.card.config.page_data = this.page_data;
|
||||
this.editor.configuration.tools.onboarding.config.page_data = this.page_data;
|
||||
this.editor.render({ blocks: this.content || [] });
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export default class Widget {
|
|||
}
|
||||
|
||||
set_title(max_chars) {
|
||||
let base = this.label || this.name;
|
||||
let base = this.title || this.label || this.name;
|
||||
let title = max_chars ? frappe.ellipsis(base, max_chars) : base;
|
||||
|
||||
if (this.icon) {
|
||||
|
|
@ -136,7 +136,7 @@ export default class Widget {
|
|||
} else {
|
||||
this.title_field[0].innerHTML = title;
|
||||
if (max_chars) {
|
||||
this.title_field[0].setAttribute('title', this.label);
|
||||
this.title_field[0].setAttribute('title', this.title || this.label);
|
||||
}
|
||||
}
|
||||
this.subtitle && this.subtitle_field.html(this.subtitle);
|
||||
|
|
@ -169,7 +169,7 @@ export default class Widget {
|
|||
primary_action: (data) => {
|
||||
Object.assign(this, data);
|
||||
data.name = this.name;
|
||||
|
||||
this.new = true;
|
||||
this.refresh();
|
||||
this.options.on_edit && this.options.on_edit(data);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,23 @@ import Widget from "./base_widget.js";
|
|||
frappe.provide("frappe.utils");
|
||||
|
||||
export default class OnboardingWidget extends Widget {
|
||||
|
||||
async refresh() {
|
||||
this.new && await this.get_onboarding_data();
|
||||
this.set_title();
|
||||
this.set_actions();
|
||||
this.set_body();
|
||||
this.setup_events();
|
||||
}
|
||||
|
||||
get_config() {
|
||||
return {
|
||||
label: this.onboarding_name
|
||||
};
|
||||
}
|
||||
|
||||
make_body() {
|
||||
this.body.empty();
|
||||
this.steps_wrapper = $(`<div class="onboarding-steps-wrapper"></div>`).appendTo(this.body);
|
||||
this.step_preview = $(`<div class="onboarding-step-preview">
|
||||
<div class="onboarding-step-body"></div>
|
||||
|
|
@ -477,11 +493,13 @@ export default class OnboardingWidget extends Widget {
|
|||
}
|
||||
|
||||
is_dismissed() {
|
||||
if (this.in_customize_mode) return false;
|
||||
|
||||
let dismissed = JSON.parse(
|
||||
localStorage.getItem("dismissed-onboarding") || "{}"
|
||||
);
|
||||
if (Object.keys(dismissed).includes(this.label)) {
|
||||
let last_hidden = new Date(dismissed[this.label]);
|
||||
if (Object.keys(dismissed).includes(this.title)) {
|
||||
let last_hidden = new Date(dismissed[this.title]);
|
||||
let today = new Date();
|
||||
let diff = frappe.datetime.get_hour_diff(today, last_hidden);
|
||||
return diff < 24;
|
||||
|
|
@ -490,6 +508,8 @@ export default class OnboardingWidget extends Widget {
|
|||
}
|
||||
|
||||
set_actions() {
|
||||
if (this.in_customize_mode) return;
|
||||
|
||||
this.action_area.empty();
|
||||
const dismiss = $(
|
||||
`<div class="small" style="cursor:pointer;">${__('Dismiss', null, 'Stop showing the onboarding widget.')}</div>`
|
||||
|
|
@ -498,7 +518,7 @@ export default class OnboardingWidget extends Widget {
|
|||
let dismissed = JSON.parse(
|
||||
localStorage.getItem("dismissed-onboarding") || "{}"
|
||||
);
|
||||
dismissed[this.label] = frappe.datetime.now_datetime();
|
||||
dismissed[this.title] = frappe.datetime.now_datetime();
|
||||
|
||||
localStorage.setItem(
|
||||
"dismissed-onboarding",
|
||||
|
|
@ -508,4 +528,27 @@ export default class OnboardingWidget extends Widget {
|
|||
});
|
||||
dismiss.appendTo(this.action_area);
|
||||
}
|
||||
|
||||
get_onboarding_data() {
|
||||
return frappe.model
|
||||
.with_doc("Module Onboarding", this.onboarding_name)
|
||||
.then(onboarding_doc => {
|
||||
if (onboarding_doc) {
|
||||
this.onboarding_doc = onboarding_doc;
|
||||
this.label = onboarding_doc.label;
|
||||
this.title = onboarding_doc.title || __("Let's Get Started");
|
||||
this.subtitle = onboarding_doc.subtitle;
|
||||
this.success = onboarding_doc.success;
|
||||
this.docs_url = onboarding_doc.docs_url;
|
||||
this.user_can_dismiss = onboarding_doc.user_can_dismiss;
|
||||
const method =
|
||||
"frappe.desk.doctype.onboarding_step.onboarding_step.get_onboarding_steps";
|
||||
return frappe
|
||||
.xcall(method, { ob_steps: onboarding_doc.steps })
|
||||
.then(steps => {
|
||||
this.steps = steps;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,6 +124,24 @@ class ChartDialog extends WidgetDialog {
|
|||
}
|
||||
}
|
||||
|
||||
class OnboardingDialog extends WidgetDialog {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
get_fields() {
|
||||
return [
|
||||
{
|
||||
fieldtype: "Link",
|
||||
fieldname: "onboarding_name",
|
||||
label: "Onboarding Name",
|
||||
options: "Module Onboarding",
|
||||
reqd: 1,
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class CardDialog extends WidgetDialog {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
|
@ -531,6 +549,7 @@ export default function get_dialog_constructor(type) {
|
|||
shortcut: ShortcutDialog,
|
||||
number_card: NumberCardDialog,
|
||||
links: CardDialog,
|
||||
onboarding: OnboardingDialog
|
||||
};
|
||||
|
||||
return widget_map[type] || WidgetDialog;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue