Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: handle template in BuilderFieldsKeyValue. WF-31 #484

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 32 additions & 61 deletions src/ui/src/builder/BuilderFieldsKeyValue.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,42 +75,52 @@

<script setup lang="ts">
import {
PropType,
Ref,
computed,
inject,
nextTick,
onMounted,
Ref,
ref,
toRefs,
inject,
computed,
watch,
} from "vue";
import { Component } from "../writerTypes";
import BuilderFieldsObject from "./BuilderFieldsObject.vue";
import { useComponentActions } from "./useComponentActions";
import injectionKeys from "../injectionKeys";
import { useEvaluator } from "../renderer/useEvaluator";
import type { InstancePath } from "../writerTypes";
import BuilderFieldsObject from "./BuilderFieldsObject.vue";
import BuilderTemplateInput from "./BuilderTemplateInput.vue";
import { useComponentActions } from "./useComponentActions";

const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
const { setContentValue } = useComponentActions(wf, ssbm);

const props = defineProps<{
componentId: Component["id"];
fieldKey: string;
}>();
const props = defineProps({
componentId: { type: String, required: true },
fieldKey: { type: String, required: true },
instancePath: { type: Array as PropType<InstancePath>, required: true },
});

const { componentId, fieldKey } = toRefs(props);
const component = computed(() => wf.getComponentById(componentId.value));

const rootEl: Ref<HTMLElement> = ref(null);
const assistedKeyEl: Ref<HTMLInputElement> = ref(null);
type Mode = "assisted" | "freehand";
const mode: Ref<Mode> = ref(null);
const assistedEntries: Ref<Record<string, string>> = ref({});
const assistedEntries: Ref<Record<string, string | number | null>> = ref({});
const formAdd: Ref<{ key: string; value: string }> = ref({
key: "",
value: "",
});

const { getEvaluatedFields } = useEvaluator(wf);

const evaluatedValue = computed<Record<string, string | number | null>>(
() => getEvaluatedFields(props.instancePath)[fieldKey.value].value,
);

