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

feat: Workflows (preview only) #503

Closed
wants to merge 6 commits into from
Closed
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
20 changes: 20 additions & 0 deletions src/ui/src/builder/BuilderApp.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<BuilderSidebar></BuilderSidebar>
</div>
<div
v-if="builderMode !== 'workflows'"
class="builderMain"
:class="{
buildMode: builderMode !== 'preview',
Expand Down Expand Up @@ -68,6 +69,19 @@
</div>
</div>
</div>
<div v-if="builderMode == 'workflows'">
<WorkflowsCanvas></WorkflowsCanvas>
<div
v-if="ssbm.isSelectionActive()"
:key="selectedId ?? 'noneSelected'"
class="settingsBar"
:class="{
collapsed: ssbm.isSettingsBarCollapsed(),
}"
>
<BuilderSettings></BuilderSettings>
</div>
</div>
</div>
<!-- INSTANCE TRACKERS -->

Expand Down Expand Up @@ -135,6 +149,7 @@ import BuilderInstanceTracker from "./BuilderInstanceTracker.vue";
import BuilderInsertionOverlay from "./BuilderInsertionOverlay.vue";
import BuilderInsertionLabel from "./BuilderInsertionLabel.vue";
import { isPlatformMac } from "../core/detectPlatform";
import WorkflowsCanvas from "./workflows/WorkflowsCanvas.vue";

const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
Expand Down Expand Up @@ -253,6 +268,11 @@ function handleRendererDrop(ev: DragEvent) {
function handleRendererClick(ev: PointerEvent): void {
if (builderMode.value === "preview") return;

const unselectableEl: HTMLElement = (ev.target as HTMLElement).closest(
"[data-writer-unselectable]",
);
if (unselectableEl) return;

const targetEl: HTMLElement = (ev.target as HTMLElement).closest(
"[data-writer-id]",
);
Expand Down
3 changes: 2 additions & 1 deletion src/ui/src/builder/BuilderFieldsText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</template>
<template v-else-if="templateField.control == FieldControl.Textarea">
<BuilderTemplateInput
multiline="true"
:multiline="true"
variant="text"
class="content"
:value="component.content[fieldKey]"
Expand Down Expand Up @@ -44,6 +44,7 @@ const props = defineProps<{
}>();
const { componentId, fieldKey } = toRefs(props);
const component = computed(() => wf.getComponentById(componentId.value));

const templateField = computed(() => {
const { type } = component.value;
const definition = wf.getComponentDefinition(type);
Expand Down
15 changes: 13 additions & 2 deletions src/ui/src/builder/BuilderSidebar.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
<template>
<div class="BuilderSidebar">
<BuilderSidebarToolbar class="toolbar"></BuilderSidebarToolbar>
<BuilderSidebarTree class="tree"></BuilderSidebarTree>
<template v-if="builderMode == 'workflows'">
<BuilderSidebarWorkflowsToolkit></BuilderSidebarWorkflowsToolkit>
</template>
<template v-else>
<BuilderSidebarToolbar class="toolbar"></BuilderSidebarToolbar>
<BuilderSidebarTree class="tree"></BuilderSidebarTree>
</template>
</div>
</template>

<script setup lang="ts">
import { computed, inject } from "vue";
import BuilderSidebarToolbar from "./BuilderSidebarToolbar.vue";
import BuilderSidebarWorkflowsToolkit from "./BuilderSidebarWorkflowsToolkit.vue";
import BuilderSidebarTree from "./BuilderSidebarTree.vue";
import injectionKeys from "../injectionKeys";

const ssbm = inject(injectionKeys.builderManager);
const builderMode = computed(() => ssbm.getMode());
</script>

<style scoped>
Expand Down
11 changes: 11 additions & 0 deletions src/ui/src/builder/BuilderSidebarTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
<i class="material-symbols-outlined"> add </i>
Add Page
</button>
<button @click="addWorkflow">
<i class="material-symbols-outlined"> add </i>
Add Workflow
</button>
</div>
</div>
</template>
Expand Down Expand Up @@ -180,6 +184,13 @@ async function addPage() {
await nextTick();
ssbm.setSelection(pageId);
}

async function addWorkflow() {
const pageId = createAndInsertComponent("workflow", "root");
wf.setActivePageId(pageId);
await nextTick();
ssbm.setSelection(pageId);
}
</script>

<style scoped>
Expand Down
191 changes: 191 additions & 0 deletions src/ui/src/builder/BuilderSidebarWorkflowsToolkit.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
<template>
<div class="BuilderSidebarWorkflowsToolkit">
<div class="sectionTitle">
<i class="material-symbols-outlined"> handyman </i>
<h3>Workflows Toolkit</h3>
</div>
<div class="categories">
<template
v-for="(categoryData, category) in categoriesData"
:key="category"
>
<div v-if="categoryData.isVisible !== false" class="category">
<div class="title">
<i class="material-symbols-outlined">{{
categoryData.icon ?? "question_mark"
}}</i>
<h4>{{ category }}</h4>

<div
class="drop-arrow"
@click="toggleCollapseCategory(category)"
>
<i class="material-symbols-outlined">
{{
categoryData.isCollapsed
? "expand_more"
: "expand_less"
}}
</i>
</div>
</div>

<div v-show="!categoryData.isCollapsed" class="components">
<div
v-for="(
definition, type
) in definitionsByDisplayCategory[category]"
:key="type"
class="component button"
:title="definition.description"
draggable="true"
:data-component-type="type"
@dragend="handleDragEnd($event)"
@dragstart="handleDragStart($event, type)"
>
{{ definition.name ?? type }}
<i
v-if="type.startsWith('custom_')"
class="material-symbols-outlined"
title="(Custom component template)"
>manga</i
>
</div>
</div>
</div>
</template>
</div>
</div>
</template>

<script setup lang="ts">
import { Ref, computed, inject, ref } from "vue";
import injectionKeys from "../injectionKeys";
import nodeTemplateDefs from "./workflows/nodeTemplateDefs";

const wf = inject(injectionKeys.core);

type CategoryData = {
isVisible?: boolean;
isCollapsed?: boolean;
icon?: string;
};

const categoriesData: Ref<Record<string, CategoryData>> = ref({
Content: {
icon: "toc",
isCollapsed: false,
},
});

function toggleCollapseCategory(categoryId: string) {
const categoryData = categoriesData.value[categoryId];
categoryData.isCollapsed = !categoryData.isCollapsed;
}

const definitionsByDisplayCategory = computed(() => {
const types = Object.keys(nodeTemplateDefs);
const result: Record<string, Record<string, any>> = {};

types.map((type) => {
const definition = nodeTemplateDefs[type];
const isMatch = Object.keys(categoriesData.value).includes(
definition.category,
);
let displayCategory: string;
if (!isMatch) {
displayCategory = "Other";
} else {
displayCategory = definition.category;
}
if (!result[displayCategory]) {
result[displayCategory] = {};
}
result[displayCategory][type] = definition;
});

return result;
});

const handleDragStart = (ev: DragEvent, type: string) => {
ev.dataTransfer.setData(
`application/json;writer=node`,
JSON.stringify({ type }),
);
};

const handleDragEnd = (ev: DragEvent) => {};
</script>

<style scoped>
@import "./sharedStyles.css";

.BuilderSidebarWorkflowsToolkit {
font-size: 0.7rem;
}

.BuilderSidebarWorkflowsToolkit > .sectionTitle {
padding: 16px;
position: sticky;
top: 0;
background: var(--builderBackgroundColor);
font-size: 0.875rem;
height: 40px;
}

h3 {
font-weight: 500;
font-size: 0.875rem;
}

.categories {
padding: 0 12px 12px 12px;
flex: 1 1 auto;
display: flex;
flex-direction: column;
}

.category .title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
gap: 8px;
}

.category .title h4 {
flex: 1 0 auto;
}

.category .components {
padding: 4px 0 4px 0;
}

.component {
padding: 8px;
border-radius: 4px;
cursor: grab;
display: flex;
align-items: center;
gap: 4px;
height: 33px;
}

.component:hover {
background: var(--builderSubtleHighlightColor);
}

.drop-arrow {
border-radius: 50%;
min-width: 24px;
min-height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}

.drop-arrow:hover {
background: var(--builderSubtleSeparatorColor);
}
</style>
9 changes: 8 additions & 1 deletion src/ui/src/builder/BuilderSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
logEntryCount
}}</span>
</div>
<div
:class="{ active: activeId == 'workflows' }"
@click="selectOption('workflows')"
>
<i class="icon material-symbols-outlined"> preview </i>
Workflows
</div>
<div
:class="{ active: activeId == 'preview' }"
@click="selectOption('preview')"
Expand All @@ -31,7 +38,7 @@ const ssbm = inject(injectionKeys.builderManager);

