Skip to content

Commit

Permalink
Merge pull request #554 from writer/feat-workflows-foundations
Browse files Browse the repository at this point in the history
feat: Workflows foundations
  • Loading branch information
ramedina86 authored Sep 23, 2024
2 parents 4a3fa30 + b99c89e commit 0a9643d
Show file tree
Hide file tree
Showing 18 changed files with 1,048 additions and 283 deletions.
8 changes: 7 additions & 1 deletion src/ui/src/builder/BuilderSettingsHandlers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,13 @@ const recognisedEvents: ComputedRef<WriterComponentDefinition["events"]> =
});
const userFunctions = computed(() => wf.getUserFunctions());
const pageKeys = computed(() => wf.getPageKeys());
const pageKeys = computed(() => {
const pages = wf.getComponents("root");
const pageKeys = pages
.map((page) => page.content["key"])
.filter((pageKey) => Boolean(pageKey));
return pageKeys;
});
const isHandlerInvalid = (eventType: string) => {
const handlerFunctionName = component.value.handlers?.[eventType];
Expand Down
31 changes: 27 additions & 4 deletions src/ui/src/builder/BuilderSidebarTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@
></BuilderTreeBranch>
</div>
</div>
<div class="addPage">
<button @click="addPage">
<div class="add">
<button v-if="rootType == 'root'" @click="addPage">
<i class="material-symbols-outlined"> add </i>
Add Page
</button>
<button v-if="rootType == 'workflows_root'" @click="addWorkflow">
<i class="material-symbols-outlined"> add </i>
Add Workflow
</button>
</div>
</div>
</template>
Expand All @@ -73,8 +77,17 @@ const { createAndInsertComponent, goToComponentParentPage } =
const searchQuery: Ref<string> = ref(null);
const matchIndex: Ref<number> = ref(-1);
const rootType = computed(() => {
let targetType = "root";
if (ssbm.getMode() == "workflows") {
targetType = "workflows_root";
}
return targetType;
});
const rootComponents = computed(() => {
return wf.getComponents(null, { sortedByPosition: true });
return wf
.getComponents(null, { sortedByPosition: true })
.filter((c) => c.type == rootType.value);
});
function determineMatch(component: Component, query: string): boolean {
Expand Down Expand Up @@ -152,6 +165,16 @@ async function addPage() {
await nextTick();
ssbm.setSelection(pageId);
}
async function addWorkflow() {
const pageId = createAndInsertComponent(
"workflows_workflow",
"workflows_root",
);
wf.setActivePageId(pageId);
await nextTick();
ssbm.setSelection(pageId);
}
</script>

<style scoped>
Expand All @@ -172,7 +195,7 @@ async function addPage() {
padding: 0 12px 12px 12px;
}
.addPage {
.add {
margin-top: auto;
padding: 0 16px 16px 16px;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ui/src/builder/builderManager.ts
Original file line number Diff line number Diff line change
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
5 changes: 3 additions & 2 deletions src/ui/src/builder/useComponentActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function useComponentActions(wf: Core, ssbm: BuilderManager) {
parentId,
content: initContent,
handlers: {},
position: position ?? getNextInsertionPosition(parentId, type)
position: position ?? getNextInsertionPosition(parentId, type),
};

return component;
Expand Down Expand Up @@ -806,7 +806,8 @@ 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 == "workflows_workflow")
return componentId;
return getContainingPageId(component.parentId);
}

Expand Down
233 changes: 27 additions & 206 deletions src/ui/src/components/core/root/CoreRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<template v-for="(vnode, index) in getChildrenVNodes()" :key="index">
<component
:is="vnode"
v-if="vnode.key === `${activePageId}:0`"
v-if="vnode.key === `${displayedPageId}:0`"
></component>
</template>
</div>
Expand All @@ -12,7 +12,6 @@
<script lang="ts">
import { FieldType } from "@/writerTypes";
import * as sharedStyleFields from "@/renderer/sharedStyleFields";
import { nextTick } from "vue";
import { useEvaluator } from "@/renderer/useEvaluator";
const ssHashChangeStub = `
Expand Down Expand Up @@ -67,17 +66,28 @@ export default {
};
</script>
<script setup lang="ts">
import { computed, inject, ref, Ref, watch, onBeforeMount } from "vue";
import {
computed,
inject,
ref,
Ref,
watch,
nextTick,
onBeforeMount,
} from "vue";
import injectionKeys from "@/injectionKeys";
import { changePageInHash, getParsedHash } from "@/core/navigation";
const importedModulesSpecifiers: Record<string, string> = {};
const wf = inject(injectionKeys.core);
const ssbm = inject(injectionKeys.builderManager);
const getChildrenVNodes = inject(injectionKeys.getChildrenVNodes);
const rootEl: Ref<HTMLElement> = ref(null);
const { isComponentVisible } = useEvaluator(wf);
const getFirstPageId = () => {
const displayedPageId = computed(() => {
const activePageId = wf.getActivePageId();
if (activePageId && wf.isChildOf("root", activePageId)) return activePageId;
const pageComponents = wf.getComponents("root", {
includeBMC: true,
includeCMC: true,
Expand All @@ -87,93 +97,8 @@ const getFirstPageId = () => {
const visiblePages = pageComponents.filter((c) => isComponentVisible(c.id));
if (visiblePages.length == 0) return null;
return visiblePages[0].id;
};
const hashRegex = /^((?<pageKey>[^/]*))?(\/(?<routeVars>.*))?$/;
const routeVarRegex = /^(?<key>[^=]+)=(?<value>.*)$/;
const activePageId = computed(() => wf.getActivePageId() ?? getFirstPageId());
watch(activePageId, (newPageId) => {
const page = wf.getComponentById(newPageId);
const pageKey = page.content?.["key"];
if (ssbm && ssbm.getSelectedId() !== newPageId) {
ssbm.setSelection(null);
}
nextTick().then(() => {
window.scrollTo(0, 0);
const rendererEl = document.querySelector(".ComponentRenderer");
rendererEl.parentElement.scrollTo(0, 0);
});
changePageInHash(pageKey);
});
type ParsedHash = {
pageKey?: string;
routeVars: Record<string, string>;
};
function getParsedHash(): ParsedHash {
const docHash = document.location.hash.substring(1);
const hashMatchGroups = docHash.match(hashRegex)?.groups;
let pageKey: string;
let routeVars: Record<string, string> = {};
if (!hashMatchGroups) return { pageKey, routeVars };
pageKey = hashMatchGroups?.pageKey
? decodeURIComponent(hashMatchGroups.pageKey)
: undefined;
const routeVarsSegments = hashMatchGroups.routeVars?.split("&") ?? [];
routeVarsSegments.forEach((routeVarSegment) => {
const matchGroups = routeVarSegment.match(routeVarRegex)?.groups;
if (!matchGroups) return;
const { key, value } = matchGroups;
const decodedKey = decodeURIComponent(key);
const decodedValue = decodeURIComponent(value);
routeVars[decodedKey] = decodedValue;
});
return { pageKey, routeVars };
}
function setHash(parsedHash: ParsedHash) {
const { pageKey, routeVars } = parsedHash;
let hash = "";
if (pageKey) {
hash += `${encodeURIComponent(pageKey)}`;
}
if (Object.keys(routeVars).length > 0) {
hash += "/";
hash += Object.entries(routeVars)
.map(([key, value]) => {
// Vars set to null are excluded from the hash
if (value === null) return null;
return `${encodeURIComponent(key)}=${encodeURIComponent(
value,
)}`;
})
.filter((segment) => segment)
.join("&");
}
document.location.hash = hash;
}
function changePageInHash(targetPageKey: string) {
const parsedHash = getParsedHash();
parsedHash.pageKey = targetPageKey;
setHash(parsedHash);
}
function changeRouteVarsInHash(targetRouteVars: Record<string, string>) {
const parsedHash = getParsedHash();
const routeVars = parsedHash?.routeVars ?? {};
parsedHash.routeVars = { ...routeVars, ...targetRouteVars };
setHash(parsedHash);
}
function handleHashChange() {
const parsedHash = getParsedHash();
const event = new CustomEvent("wf-hashchange", {
Expand All @@ -186,126 +111,22 @@ function handleHashChange() {
wf.setActivePageFromKey(parsedHash.pageKey);
}
async function importStylesheet(stylesheetKey: string, path: string) {
const existingEl = document.querySelector(
`[data-writer-stylesheet-key="${stylesheetKey}"]`,
);
existingEl?.remove();
const el = document.createElement("link");
el.dataset.writerStylesheetKey = stylesheetKey;
el.setAttribute("href", path);
el.setAttribute("rel", "stylesheet");
document.head.appendChild(el);
}
async function importScript(scriptKey: string, path: string) {
const existingEl = document.querySelector(
`[data-writer-script-key="${scriptKey}"]`,
);
existingEl?.remove();
const el = document.createElement("script");
el.dataset.writerScriptKey = scriptKey;
el.src = path;
el.setAttribute("rel", "modulepreload");
document.head.appendChild(el);
}
async function importModule(moduleKey: string, specifier: string) {
importedModulesSpecifiers[moduleKey] = specifier;
await import(/* @vite-ignore */ specifier);
}
async function handleFunctionCall(
moduleKey: string,
functionName: string,
args: any[],
) {
const specifier = importedModulesSpecifiers[moduleKey];
const m = await import(/* @vite-ignore */ specifier);
if (!m) {
// eslint-disable-next-line no-console
console.warn(
`The module with key "${moduleKey}" cannot be found. Please check that it has been imported.`,
);
return;
watch(displayedPageId, (newPageId) => {
const page = wf.getComponentById(newPageId);
const pageKey = page.content?.["key"];
if (ssbm && ssbm.getSelectedId() !== newPageId) {
ssbm.setSelection(null);
}
m[functionName](...args);
}
type FileDownloadMailItemPayload = {
data: string;
fileName: string;
};
function addMailSubscriptions() {
wf.addMailSubscription(
"fileDownload",
(mailItem: FileDownloadMailItemPayload) => {
const el = document.createElement("a");
el.href = mailItem.data;
el.download = mailItem.fileName;
el.click();
},
);
wf.addMailSubscription("openUrl", (url: string) => {
const el = document.createElement("a");
el.href = url;
el.target = "_blank";
el.rel = "noopener noreferrer";
el.click();
});
wf.addMailSubscription("pageChange", (pageKey: string) => {
changePageInHash(pageKey);
nextTick().then(() => {
window.scrollTo(0, 0);
document
.querySelector(".ComponentRenderer")
?.parentElement?.scrollTo(0, 0);
});
wf.addMailSubscription(
"routeVarsChange",
(routeVars: Record<string, string>) => {
changeRouteVarsInHash(routeVars);
},
);
wf.addMailSubscription(
"importStylesheet",
({ stylesheetKey, path }: { stylesheetKey: string; path: string }) => {
importStylesheet(stylesheetKey, path);
},
);
wf.addMailSubscription(
"importScript",
({ scriptKey, path }: { scriptKey: string; path: string }) => {
importScript(scriptKey, path);
},
);
wf.addMailSubscription(
"importModule",
({
moduleKey,
specifier,
}: {
moduleKey: string;
specifier: string;
}) => {
importModule(moduleKey, specifier);
},
);
wf.addMailSubscription(
"functionCall",
({
moduleKey,
functionName,
args,
}: {
moduleKey: string;
functionName: string;
args: any[];
}) => {
handleFunctionCall(moduleKey, functionName, args);
},
);
}
changePageInHash(pageKey);
});
onBeforeMount(() => {
addMailSubscriptions();
window.addEventListener("hashchange", () => {
handleHashChange();
});
Expand Down
Loading

0 comments on commit 0a9643d

Please sign in to comment.