Files
xo-stack/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue
T

252 lines
7.8 KiB
Vue

<template>
<div class="input-wrapper">
<h2>Input</h2>
<div class="form-group-row" :class="{ 'extended': state.selectedFromFunction?.extendedInput }">
<div class="form-group select-group">
<label for="functionSelect">Select Function</label>
<select v-model="state.selectedFromFunction">
<option v-for="fromFunction of props.fromFunctions" :key="fromFunction.label" :value="fromFunction">{{ fromFunction.label }}</option>
</select>
</div>
<div class="form-group input-group" :class="{ 'extended': state.selectedFromFunction?.extendedInput }">
<template v-for="(_input, i) of state.selectedFromFunction.inputs" :key="i">
<label for="field">{{ state.selectedFromFunction.inputs[i] }}</label>
<div>
<textarea
v-if="state.selectedFromFunction?.extendedInput"
v-model="state.selectedFromFunction.models[i]"
class="extended-input"
@input="autoGrow($event.target)"
></textarea>
<input
v-else
type="text"
v-model="state.selectedFromFunction.models[i]"
/>
<div v-if="state.error" class="text-error">{{ state.error }}</div>
</div>
</template>
</div>
</div>
<div>
<button type="submit" class="gradient-button" @click="() => setPrimitive(() => state.selectedFromFunction.callback(state.selectedFromFunction.models))">Create Output</button>
</div>
</div>
<div class="section output-section">
<h2>Output</h2>
<template v-for="(toFunction, i) of toFunctions" :key="i">
<div>
<div class="output-box-container">
<LabelledSection :label="toFunction.label" :content="getPrimitive(() => toFunction.callback(state.primitive))" />
<CopyButton :content="getPrimitive(() => toFunction.callback(state.primitive))" />
</div>
</div>
</template>
<template v-if="state.methods.length && state.primitive">
<div class="form-group-row" :class="{ 'extended': state.selectedMethod?.extendedInput }">
<div class="form-group select-group">
<label for="functionSelect">Method</label>
<select v-model="state.selectedMethod">
<option v-for="method of props.methods" :key="method.label" :value="method">{{ method.label }}</option>
</select>
</div>
<div class="form-group input-group" :class="{ 'extended': state.selectedMethod?.extendedInput }">
<template v-for="(_input, i) of state.selectedMethod.inputs" :key="i">
<label for="field">{{ state.selectedMethod.inputs[i] }}</label>
<div>
<textarea
v-if="state.selectedMethod?.extendedInput"
v-model="state.selectedMethod.models[i]"
class="extended-input"
@input="autoGrow($event.target)"
></textarea>
<input
v-else
type="text"
v-model="state.selectedMethod.models[i]"
/>
<div v-if="state.selectedMethod.error" class="text-error">{{ state.selectedMethod.error }}</div>
</div>
</template>
</div>
</div>
<div>
<button type="submit" class="gradient-button" @click="() => executeMethod(state.selectedMethod.callback(state.primitive, state.selectedMethod.models))">Execute Method</button>
</div>
<!-- Result -->
<div>
<div class="output-box-container">
<LabelledSection label="Result" :content="state.selectedMethod.result" />
<CopyButton :content="state.selectedMethod.result" />
</div>
</div>
</template>
</div>
</template>
<script setup generic="Primitive" lang="ts">
import { ref } from 'vue';
import type { Ref } from 'vue';
import LabelledSection from './LabelledSection.vue';
import CopyButton from './CopyButton.vue';
export interface FromFunction<Primitive> {
// The label of the function
label: string,
// Array of inputs that the function takes
inputs: Array<string>,
// Array of models that the input stores its value in
models: Array<any>,
// Function that takes the models and returns the primitive
callback: (value: Array<any>) => Primitive,
// Whether the input is extended onto a new line
extendedInput?: boolean
}
export interface ToFunction<Primitive> {
label: string;
callback: (primitive: Primitive) => string;
result?: string;
}
export interface Method<Primitive> {
// The label of the function
label: string;
// Array of inputs that the function takes
inputs: Array<string>,
// Array of models that the input stores its value in
models: Array<any>,
// Result of method execution,
result?: string;
// Error (if one occurred).
error?: string;
// Function that takes the models and returns the primitive
callback: (primitive: Primitive, value: Array<any>) => string;
// Whether the input is extended onto a new line
extendedInput?: boolean
}
export interface Props<Primitive> {
fromFunctions: Array<FromFunction<Primitive>>,
toFunctions: Array<ToFunction<Primitive>>,
methods?: Array<Method<Primitive>>,
}
const props = withDefaults(defineProps<Props<Primitive>>(), {
fromFunctions: () => [],
toFunctions: () => [],
methods: () => [],
});
const state: Ref<{
fromFunctions: Array<FromFunction<Primitive>>,
toFunctions: Array<ToFunction<Primitive>>,
methods: Array<Method<Primitive>>,
// Selected In Function
selectedFromFunction: FromFunction<Primitive>,
// Selected Method
selectedMethod?: Method<Primitive>,
// The Primitive.
primitive?: Primitive,
// Error to display if an invalid input was given.
error?: string;
}> = ref({
// From functions, to functions and methods.
fromFunctions: [ ...props.fromFunctions ],
toFunctions: [ ...props.toFunctions ],
methods: [ ...props.methods ],
// The selected function.
selectedFromFunction: props.fromFunctions[0],
// The selected method.
selectedMethod: props.methods[0] || undefined,
// The Primitive.
primitive: undefined,
})
async function setPrimitive(functionToExecute: () => Primitive | Promise<Primitive>): void {
try {
state.value.primitive = await functionToExecute();
state.value.error = undefined;
state.value.toFunctions.forEach((wrappedMethod, i) => {
state.value.toFunctions[i].result = wrappedMethod.callback(state.value.primitive)
});
state.value.methods.forEach((method, i) => {
state.value.methods[i].models.forEach((model, j) => {
state.value.methods[i].models[j] = '';
});
state.value.methods[i].result = '';
});
} catch(error: any) {
state.value.error = `${error}`;
}
}
function getPrimitive(functionToExecute: () => string): string {
try {
if(!state.value.primitive) {
return '';
}
return functionToExecute();
} catch(error: any) {
return error.message;
}
}
async function executeMethod(wrappedMethod: () => string | Promise<string>): string {
try {
if(!state.value.primitive) {
return '';
}
state.value.selectedMethod.result = await wrappedMethod;
state.value.selectedMethod.error = undefined;
} catch(error: any) {
state.value.selectedMethod.error = error.message;
return error.message;
}
};
// Select the first "from" function.
state.value.selectedFromFunction = state.value.fromFunctions[0];
const autoGrow = (element: HTMLTextAreaElement) => {
element.style.height = 'auto'
element.style.height = `${Math.min(element.scrollHeight, 300)}px`
}
</script>
<style>
.text-error {
color: #f46666;
overflow-wrap: break-word;
}
</style>