let selectedId: Ref<string> = ref(null);

const selectOption = (optionId: "ui" | "code" | "preview") => {
const selectOption = (optionId: "ui" | "code" | "workflows" | "preview") => {
selectedId.value = optionId;
ssbm.setMode(optionId);
if (ssbm.getMode() != "preview") return;
Expand Down
4 changes: 2 additions & 2 deletions src/ui/src/builder/builderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function generateBuilderManager() {
};

type State = {
mode: "ui" | "code" | "preview";
mode: "ui" | "code" | "workflows" | "preview";
selection: {
componentId: Component["id"];
instancePath: string;
Expand Down Expand Up @@ -100,7 +100,7 @@ export function generateBuilderManager() {
`.ComponentRenderer [data-writer-id="${componentId}"]`,
);
resolvedInstancePath =
componentFirstElement.dataset.writerInstancePath;
componentFirstElement?.dataset?.writerInstancePath;
}

state.value.selection = {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/src/builder/useComponentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) {
): Component["id"] {
const component = wf.getComponentById(componentId);
if (!component || component.type == "root") return null;
if (component.type == "page") return componentId;
if (component.type == "page" || component.type == "workflow") return componentId;
return getContainingPageId(component.parentId);
}

Expand Down
35 changes: 35 additions & 0 deletions src/ui/src/builder/workflows/WorkflowsArrow.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<g>
<!-- Arrow Line -->
<line
:x1="props.arrow.x1"
:y1="props.arrow.y1"
:x2="props.arrow.x2"
:y2="props.arrow.y2"
:stroke="props.arrow.color"
stroke-width="1"
marker-end="url(#arrowhead)"
/>
</g>
</template>

<script setup lang="ts">
import { computed } from "vue";

const props = defineProps<{
arrow: {
x1: number;
y1: number;
x2: number;
y2: number;
color: string;
};
}>();
</script>

<style scoped>
@import "../sharedStyles.css";

.WorkflowsArrow {
}
</style>
Loading