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:
parent
bac00b5a36
commit
0614f99ea1
@ -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 '⌘';
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user