fix: single store state for all undo/redo related changes

This commit is contained in:
Shariq Ansari 2023-02-21 21:44:54 +05:30
parent 99a645bffd
commit 0866a7003e
10 changed files with 57 additions and 97 deletions

View file

@ -85,7 +85,7 @@ function delete_column(with_children) {
// remove column
columns.splice(index, 1);
store.selected_field = null;
store.form.selected_field = null;
}
function move_columns_to_section() {
@ -101,7 +101,7 @@ function move_columns_to_section() {
store.selected(column.df.name) ? 'selected' : ''
]"
:title="column.df.fieldname"
@click.stop="store.selected_field = column.df"
@click.stop="store.form.selected_field = column.df"
@mouseover.stop="hovered = true"
@mouseout.stop="hovered = false"
>

View file

@ -19,7 +19,7 @@ function remove_field() {
}
let index = props.column.fields.indexOf(props.field);
props.column.fields.splice(index, 1);
store.selected_field = null;
store.form.selected_field = null;
}
function move_fields_to_column() {
@ -51,7 +51,7 @@ function duplicate_field() {
// push duplicate_field after props.field in the same column
let index = props.column.fields.indexOf(props.field);
props.column.fields.splice(index + 1, 0, duplicate_field);
store.selected_field = duplicate_field.df;
store.form.selected_field = duplicate_field.df;
}
</script>
@ -63,7 +63,7 @@ function duplicate_field() {
store.selected(field.df.name) ? 'selected' : ''
]"
:title="field.df.fieldname"
@click.stop="store.selected_field = field.df"
@click.stop="store.form.selected_field = field.df"
@mouseover.stop="hovered = true"
@mouseout.stop="hovered = false"
>

View file

@ -14,18 +14,18 @@ let docfield_df = computed(() => {
if (in_list(frappe.model.layout_fields, df.fieldtype) || df.hidden) {
return false;
}
if (df.depends_on && !evaluate_depends_on_value(df.depends_on, store.selected_field)) {
if (df.depends_on && !evaluate_depends_on_value(df.depends_on, store.form.selected_field)) {
return false;
}
if (
in_list(["fetch_from", "fetch_if_empty"], df.fieldname) &&
in_list(frappe.model.no_value_type, store.selected_field.fieldtype)
in_list(frappe.model.no_value_type, store.form.selected_field.fieldtype)
) {
return false;
}
if (df.fieldname === "reqd" && store.selected_field.fieldtype === "Check") {
if (df.fieldname === "reqd" && store.form.selected_field.fieldtype === "Check") {
return false;
}
@ -34,11 +34,11 @@ let docfield_df = computed(() => {
df.options = "";
args.value = {};
if (in_list(["Table", "Link"], store.selected_field.fieldtype)) {
if (in_list(["Table", "Link"], store.form.selected_field.fieldtype)) {
df.fieldtype = "Link";
df.options = "DocType";
if (store.selected_field.fieldtype === "Table") {
if (store.form.selected_field.fieldtype === "Table") {
args.value.is_table_field = 1;
}
}
@ -63,14 +63,14 @@ let docfield_df = computed(() => {
<template>
<SearchBox v-model="search_text" />
<div class="control-data">
<div v-if="store.selected_field">
<div v-if="store.form.selected_field">
<div class="field" v-for="(df, i) in docfield_df" :key="i">
<component
:is="df.fieldtype.replace(' ', '') + 'Control'"
:args="args"
:df="df"
:value="store.selected_field[df.fieldname]"
v-model="store.selected_field[df.fieldname]"
:value="store.form.selected_field[df.fieldname]"
v-model="store.form.selected_field[df.fieldname]"
:data-fieldname="df.fieldname"
:data-fieldtype="df.fieldtype"
/>

View file

@ -8,11 +8,11 @@ import { onClickOutside, useMagicKeys, whenever } from "@vueuse/core";
let store = useStore();
let should_render = computed(() => {
return Object.keys(store.layout).length !== 0;
return Object.keys(store.form.layout).length !== 0;
});
let container = ref(null);
onClickOutside(container, () => store.selected_field = null);
onClickOutside(container, () => store.form.selected_field = null);
// cmd/ctrl + s to save the form
const { meta_s, ctrl_s } = useMagicKeys();
@ -53,7 +53,7 @@ function setup_change_doctype_dialog() {
}
watch(
() => store.layout,
() => store.form.layout,
() => (store.dirty = true),
{ deep: true }
);
@ -69,7 +69,7 @@ onMounted(() => {
v-if="should_render"
ref="container"
class="form-builder-container"
@click="store.selected_field = null"
@click="store.form.selected_field = null"
>
<div class="form-controls" @click.stop>
<div class="form-sidebar">

View file

@ -67,21 +67,21 @@ function delete_section(with_children) {
// remove section
sections.splice(index, 1);
store.selected_field = null;
store.form.selected_field = null;
}
function select_section() {
if (props.section.df.collapsible) {
collapsed.value = !collapsed.value;
}
store.selected_field = props.section.df;
store.form.selected_field = props.section.df;
}
function move_sections_to_tab() {
let new_tab = move_children_to_parent(props, "tab", "section", store.layout);
let new_tab = move_children_to_parent(props, "tab", "section", store.form.layout);
// activate tab
store.active_tab = new_tab;
store.form.active_tab = new_tab;
}
</script>

View file

@ -34,7 +34,7 @@ function resize(e) {
}
watch(
() => store.selected_field,
() => store.form.selected_field,
value => {
active_tab.value = value ? tab_titles[1] : tab_titles[0];
},

View file

@ -9,12 +9,12 @@ import { ref, computed, nextTick } from "vue";
let store = useStore();
let dragged = ref(false);
let has_tabs = computed(() => store.layout.tabs.length > 1);
store.active_tab = store.layout.tabs[0].df.name;
let has_tabs = computed(() => store.form.layout.tabs.length > 1);
store.form.active_tab = store.form.layout.tabs[0].df.name;
function activate_tab(tab) {
store.active_tab = tab.df.name;
store.selected_field = tab.df;
store.form.active_tab = tab.df.name;
store.form.selected_field = tab.df;
// scroll to active tab
nextTick(() => {
@ -28,24 +28,24 @@ function activate_tab(tab) {
function drag_over(tab) {
!dragged.value &&
setTimeout(() => {
store.active_tab = tab.df.name;
store.form.active_tab = tab.df.name;
}, 500);
}
function add_new_tab() {
let tab = {
df: store.get_df("Tab Break", "", "Tab " + (store.layout.tabs.length + 1)),
df: store.get_df("Tab Break", "", "Tab " + (store.form.layout.tabs.length + 1)),
sections: [section_boilerplate()],
};
store.layout.tabs.push(tab);
store.form.layout.tabs.push(tab);
activate_tab(tab);
}
function add_new_section() {
let section = section_boilerplate();
store.current_tab.sections.push(section);
store.selected_field = section.df;
store.form.selected_field = section.df;
}
function is_current_tab_empty() {
@ -76,7 +76,7 @@ function remove_tab() {
}
function delete_tab(with_children) {
let tabs = store.layout.tabs;
let tabs = store.form.layout.tabs;
let index = tabs.indexOf(store.current_tab);
if (!with_children) {
@ -102,17 +102,17 @@ function delete_tab(with_children) {
// activate previous tab
let prev_tab_index = index == 0 ? 0 : index - 1;
store.active_tab = tabs[prev_tab_index].df.name;
store.selected_field = null;
store.form.active_tab = tabs[prev_tab_index].df.name;
store.form.selected_field = null;
}
</script>
<template>
<div class="tab-header" v-if="!(store.layout.tabs.length == 1 && store.read_only)">
<div class="tab-header" v-if="!(store.form.layout.tabs.length == 1 && store.read_only)">
<draggable
v-show="has_tabs"
class="tabs"
v-model="store.layout.tabs"
v-model="store.form.layout.tabs"
group="tabs"
filter="[data-has-std-field='true']"
:prevent-on-filter="false"
@ -123,7 +123,7 @@ function delete_tab(with_children) {
>
<template #item="{ element }">
<div
:class="['tab', store.active_tab == element.df.name ? 'active' : '']"
:class="['tab', store.form.active_tab == element.df.name ? 'active' : '']"
:title="element.df.fieldname"
:data-is-custom="element.df.is_custom_field"
:data-has-std-field="store.has_standard_field(element)"
@ -166,9 +166,9 @@ function delete_tab(with_children) {
<div class="tab-contents">
<div
class="tab-content"
v-for="(tab, i) in store.layout.tabs"
v-for="(tab, i) in store.form.layout.tabs"
:key="i"
:class="[store.active_tab == tab.df.name ? 'active' : '']"
:class="[store.form.active_tab == tab.df.name ? 'active' : '']"
>
<draggable
class="tab-content-container"

View file

@ -24,7 +24,7 @@ let doctype_df = computed(() => {
doctypes.value = store
.get_updated_fields()
.filter(df => df.fieldtype == "Link")
.filter(df => df.options && df.fieldname != store.selected_field.fieldname)
.filter(df => df.options && df.fieldname != store.form.selected_field.fieldname)
.sort((a, b) => a.options.localeCompare(b.options))
.map(df => ({
label: `${df.options} (${df.fieldname})`,

View file

@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { create_layout, scrub_field_names, get_field_by_name } from "./utils";
import { computed, nextTick, ref, watch } from "vue";
import { create_layout, scrub_field_names } from "./utils";
import { computed, nextTick, ref } from "vue";
import { useDebouncedRefHistory, onKeyDown } from "@vueuse/core";
export const useStore = defineStore("form-builder-store", () => {
@ -8,15 +8,17 @@ export const useStore = defineStore("form-builder-store", () => {
let doc = ref(null);
let docfields = ref([]);
let custom_docfields = ref([]);
let layout = ref({});
let active_tab = ref("");
let selected_field = ref(null);
let form = ref({
layout: {},
active_tab: null,
selected_field: null,
});
let dirty = ref(false);
let read_only = ref(false);
let is_customize_form = ref(false);
let preview = ref(false);
let drag = ref(false);
let get_animation = ref("cubic-bezier(0.34, 1.56, 0.64, 1)");
let get_animation = "cubic-bezier(0.34, 1.56, 0.64, 1)";
let ref_history = ref(null);
// Getters
@ -25,12 +27,12 @@ export const useStore = defineStore("form-builder-store", () => {
});
let current_tab = computed(() => {
return layout.value.tabs.find((tab) => tab.df.name == active_tab.value);
return form.value.layout.tabs.find((tab) => tab.df.name == form.value.active_tab);
});
// Actions
function selected(name) {
return selected_field.value?.name == name;
return form.value.selected_field?.name == name;
}
function get_df(fieldtype, fieldname = "", label = "") {
@ -87,9 +89,9 @@ export const useStore = defineStore("form-builder-store", () => {
}
}
layout.value = get_layout();
active_tab.value = layout.value.tabs[0].df.name;
selected_field.value = null;
form.value.layout = get_layout();
form.value.active_tab = form.value.layout.tabs[0].df.name;
form.value.selected_field = null;
nextTick(() => {
dirty.value = false;
@ -101,8 +103,6 @@ export const useStore = defineStore("form-builder-store", () => {
setup_undo_redo();
}
let data = ref({ active_tab, layout, selected_field });
let undo_redo_keyboard_event = onKeyDown(true, (e) => {
if (e.ctrlKey || e.metaKey) {
if (e.key === "z" && !e.shiftKey && ref_history.value.canUndo) {
@ -114,30 +114,9 @@ export const useStore = defineStore("form-builder-store", () => {
});
function setup_undo_redo() {
data.value = {
active_tab: active_tab,
layout: layout,
selected_field: selected_field,
};
ref_history.value = useDebouncedRefHistory(data, { deep: true, debounce: 100 });
ref_history.value = useDebouncedRefHistory(form, { deep: true, debounce: 100 });
undo_redo_keyboard_event;
watch(data, (d) => {
layout.value = d.layout;
active_tab.value = d.active_tab;
selected_field.value = d.selected_field;
if (d.selected_field?.name) {
let field = get_field_by_name(
layout.value.tabs,
"sections",
d.selected_field?.name
);
selected_field.value = field ? field.df : null;
}
});
}
function reset_changes() {
@ -240,7 +219,7 @@ export const useStore = defineStore("form-builder-store", () => {
let fields = [];
let idx = 0;
let layout_fields = JSON.parse(JSON.stringify(layout.value.tabs));
let layout_fields = JSON.parse(JSON.stringify(form.value.layout.tabs));
layout_fields.forEach((tab, i) => {
if (
@ -309,20 +288,18 @@ export const useStore = defineStore("form-builder-store", () => {
return {
doctype,
doc,
layout,
active_tab,
selected_field,
form,
dirty,
read_only,
is_customize_form,
preview,
drag,
get_animation,
selected,
get_docfields,
current_tab,
selected,
get_df,
has_standard_field,
current_tab,
fetch,
reset_changes,
validate_fields,

View file

@ -349,20 +349,3 @@ export function confirm_dialog(
d.show();
d.set_message(message);
}
export function get_field_by_name(parent, child, name) {
let field = null;
parent.every((f) => {
if (f.df.name == name) {
field = f;
return false;
}
if (child) {
let new_child = child == "sections" ? "columns" : child == "columns" ? "fields" : "";
field = get_field_by_name(f[child], new_child, name);
if (field) return false;
}
return true;
});
return field;
}