fix: Added Onboarding Block

This commit is contained in:
shariquerik 2021-07-20 16:33:13 +05:30
parent 4a05697cfe
commit 8739b5e3ad
9 changed files with 254 additions and 25 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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 = {

View 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
};
}
}

View file

@ -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 {

View file

@ -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);
},

View file

@ -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;
});
}
});
}
}

View file

@ -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;