first version

This commit is contained in:
2026-02-10 14:42:19 +03:00
parent e1b3497cd5
commit 631126a85c
10 changed files with 390 additions and 49 deletions

View File

@@ -1,19 +1,83 @@
import { ref } from 'vue';
import { type Ref, ref, shallowRef } from 'vue';
const API_BASE_URL = ''; // Backend API URL
export interface RuntimeExecutor<TIn, TOut> {
execute(this: void, method: string): Promise<TOut | undefined>;
running: Ref<boolean>;
inputs: Ref<TIn>;
outputs: Ref<TOut | undefined>;
error: Ref<string | undefined>;
duration: Ref<number>;
}
export const runtime = {
createExecutor(className: string) {
const inputs = ref();
const outputs = ref();
return {
execute(method: string): Promise<unknown> {
console.log('executing...', className, method);
createExecutor<TIn, TOut>(
className: string,
initInputs: TIn,
silentErrors = true,
): RuntimeExecutor<TIn, TOut> {
const inputs = ref<TIn>(initInputs) as Ref<TIn>;
const outputs = ref<TOut>();
const duration = shallowRef(0);
const running = shallowRef(false);
const error = shallowRef<string>();
return new Promise((r) => {
setTimeout(() => r(null), 2500);
const execute = async (method: string): Promise<TOut | undefined> => {
console.trace('executing...', className, method);
const startTime = performance.now();
try {
error.value = undefined;
running.value = true;
duration.value = 0;
const response = await fetch(`${API_BASE_URL}/api/runtime/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
className,
methodName: method,
inputs: inputs.value,
}),
});
},
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
outputs.value = data.outputs || {};
return outputs.value;
} catch (err) {
console.error('Runtime execution error:', err);
error.value = err instanceof Error ? err.message : 'An error occurred';
if (!silentErrors) {
throw err;
}
} finally {
running.value = false;
const endTime = performance.now();
duration.value = Math.round((endTime - startTime) * 10) / 10;
}
return undefined;
};
return {
execute,
running,
duration,
inputs,
outputs,
error,
};
},
};

View File

@@ -0,0 +1,96 @@
<template>
<q-card class="s8n-calculator q-pa-md">
<q-card-section>
<div class="text-h6">Simple Calculator</div>
</q-card-section>
<q-card-section class="q-gutter-md">
<q-select
outlined
v-model="inputs.operator"
label="Operator"
:options="[
{ label: 'Add (+)', value: 'add' },
{ label: 'Subtract (-)', value: 'subtract' },
{ label: 'Multiply (×)', value: 'multiply' },
{ label: 'Divide (÷)', value: 'divide' },
]"
emit-value
map-options
/>
<q-input
v-for="(a, aidx) in inputs.args"
:key="aidx"
outlined
v-model.number="inputs.args[aidx]"
:label="`${aidx === 0 ? 'Base' : aidx === 1 ? 'Second' : aidx === 2 ? 'Third' : `${aidx + 1}th`} Number`"
type="number"
>
<template v-slot:after>
<q-btn
v-if="aidx > 1"
@click="inputs.args.splice(aidx, 1)"
class="col-auto"
icon="mdi-delete"
color="negative"
outline
dense
></q-btn>
</template>
</q-input>
<q-btn icon="mdi-plus" @click="inputs.args[inputs.args.length] = 0"></q-btn>
<div class="result-section q-mt-md">
<q-banner v-if="error" class="bg-negative text-white" rounded>
{{ error }}
</q-banner>
<div v-else-if="outputs !== undefined" class="text-h5 text-primary">
Result: {{ outputs.result }} ({{ duration }}ms)
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn
color="primary"
@click="execute('Calc')"
:loading="running"
size="lg"
icon="calculate"
label="Calculate"
/>
</q-card-actions>
</q-card>
</template>
<script setup lang="ts">
import { runtime } from '../components/s8n-runtime';
const { execute, running, error, duration, outputs, inputs } = runtime.createExecutor<
{
operator: string;
args: number[];
},
{
result?: number;
}
>('S8n.Components.Basics.Calculator', {
operator: 'add',
args: [0, 0],
});
</script>
<style lang="scss" scoped>
.s8n-calculator {
max-width: 400px;
width: 100%;
.result-section {
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -1,43 +1,9 @@
<template>
<q-page class="row items-center justify-evenly">
<example-component
title="Example component"
active
:todos="todos"
:meta="meta"
></example-component>
<component-calculator />
</q-page>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { Todo, Meta } from 'components/models';
import ExampleComponent from 'components/ExampleComponent.vue';
const todos = ref<Todo[]>([
{
id: 1,
content: 'ct1',
},
{
id: 2,
content: 'ct2',
},
{
id: 3,
content: 'ct3',
},
{
id: 4,
content: 'ct4',
},
{
id: 5,
content: 'ct5',
},
]);
const meta = ref<Meta>({
totalCount: 1200,
});
import ComponentCalculator from 'components/ComponentCalculator.vue';
</script>