2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 11:29:03 +00:00

feat: add editor mode and apply changes shortcuts

- fix save shortcuts (in CommonForm too)
- update shortcuts helper list
- add documentation link
This commit is contained in:
18alantom 2023-03-09 21:46:31 +05:30
parent bac00b5a36
commit 0614f99ea1
5 changed files with 101 additions and 26 deletions

View File

@ -152,9 +152,30 @@ export default defineComponent({
}, },
], ],
}, },
{
label: t`Template Builder`,
description: t`Applicable when Template Builder is open`,
collapsed: false,
shortcuts: [
{
shortcut: [this.ctrl, 'Enter'],
description: t`Apply and view changes made to the print template`,
},
{
shortcut: [this.ctrl, 'E'],
description: t`Toggle Edit Mode`,
},
],
},
]; ];
}, },
computed: { computed: {
ctrl() {
if (this.isMac) {
return 'control';
}
return 'Ctrl';
},
pmod() { pmod() {
if (this.isMac) { if (this.isMac) {
return '⌘'; return '⌘';

View File

@ -181,7 +181,9 @@ export default defineComponent({
}, },
deactivated(): void { deactivated(): void {
docsPathRef.value = ''; docsPathRef.value = '';
focusedDocsRef.add(this.docOrNull); if (this.docOrNull) {
focusedDocsRef.delete(this.doc);
}
}, },
computed: { computed: {
hasDoc(): boolean { hasDoc(): boolean {

View File

@ -139,11 +139,13 @@
<!-- Template Editor --> <!-- Template Editor -->
<div class="min-h-0"> <div class="min-h-0">
<TemplateEditor <TemplateEditor
v-if="typeof doc.template === 'string' && hints"
ref="templateEditor"
class="overflow-auto custom-scroll h-full" class="overflow-auto custom-scroll h-full"
v-if="typeof doc.template === 'string'" :initial-value="doc.template"
v-model.lazy="doc.template"
:disabled="!doc.isCustom" :disabled="!doc.isCustom"
:hints="hints ?? undefined" :hints="hints"
@blur="(value: string) => setTemplate(value)"
/> />
</div> </div>
@ -184,6 +186,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { EditorView } from 'codemirror';
import { Doc } from 'fyo/model/doc'; import { Doc } from 'fyo/model/doc';
import { PrintTemplate } from 'models/baseModels/PrintTemplate'; import { PrintTemplate } from 'models/baseModels/PrintTemplate';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
@ -194,12 +197,13 @@ import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import HorizontalResizer from 'src/components/HorizontalResizer.vue'; import HorizontalResizer from 'src/components/HorizontalResizer.vue';
import PageHeader from 'src/components/PageHeader.vue'; import PageHeader from 'src/components/PageHeader.vue';
import { handleErrorWithDialog } from 'src/errorHandling'; import { handleErrorWithDialog } from 'src/errorHandling';
import { docsPathMap } from 'src/utils/misc';
import { import {
baseTemplate, baseTemplate,
getPrintTemplatePropHints, getPrintTemplatePropHints,
getPrintTemplatePropValues, getPrintTemplatePropValues,
} from 'src/utils/printTemplates'; } from 'src/utils/printTemplates';
import { showSidebar } from 'src/utils/refs'; import { docsPathRef, focusedDocsRef, showSidebar } from 'src/utils/refs';
import { PrintValues } from 'src/utils/types'; import { PrintValues } from 'src/utils/types';
import { import {
getActionsForDoc, getActionsForDoc,
@ -207,6 +211,7 @@ import {
openSettings, openSettings,
showToast, showToast,
} from 'src/utils/ui'; } from 'src/utils/ui';
import { Shortcuts } from 'src/utils/vueUtils';
import { getMapFromList } from 'utils/index'; import { getMapFromList } from 'utils/index';
import { computed, defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import PrintContainer from './PrintContainer.vue'; import PrintContainer from './PrintContainer.vue';
@ -225,6 +230,7 @@ export default defineComponent({
FormControl, FormControl,
TemplateBuilderHint, TemplateBuilderHint,
}, },
inject: { shortcutManager: { from: 'shortcuts' } },
provide() { provide() {
return { doc: computed(() => this.doc) }; return { doc: computed(() => this.doc) };
}, },
@ -234,10 +240,8 @@ export default defineComponent({
editMode: false, editMode: false,
showHints: false, showHints: false,
wasSidebarShown: true, wasSidebarShown: true,
showEditor: false,
hints: undefined, hints: undefined,
values: null, values: null,
helpersCollapsed: true,
displayDoc: null, displayDoc: null,
scale: 0.65, scale: 0.65,
panelWidth: 22 /** rem */ * 16 /** px */, panelWidth: 22 /** rem */ * 16 /** px */,
@ -248,7 +252,6 @@ export default defineComponent({
hints?: Record<string, unknown>; hints?: Record<string, unknown>;
values: null | PrintValues; values: null | PrintValues;
doc: PrintTemplate | null; doc: PrintTemplate | null;
showEditor: boolean;
displayDoc: PrintTemplate | null; displayDoc: PrintTemplate | null;
scale: number; scale: number;
panelWidth: number; panelWidth: number;
@ -256,6 +259,7 @@ export default defineComponent({
}, },
async mounted() { async mounted() {
await this.setDoc(); await this.setDoc();
focusedDocsRef.add(this.doc);
if (this.doc?.template == null) { if (this.doc?.template == null) {
await this.doc?.set('template', baseTemplate); await this.doc?.set('template', baseTemplate);
@ -270,7 +274,39 @@ export default defineComponent({
this.wasSidebarShown = showSidebar.value; this.wasSidebarShown = showSidebar.value;
}, },
activated(): void {
docsPathRef.value = docsPathMap.PrintTemplate ?? '';
this.shortcuts.ctrl.set(['Enter'], this.setTemplate);
this.shortcuts.ctrl.set(['KeyE'], this.toggleEditMode);
},
deactivated(): void {
docsPathRef.value = '';
if (this.doc instanceof Doc) {
focusedDocsRef.delete(this.doc);
}
this.shortcuts.ctrl.delete(['Enter']);
this.shortcuts.ctrl.delete(['KeyE']);
},
methods: { methods: {
getTemplateEditorState() {
const fallback = this.doc?.template ?? '';
// @ts-ignore
const { view } = this.$refs.templateEditor ?? {};
if (!(view instanceof EditorView)) {
return fallback;
}
return view.state.doc.toString();
},
async setTemplate(value?: string) {
if (!this.doc?.isCustom) {
return;
}
value ??= this.getTemplateEditorState();
await this.doc?.set('template', value);
},
setScale({ target }: Event) { setScale({ target }: Event) {
if (!(target instanceof HTMLInputElement)) { if (!(target instanceof HTMLInputElement)) {
return; return;
@ -279,7 +315,16 @@ export default defineComponent({
const scale = Number(target.value); const scale = Number(target.value);
this.scale = Math.max(Math.min(scale, 10), 0.15); this.scale = Math.max(Math.min(scale, 10), 0.15);
}, },
toggleEditMode() { async toggleEditMode() {
if (!this.doc?.isCustom) {
return;
}
let message = this.t`Please set a Display Doc`;
if (!this.displayDoc) {
return showToast({ type: 'warning', message, duration: 1000 });
}
this.editMode = !this.editMode; this.editMode = !this.editMode;
if (this.editMode) { if (this.editMode) {
@ -288,7 +333,7 @@ export default defineComponent({
showSidebar.value = this.wasSidebarShown; showSidebar.value = this.wasSidebarShown;
} }
let message = this.t`Edit Mode Enabled`; message = this.t`Edit Mode Enabled`;
if (!this.editMode) { if (!this.editMode) {
message = this.t`Edit Mode Disabled`; message = this.t`Edit Mode Disabled`;
} }
@ -376,6 +421,16 @@ export default defineComponent({
}, },
}, },
computed: { computed: {
shortcuts(): Shortcuts {
// @ts-ignore
const shortcutManager = this.shortcutManager;
if (shortcutManager instanceof Shortcuts) {
return shortcutManager;
}
// no-op (hopefully)
throw Error('Shortcuts Not Found');
},
maxWidth() { maxWidth() {
return window.innerWidth - 12 * 16 - 100; return window.innerWidth - 12 * 16 - 100;
}, },

View File

@ -18,16 +18,14 @@ import { defineComponent, markRaw } from 'vue';
export default defineComponent({ export default defineComponent({
data() { data() {
return { state: null, view: null, compartments: {}, changed: false } as { return { state: null, view: null, compartments: {} } as {
state: EditorState | null; state: EditorState | null;
view: EditorView | null; view: EditorView | null;
compartments: Record<string, Compartment>; compartments: Record<string, Compartment>;
changed: boolean;
}; };
}, },
props: { props: {
modelModifiers: { type: Object, default: () => ({}) }, initialValue: { type: String, required: true },
modelValue: { type: String, required: true },
disabled: { type: Boolean, default: false }, disabled: { type: Boolean, default: false },
hints: { type: Object }, hints: { type: Object },
}, },
@ -36,11 +34,16 @@ export default defineComponent({
this.setDisabled(value); this.setDisabled(value);
}, },
}, },
emits: ['update:modelValue'], emits: ['input', 'blur'],
mounted() { mounted() {
if (!this.view) { if (!this.view) {
this.init(); this.init();
} }
if (this.fyo.store.isDevelopment) {
// @ts-ignore
window.te = this;
}
}, },
methods: { methods: {
init() { init() {
@ -61,7 +64,7 @@ export default defineComponent({
const completions = getCompletionsFromHints(this.hints ?? {}); const completions = getCompletionsFromHints(this.hints ?? {});
const view = new EditorView({ const view = new EditorView({
doc: this.modelValue, doc: this.initialValue,
extensions: [ extensions: [
EditorView.updateListener.of(this.updateListener), EditorView.updateListener.of(this.updateListener),
readOnly.of(EditorState.readOnly.of(this.disabled)), readOnly.of(EditorState.readOnly.of(this.disabled)),
@ -80,19 +83,12 @@ export default defineComponent({
}, },
updateListener(update: ViewUpdate) { updateListener(update: ViewUpdate) {
if (update.docChanged) { if (update.docChanged) {
this.changed = true; this.$emit('input', this.view?.state.doc.toString() ?? '');
} }
if (this.modelModifiers.lazy && !update.focusChanged) { if (update.focusChanged && !this.view?.hasFocus) {
return; this.$emit('blur', this.view?.state.doc.toString() ?? '');
} }
if (!this.changed) {
return;
}
this.$emit('update:modelValue', this.view?.state.doc.toString() ?? '');
this.changed = false;
}, },
setDisabled(value: boolean) { setDisabled(value: boolean) {
const { readOnly, editable } = this.compartments; const { readOnly, editable } = this.compartments;

View File

@ -115,6 +115,7 @@ export const docsPathMap: Record<string, string | undefined> = {
[ModelNameEnum.Party]: 'entries/party', [ModelNameEnum.Party]: 'entries/party',
[ModelNameEnum.Item]: 'entries/items', [ModelNameEnum.Item]: 'entries/items',
[ModelNameEnum.Tax]: 'entries/taxes', [ModelNameEnum.Tax]: 'entries/taxes',
[ModelNameEnum.PrintTemplate]: 'miscellaneous/print-templates',
// Miscellaneous // Miscellaneous
Search: 'miscellaneous/search', Search: 'miscellaneous/search',