const setMode = async (newMode: Mode) => {
if (mode.value == newMode) return;
mode.value = newMode;
Expand All @@ -123,33 +133,13 @@ const setMode = async (newMode: Mode) => {
};

const handleSwitchToAssisted = () => {
let currentValue = component.value.content[fieldKey.value];

if (!currentValue) return;

let parsedValue: any;

// Attempt to populate assisted from existing JSON data

try {
parsedValue = JSON.parse(currentValue);
if (typeof parsedValue != "object") {
throw "Invalid structure.";
}
assistedEntries.value = parsedValue;
} catch {
component.value.content[fieldKey.value] = JSON.stringify(
assistedEntries.value,
null,
2,
);
}
assistedEntries.value = evaluatedValue.value ?? {};
};

const addAssistedEntry = () => {
const { key, value } = formAdd.value;
if (key === "" || value === "") return;
assistedEntries.value[key] = value;
assistedEntries.value = { ...assistedEntries.value, [key]: value };
setContentValue(
component.value.id,
fieldKey.value,
Expand All @@ -160,7 +150,9 @@ const addAssistedEntry = () => {
};

const removeAssistedEntry = (key: string) => {
delete assistedEntries.value[key];
const assistedEntriesCopy = assistedEntries.value;
delete assistedEntriesCopy[key];
assistedEntries.value = assistedEntriesCopy;

if (Object.keys(assistedEntries.value).length == 0) {
setContentValue(component.value.id, fieldKey.value, undefined);
Expand All @@ -179,39 +171,18 @@ const removeAssistedEntry = (key: string) => {
*/
watch(
() => component.value?.content[fieldKey.value],
async (currentValue) => {
if (!component.value) return;
let parsedValue: any;

try {
parsedValue = JSON.parse(currentValue);
assistedEntries.value = parsedValue;
} catch {
// If parsing fails, preserve the previous assistedEntries value
}
async () => {
if (!component.value || !evaluatedValue.value) return;
assistedEntries.value = evaluatedValue.value;
},
);

onMounted(async () => {
const currentValue = component.value.content[fieldKey.value];
let parsedValue: any;

if (!currentValue) {
setMode("assisted");
return;
}

// Attempt assisted mode first, if the JSON can be parsed into an object

try {
parsedValue = JSON.parse(currentValue);
if (typeof parsedValue != "object") {
throw "Invalid structure.";
}
assistedEntries.value = parsedValue;
if (evaluatedValue.value) {
assistedEntries.value = evaluatedValue.value;
setMode("assisted");
} catch {
// Fall back to freehand if assisted mode isn't possible
} else {
setMode("freehand");
}
});
Expand Down
18 changes: 12 additions & 6 deletions src/ui/src/builder/BuilderSettingsProperties.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
class="content"
:field-key="fieldKey"
:component-id="selectedComponent.id"
:instance-path="selectedInstancePath"
></BuilderFieldsKeyValue>

<BuilderFieldsText
Expand Down Expand Up @@ -118,20 +119,25 @@

<script setup lang="ts">
import { computed, inject } from "vue";
import BuilderFieldsKeyValue from "./BuilderFieldsKeyValue.vue";
import { FieldType, FieldCategory } from "../writerTypes";
import injectionKeys from "../injectionKeys";
import { parseInstancePathString } from "../renderer/instancePath";
import { FieldCategory, FieldType, InstancePath } from "../writerTypes";
import BuilderFieldsAlign from "./BuilderFieldsAlign.vue";
import BuilderFieldsColor from "./BuilderFieldsColor.vue";
import BuilderFieldsKeyValue from "./BuilderFieldsKeyValue.vue";
import BuilderFieldsObject from "./BuilderFieldsObject.vue";
import BuilderFieldsPadding from "./BuilderFieldsPadding.vue";
import BuilderFieldsShadow from "./BuilderFieldsShadow.vue";
import BuilderFieldsText from "./BuilderFieldsText.vue";
import BuilderFieldsObject from "./BuilderFieldsObject.vue";
import BuilderFieldsWidth from "./BuilderFieldsWidth.vue";
import BuilderFieldsAlign from "./BuilderFieldsAlign.vue";
import BuilderFieldsPadding from "./BuilderFieldsPadding.vue";
import injectionKeys from "../injectionKeys";

const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);

const selectedInstancePath = computed<InstancePath>(() =>
parseInstancePathString(ssbm.getSelection()?.instancePath),
);

const selectedComponent = computed(() => {
return wf.getComponentById(ssbm.getSelectedId());
});
Expand Down
39 changes: 26 additions & 13 deletions src/ui/src/builder/BuilderTemplateInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
ref="input"
v-capture-tabs
class="templateInput"
:variant="props.vatiant"
:variant="props.variant"
:value="props.value"
autocorrect="off"
autocomplete="off"
Expand All @@ -60,23 +60,36 @@
</template>

<script setup lang="ts">
import { inject, ref, nextTick } from "vue";
import injectionKeys from "../injectionKeys";
import Fuse from "fuse.js";
import { PropType, inject, nextTick, ref } from "vue";
import injectionKeys from "../injectionKeys";

const emit = defineEmits(["input", "update:value"]);
const props = defineProps<{
inputId?: string;
value?: string;
multiline?: boolean;
variant?: "code" | "text";
type?: "state" | "template";
options?: Record<string, string>;
placeholder?: string;
}>();

const props = defineProps({
inputId: { type: String, required: false, default: undefined },
value: { type: String, required: false, default: undefined },
multiline: { type: Boolean, required: false },
variant: {
type: String as PropType<"code" | "text">,
required: false,
default: undefined,
},
type: {
type: String as PropType<"state" | "template">,
required: false,
default: undefined,
},
options: {
type: Object as PropType<Record<string, string>>,
required: false,
default: undefined,
},
placeholder: { type: String, required: false, default: undefined },
});

const ss = inject(injectionKeys.core);
const autocompleteOptions = ref<string[]>([]);
const autocompleteOptions = ref<{ text: string; type: string }[]>([]);
const input = ref<HTMLInputElement | null>(null);

defineExpose({
Expand Down
37 changes: 23 additions & 14 deletions src/ui/src/renderer/ComponentProxy.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
<script lang="ts">
import { Ref, computed, h, inject, provide, ref, watch } from "vue";
import {
PropType,
Ref,
VNode,
computed,
h,
inject,
provide,
ref,
watch,
} from "vue";
import { getTemplate } from "../core/templateMap";
import injectionKeys from "../injectionKeys";
import {
Component,
InstancePath,
InstancePathItem,
UserFunction,
} from "../writerTypes";
import ComponentProxy from "./ComponentProxy.vue";
import { useEvaluator } from "./useEvaluator";
import injectionKeys from "../injectionKeys";
import { VNode } from "vue";
import ChildlessPlaceholder from "./ChildlessPlaceholder.vue";
import ComponentProxy from "./ComponentProxy.vue";
import RenderError from "./RenderError.vue";
import { flattenInstancePath } from "./instancePath";
import { useEvaluator } from "./useEvaluator";

export default {
props: ["componentId", "instancePath", "instanceData"],
props: {
componentId: { type: String, required: true },
instancePath: { type: Array as PropType<InstancePath>, required: true },
instanceData: { validator: () => true, required: true },
},
setup(props) {
const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
const componentId: Component["id"] = props.componentId;
const componentId = props.componentId;
const component = computed(() => wf.getComponentById(componentId));
const template = getTemplate(component.value.type);
const instancePath: InstancePath = props.instancePath;
const instancePath = props.instancePath;
const instanceData = props.instanceData;
const { getEvaluatedFields, isComponentVisible } = useEvaluator(wf);
const evaluatedFields = getEvaluatedFields(instancePath);
Expand Down Expand Up @@ -141,19 +155,14 @@ export default {
];
};

const flattenInstancePath = (path: InstancePath) => {
return path
.map((ie) => `${ie.componentId}:${ie.instanceNumber}`)
.join(",");
};
const flattenedInstancePath = flattenInstancePath(instancePath);

provide(injectionKeys.evaluatedFields, evaluatedFields);
provide(injectionKeys.componentId, componentId);
provide(injectionKeys.isBeingEdited, isBeingEdited);
provide(injectionKeys.isDisabled, isDisabled);
provide(injectionKeys.instancePath, instancePath);
provide(injectionKeys.instanceData, instanceData);
provide(injectionKeys.instanceData, instanceData as any);
provide(injectionKeys.renderProxiedComponent, renderProxiedComponent);
provide(injectionKeys.getChildrenVNodes, getChildrenVNodes);
provide(injectionKeys.flattenedInstancePath, flattenedInstancePath);
Expand Down
13 changes: 13 additions & 0 deletions src/ui/src/renderer/instancePath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { InstancePath } from "../writerTypes";

export function flattenInstancePath(path: InstancePath) {
return path.map((ie) => `${ie.componentId}:${ie.instanceNumber}`).join(",");
}

export function parseInstancePathString(raw?: string): InstancePath {
if (!raw) return [];
return raw.split(",").map((record) => {
const [componentId, instanceNumber] = record.split(":");
return { componentId, instanceNumber: Number(instanceNumber) };
});
}
Loading