├── apps ├── .env.default ├── types │ ├── README.md │ ├── eslint.config.mjs │ ├── lint-staged.config.mjs │ ├── src │ │ ├── node-types.ts │ │ ├── selection.ts │ │ ├── node-data.ts │ │ └── node-validation-schema.ts │ ├── tsconfig.json │ └── package.json ├── frontend │ ├── src │ │ ├── app │ │ │ ├── utils │ │ │ │ ├── noop.ts │ │ │ │ ├── consts.ts │ │ │ │ ├── typescript.ts │ │ │ │ ├── copy.ts │ │ │ │ ├── validation │ │ │ │ │ ├── get-is-valid-json.ts │ │ │ │ │ ├── get-is-valid-layout-directions.ts │ │ │ │ │ ├── get-is-valid-from-properties.ts │ │ │ │ │ ├── get-nodes-definitions-by-type.ts │ │ │ │ │ ├── get-node-definition.ts │ │ │ │ │ ├── flat-errors.ts │ │ │ │ │ ├── get-node-errors.mock.ts │ │ │ │ │ ├── get-node-errors.spec.ts │ │ │ │ │ └── get-node-errors.ts │ │ │ │ ├── browser.ts │ │ │ │ ├── url.ts │ │ │ │ ├── update-keys.ts │ │ │ │ ├── position-utils.ts │ │ │ │ ├── get-node-add-change.ts │ │ │ │ ├── ensure-bounds.ts │ │ │ │ └── show-snackbar.tsx │ │ │ ├── features │ │ │ │ ├── palette │ │ │ │ │ ├── palette-container.module.css │ │ │ │ │ ├── components │ │ │ │ │ │ ├── footer │ │ │ │ │ │ │ ├── palette-footer.module.css │ │ │ │ │ │ │ └── palette-footer.tsx │ │ │ │ │ │ ├── header │ │ │ │ │ │ │ ├── palette-header.module.css │ │ │ │ │ │ │ └── palette-header.tsx │ │ │ │ │ │ ├── dragged-item │ │ │ │ │ │ │ ├── dragged-item.module.css │ │ │ │ │ │ │ └── dragged-item.tsx │ │ │ │ │ │ └── items │ │ │ │ │ │ │ ├── palette-items.module.css │ │ │ │ │ │ │ └── palette-items.tsx │ │ │ │ │ ├── variables.css │ │ │ │ │ ├── palette-container-lazy.tsx │ │ │ │ │ └── hooks │ │ │ │ │ │ └── use-palette-drag-and-drop.tsx │ │ │ │ ├── json-form │ │ │ │ │ ├── types │ │ │ │ │ │ ├── utils.ts │ │ │ │ │ │ ├── label.ts │ │ │ │ │ │ ├── rules.ts │ │ │ │ │ │ ├── default-properties.ts │ │ │ │ │ │ ├── layouts.ts │ │ │ │ │ │ └── uischema.ts │ │ │ │ │ ├── controls │ │ │ │ │ │ ├── ai-tools-control │ │ │ │ │ │ │ ├── ai-tools-control.module.css │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ ├── add-ai-tool-footer │ │ │ │ │ │ │ │ ├── add-ai-tool-footer.module.css │ │ │ │ │ │ │ │ └── add-ai-tool-footer.tsx │ │ │ │ │ │ │ │ └── add-ai-tool-form-content │ │ │ │ │ │ │ │ └── add-ai-tool-form-content.module.css │ │ │ │ │ │ ├── dynamic-conditions-control │ │ │ │ │ │ │ ├── dynamic-conditions-form │ │ │ │ │ │ │ │ └── conditions-form.module.css │ │ │ │ │ │ │ ├── dynamic-condition-modal-footer │ │ │ │ │ │ │ │ └── condition-modal-footer.tsx │ │ │ │ │ │ │ ├── dynamic-conditions-control.module.css │ │ │ │ │ │ │ └── dynamic-conditions-form-field │ │ │ │ │ │ │ │ └── conditions-form-field.module.css │ │ │ │ │ │ ├── label-control │ │ │ │ │ │ │ └── label-control.tsx │ │ │ │ │ │ ├── switch-control │ │ │ │ │ │ │ └── switch-control.tsx │ │ │ │ │ │ ├── date-picker-control │ │ │ │ │ │ │ └── date-picker-control.tsx │ │ │ │ │ │ ├── control-wrapper.tsx │ │ │ │ │ │ ├── text-area-control │ │ │ │ │ │ │ └── text-area-control.tsx │ │ │ │ │ │ └── select-control │ │ │ │ │ │ │ └── select-control.tsx │ │ │ │ │ ├── layouts │ │ │ │ │ │ ├── vertical-layout │ │ │ │ │ │ │ ├── vertical-layout.module.css │ │ │ │ │ │ │ └── vertical-layout.tsx │ │ │ │ │ │ ├── accordion-layout │ │ │ │ │ │ │ ├── accordion-layout.module.css │ │ │ │ │ │ │ └── accordion-layout.tsx │ │ │ │ │ │ ├── group-layout │ │ │ │ │ │ │ ├── group-layout.module.css │ │ │ │ │ │ │ └── group-layout.tsx │ │ │ │ │ │ ├── horizontal-layout │ │ │ │ │ │ │ ├── horizontal-layout.module.css │ │ │ │ │ │ │ ├── use-has-child-error.ts │ │ │ │ │ │ │ └── horizontal-layout.tsx │ │ │ │ │ │ ├── render-elements.tsx │ │ │ │ │ │ └── layout-wrapper.tsx │ │ │ │ │ ├── json-form.module.css │ │ │ │ │ ├── components │ │ │ │ │ │ └── indicator-dot │ │ │ │ │ │ │ ├── indicator-dot.tsx │ │ │ │ │ │ │ └── indicator-dot.module.css │ │ │ │ │ └── utils │ │ │ │ │ │ ├── unknown-renderer.tsx │ │ │ │ │ │ ├── get-scope.ts │ │ │ │ │ │ ├── rendering.ts │ │ │ │ │ │ └── get-scope.spec.ts │ │ │ │ ├── plugins-core │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── utils │ │ │ │ │ │ └── missing-plugin.stub.ts │ │ │ │ │ ├── functions.ts │ │ │ │ │ ├── adapters │ │ │ │ │ │ ├── utils │ │ │ │ │ │ │ └── sort-by-priority.ts │ │ │ │ │ │ └── adapter-i18n.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── optional-hooks.tsx │ │ │ │ │ │ ├── optional-app-bar-toolbar.tsx │ │ │ │ │ │ ├── optional-footer-content.tsx │ │ │ │ │ │ ├── optional-app-bar-controls.tsx │ │ │ │ │ │ └── optional-edge-properties.tsx │ │ │ │ │ ├── i18n.ts │ │ │ │ │ ├── components.ts │ │ │ │ │ └── README.md │ │ │ │ ├── diagram │ │ │ │ │ ├── nodes │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── placeholder-button │ │ │ │ │ │ │ │ ├── placeholder-button.module.css │ │ │ │ │ │ │ │ └── placeholder-button.tsx │ │ │ │ │ │ │ ├── node-section │ │ │ │ │ │ │ │ ├── node-section.tsx │ │ │ │ │ │ │ │ └── node-section.module.css │ │ │ │ │ │ │ └── connectable-item │ │ │ │ │ │ │ │ ├── connectable-item.module.css │ │ │ │ │ │ │ │ └── connectable-item.tsx │ │ │ │ │ │ ├── decision-node-template │ │ │ │ │ │ │ ├── decision-node-template.module.css │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ └── branches-container.module.css │ │ │ │ │ │ ├── ai-agent-node-template │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ ├── setting-info │ │ │ │ │ │ │ │ │ ├── setting-info.module.css │ │ │ │ │ │ │ │ │ └── setting-info.tsx │ │ │ │ │ │ │ │ ├── icon-placeholder │ │ │ │ │ │ │ │ │ ├── icon-placeholder.module.css │ │ │ │ │ │ │ │ │ └── icon-placeholder.tsx │ │ │ │ │ │ │ │ ├── node-info-wrapper │ │ │ │ │ │ │ │ │ ├── node-wrapper-info.module.css │ │ │ │ │ │ │ │ │ └── node-wrapper-info.tsx │ │ │ │ │ │ │ │ └── tool-info │ │ │ │ │ │ │ │ │ ├── tool-info.module.css │ │ │ │ │ │ │ │ │ └── tool-info.tsx │ │ │ │ │ │ │ └── ai-agent-node-template.module.css │ │ │ │ │ │ └── workflow-node-template │ │ │ │ │ │ │ └── workflow-node-template.module.css │ │ │ │ │ ├── edges │ │ │ │ │ │ ├── edge.consts.ts │ │ │ │ │ │ ├── get-edge-z-index.ts │ │ │ │ │ │ └── label-edge │ │ │ │ │ │ │ ├── variables.css │ │ │ │ │ │ │ ├── label-edge.module.css │ │ │ │ │ │ │ └── use-label-edge-hover.ts │ │ │ │ │ ├── diagram.module.css │ │ │ │ │ ├── handles │ │ │ │ │ │ ├── is-inner-handle.ts │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ ├── get-handle-id.ts │ │ │ │ │ │ └── get-handle-position.ts │ │ │ │ │ ├── diagram.const.ts │ │ │ │ │ ├── diagram-wrapper.tsx │ │ │ │ │ ├── get-node-types-object.ts │ │ │ │ │ ├── listeners │ │ │ │ │ │ ├── node-drag-start-listeners.ts │ │ │ │ │ │ └── node-changed-listeners.ts │ │ │ │ │ └── selectors.ts │ │ │ │ ├── properties-bar │ │ │ │ │ ├── components │ │ │ │ │ │ ├── edge-properties │ │ │ │ │ │ │ └── edge-properties.module.css │ │ │ │ │ │ ├── properties-bar │ │ │ │ │ │ │ ├── properties-bar.module.css │ │ │ │ │ │ │ ├── render-component.tsx │ │ │ │ │ │ │ └── properties-bar.types.ts │ │ │ │ │ │ └── header │ │ │ │ │ │ │ ├── properties-bar-header.module.css │ │ │ │ │ │ │ └── properties-bar-header.tsx │ │ │ │ │ ├── properties-bar-container-lazy.tsx │ │ │ │ │ └── properties-bar-container.tsx │ │ │ │ ├── integration │ │ │ │ │ ├── consts.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── app-loader │ │ │ │ │ │ │ └── app-loader-container.tsx │ │ │ │ │ │ ├── import-export │ │ │ │ │ │ │ ├── export-modal │ │ │ │ │ │ │ │ └── open-export-modal.tsx │ │ │ │ │ │ │ ├── import-modal │ │ │ │ │ │ │ │ └── open-import-modal.tsx │ │ │ │ │ │ │ └── import-export-modal.module.css │ │ │ │ │ │ ├── with-integration.tsx │ │ │ │ │ │ ├── integration-variants │ │ │ │ │ │ │ └── wrapper │ │ │ │ │ │ │ │ └── integration-wrapper.tsx │ │ │ │ │ │ ├── save-button │ │ │ │ │ │ │ └── save-button.tsx │ │ │ │ │ │ └── saving-status │ │ │ │ │ │ │ └── saving-status.tsx │ │ │ │ │ ├── utils │ │ │ │ │ │ └── show-snackbar.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── hooks │ │ │ │ │ │ └── use-auto-save-on-close.ts │ │ │ │ ├── i18n │ │ │ │ │ ├── components │ │ │ │ │ │ └── language-selector │ │ │ │ │ │ │ ├── language-selector.module.css │ │ │ │ │ │ │ └── language-selector.tsx │ │ │ │ │ ├── plugin-components.ts │ │ │ │ │ ├── use-detect-language-change.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── i18next.d.ts │ │ │ │ ├── app-bar │ │ │ │ │ ├── variables.css │ │ │ │ │ ├── components │ │ │ │ │ │ ├── toggle-dark-mode │ │ │ │ │ │ │ └── toggle-dark-mode.tsx │ │ │ │ │ │ ├── toggle-read-only-mode │ │ │ │ │ │ │ └── toggle-read-only-mode.tsx │ │ │ │ │ │ ├── toolbar │ │ │ │ │ │ │ └── toolbar.tsx │ │ │ │ │ │ └── controls │ │ │ │ │ │ │ └── controls.tsx │ │ │ │ │ ├── app-bar-container-lazy.tsx │ │ │ │ │ ├── app-bar-container.tsx │ │ │ │ │ └── functions │ │ │ │ │ │ └── get-controls-dots-items.tsx │ │ │ │ ├── snackbar │ │ │ │ │ └── snackbar-container.tsx │ │ │ │ ├── modals │ │ │ │ │ ├── plugin-components.ts │ │ │ │ │ ├── delete-confirmation │ │ │ │ │ │ └── delete-confirmation.module.css │ │ │ │ │ ├── template-selector │ │ │ │ │ │ ├── open-template-selector-modal.tsx │ │ │ │ │ │ ├── template-selector.module.css │ │ │ │ │ │ └── components │ │ │ │ │ │ │ └── tile.tsx │ │ │ │ │ ├── providers │ │ │ │ │ │ └── modal-provider.tsx │ │ │ │ │ └── stores │ │ │ │ │ │ └── use-modal-store.ts │ │ │ │ ├── syntax-highlighter │ │ │ │ │ └── components │ │ │ │ │ │ └── syntax-highlighter-lazy.tsx │ │ │ │ └── changes-tracker │ │ │ │ │ └── stores │ │ │ │ │ └── use-changes-tracker-store.ts │ │ │ ├── plugins │ │ │ │ ├── download-pdf │ │ │ │ │ └── README.md │ │ │ │ ├── undo-redo │ │ │ │ │ └── README.md │ │ │ │ ├── reshapable-edges │ │ │ │ │ └── README.md │ │ │ │ ├── copy-paste │ │ │ │ │ └── README.md │ │ │ │ ├── avoid-nodes-edges │ │ │ │ │ └── README.md │ │ │ │ ├── help │ │ │ │ │ ├── assets │ │ │ │ │ │ └── maciej-teska-workflow.jpg │ │ │ │ │ ├── components │ │ │ │ │ │ ├── watermark │ │ │ │ │ │ │ ├── watermark.tsx │ │ │ │ │ │ │ └── watermark.module.css │ │ │ │ │ │ ├── footer-support-button.tsx │ │ │ │ │ │ └── app-bar │ │ │ │ │ │ │ └── get-app-bar-button.tsx │ │ │ │ │ ├── plugin-i18n.ts │ │ │ │ │ ├── modals │ │ │ │ │ │ ├── no-access │ │ │ │ │ │ │ ├── no-access.module.css │ │ │ │ │ │ │ └── no-access.tsx │ │ │ │ │ │ └── sales-contact │ │ │ │ │ │ │ └── sales-contact.module.css │ │ │ │ │ ├── locales │ │ │ │ │ │ ├── en │ │ │ │ │ │ │ └── translation.json │ │ │ │ │ │ └── pl │ │ │ │ │ │ │ └── translation.json │ │ │ │ │ ├── functions │ │ │ │ │ │ ├── open-help-modal.tsx │ │ │ │ │ │ ├── open-no-access-modal.tsx │ │ │ │ │ │ └── add-items-to-dots.tsx │ │ │ │ │ └── README.md │ │ │ │ ├── elk-layout │ │ │ │ │ └── README.md │ │ │ │ └── __demo │ │ │ │ │ └── README.md │ │ │ ├── components │ │ │ │ ├── form │ │ │ │ │ ├── form-control-with-label │ │ │ │ │ │ ├── form-control-with-label.module.css │ │ │ │ │ │ └── form-control-with-label.tsx │ │ │ │ │ └── label │ │ │ │ │ │ ├── label.tsx │ │ │ │ │ │ └── label.module.css │ │ │ │ ├── sidebar │ │ │ │ │ ├── variables.css │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ └── sidebar.module.css │ │ │ │ └── loader │ │ │ │ │ ├── loader.module.css │ │ │ │ │ └── loader.tsx │ │ │ ├── data │ │ │ │ ├── nodes │ │ │ │ │ ├── shared │ │ │ │ │ │ ├── shared-properties.ts │ │ │ │ │ │ └── general-information.ts │ │ │ │ │ ├── ai-agent │ │ │ │ │ │ ├── default-properties-data.ts │ │ │ │ │ │ ├── ai-agent.ts │ │ │ │ │ │ ├── select-options.ts │ │ │ │ │ │ └── schema.ts │ │ │ │ │ ├── conditional │ │ │ │ │ │ ├── default-properties-data.ts │ │ │ │ │ │ ├── conditional.ts │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ └── uischema.ts │ │ │ │ │ ├── decision │ │ │ │ │ │ ├── default-properties-data.ts │ │ │ │ │ │ ├── decision.ts │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ └── uischema.ts │ │ │ │ │ ├── delay │ │ │ │ │ │ ├── delay.ts │ │ │ │ │ │ ├── default-properties-data.ts │ │ │ │ │ │ ├── conditional-validation.ts │ │ │ │ │ │ ├── select-options.ts │ │ │ │ │ │ └── schema.ts │ │ │ │ │ ├── notification │ │ │ │ │ │ ├── default-properties-data.ts │ │ │ │ │ │ └── notification.ts │ │ │ │ │ ├── action │ │ │ │ │ │ ├── action.ts │ │ │ │ │ │ └── default-properties-data.ts │ │ │ │ │ └── trigger │ │ │ │ │ │ ├── trigger.ts │ │ │ │ │ │ └── default-properties-data.ts │ │ │ │ ├── palette.ts │ │ │ │ └── templates.ts │ │ │ ├── store │ │ │ │ ├── README.md │ │ │ │ └── slices │ │ │ │ │ ├── diagram-data-modification │ │ │ │ │ └── remove-elements.ts │ │ │ │ │ ├── user-preferences │ │ │ │ │ └── user-preferences-slice.ts │ │ │ │ │ ├── diagram-selection │ │ │ │ │ └── actions.ts │ │ │ │ │ └── palette │ │ │ │ │ └── palette-slice.ts │ │ │ ├── hooks │ │ │ │ ├── use-effect-change.ts │ │ │ │ ├── use-command-handler-keyboard.ts │ │ │ │ ├── use-translate-if-possible.ts │ │ │ │ ├── use-remove-elements.ts │ │ │ │ ├── use-theme.ts │ │ │ │ ├── use-fit-view.ts │ │ │ │ └── use-command-handler.ts │ │ │ └── app.module.css │ │ ├── main.tsx │ │ └── assets │ │ │ └── workflow-builder-logo.svg │ ├── lint-staged.config.mjs │ ├── favicon.ico │ ├── global.d.ts │ ├── scripts │ │ ├── fix-worker │ │ │ └── README.md │ │ └── preview-build.ts │ ├── tsconfig.json │ ├── Dockerfile │ ├── index.html │ ├── file-fallback-for-missing-plugins.ts │ ├── eslint.config.mjs │ ├── README.md │ └── file-replacement-plugin.ts ├── tools │ ├── tsconfig.json │ ├── lint-staged.config.mjs │ ├── README.md │ ├── package.json │ └── src │ │ └── tool-utils │ │ └── create-script.ts └── icons │ ├── lint-staged.config.mjs │ ├── index.ts │ ├── tsconfig.json │ ├── config.json │ ├── assets │ ├── status-draft.svg │ ├── status-active.svg │ ├── status-archived.svg │ ├── status-disabled.svg │ ├── status-template.svg │ ├── gemini-logo.svg │ ├── tree-structure-down.svg │ ├── jira-logo.svg │ ├── hubspot-logo.svg │ ├── workflow-builder-logo.svg │ └── airtable-logo.svg │ ├── src │ ├── icon.module.css │ └── create-icon-entry.ts │ ├── package.json │ └── README.md ├── CODEOWNERS ├── prettier.config.mjs ├── pnpm-workspace.yaml ├── lint-staged.config.mjs ├── .gitignore ├── DECISION-LOGS.md ├── docs ├── README.md └── overflow-ui.md ├── tsconfig.base.json ├── .github └── workflows │ └── build.yml └── package.json /apps/.env.default: -------------------------------------------------------------------------------- 1 | GTM_ID= 2 | -------------------------------------------------------------------------------- /apps/types/README.md: -------------------------------------------------------------------------------- 1 | # `@workflow-builder/types` 2 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/noop.ts: -------------------------------------------------------------------------------- 1 | export function noop() {} 2 | -------------------------------------------------------------------------------- /apps/tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /apps/types/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '../../eslint.config.mjs'; 2 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/consts.ts: -------------------------------------------------------------------------------- 1 | export const dataFormat = 'application/reactflow'; 2 | -------------------------------------------------------------------------------- /apps/icons/lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '../../lint-staged.config.mjs'; 2 | -------------------------------------------------------------------------------- /apps/tools/lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '../../lint-staged.config.mjs'; 2 | -------------------------------------------------------------------------------- /apps/types/lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '../../lint-staged.config.mjs'; 2 | -------------------------------------------------------------------------------- /apps/frontend/lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | export { default } from '../../lint-staged.config.mjs'; 2 | -------------------------------------------------------------------------------- /apps/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { Icon } from './src/icon'; 2 | export type { WBIcon } from './dist'; 3 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/palette-container.module.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | width: auto; 3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type Override = Omit & B; 2 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/typescript.ts: -------------------------------------------------------------------------------- 1 | export type Prettify = { 2 | [K in keyof T]: T[K]; 3 | } & {}; 4 | -------------------------------------------------------------------------------- /apps/frontend/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synergycodes/workflowbuilder/HEAD/apps/frontend/favicon.ico -------------------------------------------------------------------------------- /apps/frontend/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Every change in the repository must be reviewed by: 2 | * @piotrblaszczyk @librowski-synergy @lukasz-jazwa 3 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/index.ts: -------------------------------------------------------------------------------- 1 | import './components'; 2 | import './functions'; 3 | import './i18n'; 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/copy.ts: -------------------------------------------------------------------------------- 1 | export function copy(text: string) { 2 | navigator.clipboard?.writeText(text); 3 | } 4 | -------------------------------------------------------------------------------- /apps/icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/types/src/node-types.ts: -------------------------------------------------------------------------------- 1 | export enum NodeType { 2 | Node = 'node', 3 | AiNode = 'ai-node', 4 | DecisionNode = 'decision-node', 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/download-pdf/README.md: -------------------------------------------------------------------------------- 1 | # Download PDF 2 | 3 | https://www.overflow.dev/premium?path=/docs/misc-download-pdf-api--docs 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/undo-redo/README.md: -------------------------------------------------------------------------------- 1 | # Undo redo 2 | 3 | https://www.overflow.dev/premium?path=/docs/interaction-undo-redo-api--docs 4 | -------------------------------------------------------------------------------- /apps/icons/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sources": ["node_modules/@phosphor-icons/core/assets/regular", "./assets"], 3 | "outputDirectory": "./dist" 4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/utils/missing-plugin.stub.ts: -------------------------------------------------------------------------------- 1 | // This file is served if the real file does not exist 2 | export default {}; 3 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/reshapable-edges/README.md: -------------------------------------------------------------------------------- 1 | # Reshaping orthogonal edge 2 | 3 | The component implementing reshaping orthogonal edges via handles -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/ai-tools-control/ai-tools-control.module.css: -------------------------------------------------------------------------------- 1 | .selected-tool-button { 2 | justify-content: flex-start; 3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/copy-paste/README.md: -------------------------------------------------------------------------------- 1 | # Copy paste 2 | 3 | https://www.overflow.dev/premium?path=/docs/interaction-copy-paste-documentation--docs 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/components/placeholder-button/placeholder-button.module.css: -------------------------------------------------------------------------------- 1 | .placeholder-button { 2 | border-style: dashed; 3 | } 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/components/footer/palette-footer.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.625rem; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/decision-node-template/decision-node-template.module.css: -------------------------------------------------------------------------------- 1 | .decision-node { 2 | > div { 3 | min-width: max-content; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/avoid-nodes-edges/README.md: -------------------------------------------------------------------------------- 1 | # Avoid Nodes Edges 2 | 3 | https://www.overflow.dev/premium?path=/docs/edges-and-ports-avoid-nodes-edges-documentation--docs 4 | -------------------------------------------------------------------------------- /apps/frontend/scripts/fix-worker/README.md: -------------------------------------------------------------------------------- 1 | # Fix the worker issue 2 | 3 | After obfuscation, the worker file used by `@/plugins/avoid-nodes-edges` breaks. This script fixes it if detected. 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/edges/edge.consts.ts: -------------------------------------------------------------------------------- 1 | export const EDGE_OFFSET = 20; 2 | export const EDGE_CURVE_RADIUS = 16; 3 | export const SELF_CONNECTING_EDGE_LABEL_OFFSET = 100; 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/vertical-layout/vertical-layout.module.css: -------------------------------------------------------------------------------- 1 | .vertical-layout { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.5rem; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/properties-bar/components/edge-properties/edge-properties.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.75rem; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/assets/maciej-teska-workflow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/synergycodes/workflowbuilder/HEAD/apps/frontend/src/app/plugins/help/assets/maciej-teska-workflow.jpg -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import("prettier").Config} 3 | */ 4 | const config = { 5 | singleQuote: true, 6 | printWidth: 120, 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/setting-info/setting-info.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | gap: 0.75rem; 4 | align-items: center; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/consts.ts: -------------------------------------------------------------------------------- 1 | export const AUTO_SAVE_IF_CHANGED_OVER_X_SECONDS_AGO = 10; 2 | 3 | export const SKIP_AUTO_SAVE_CHECK_FOR_EVENTS = ['nodeDragStart', 'nodeDragChange']; 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/workflow-node-template/workflow-node-template.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | --ax-public-node-gap: 0; 3 | } 4 | 5 | .collapsible { 6 | padding-top: 0.5rem; 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/components/header/palette-header.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: space-between; 5 | gap: 0.75rem; 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/functions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-imports */ 2 | 3 | import '@/plugins/download-pdf/plugin-functions'; 4 | import '@/plugins/undo-redo/plugin-functions'; 5 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/elk-layout/README.md: -------------------------------------------------------------------------------- 1 | # ELK Layout 2 | https://www.overflow.dev/premium/elk-layout-with-handles-and-labels.html?path=/story/interaction-elk-layout--elk-layout-with-handles-and-labels -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-palette-item-outline-color-hover: var(var(--ax-node-stroke-primary-hover)); 3 | --wb-palette-item-shad-color-hover: rgba(21, 21, 22, 0.1); 4 | } 5 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | display: flex; 3 | justify-content: space-between; 4 | width: 100%; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/adapters/utils/sort-by-priority.ts: -------------------------------------------------------------------------------- 1 | export function sortByPriority(a: { priority?: number }, b: { priority?: number }) { 2 | return (b.priority || 0) - (a.priority || 0); 3 | } 4 | -------------------------------------------------------------------------------- /apps/icons/assets/status-draft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/types/src/selection.ts: -------------------------------------------------------------------------------- 1 | import { WorkflowBuilderEdge, WorkflowBuilderNode } from './node-data'; 2 | 3 | export type Selection = { 4 | node: WorkflowBuilderNode | null; 5 | edge: WorkflowBuilderEdge | null; 6 | }; 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/components/form/form-control-with-label/form-control-with-label.module.css: -------------------------------------------------------------------------------- 1 | @layer ui.component { 2 | .container { 3 | display: flex; 4 | flex-direction: column; 5 | gap: 0.25rem; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/icons/assets/status-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/icons/assets/status-archived.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/icons/assets/status-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/icons/assets/status-template.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/accordion-layout/accordion-layout.module.css: -------------------------------------------------------------------------------- 1 | .accordion-content, 2 | .accordion-content > div:not([class]) { 3 | display: flex; 4 | flex-direction: column; 5 | gap: 0.75rem; 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/properties-bar/components/properties-bar/properties-bar.module.css: -------------------------------------------------------------------------------- 1 | .extend-bounds { 2 | margin-left: -1rem; 3 | width: calc(100% + 1rem); 4 | 5 | & > * { 6 | padding-left: 1rem; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/diagram.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100vw; 3 | height: 100vh; 4 | } 5 | 6 | .container { 7 | :global(.react-flow__edgelabel-renderer) { 8 | z-index: 1001; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/i18n/components/language-selector/language-selector.module.css: -------------------------------------------------------------------------------- 1 | .title { 2 | composes: ax-public-p9 from global; 3 | 4 | color: var(--wb-app-bar-diagram-title-color); 5 | padding-right: 0.5rem; 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/components/dragged-item/dragged-item.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: absolute; 3 | background-color: transparent; 4 | transform-origin: top left; 5 | z-index: 2; 6 | top: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/get-is-valid-json.ts: -------------------------------------------------------------------------------- 1 | export function getIsValidJson(value: string): boolean { 2 | try { 3 | JSON.parse(value); 4 | 5 | return true; 6 | } catch { 7 | return false; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/ai-tools-control/components/add-ai-tool-form-content/add-ai-tool-form-content.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1.25rem; 5 | width: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "ES2022"] 4 | }, 5 | "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts", "src/**/*.spec.tsx", "src/**/*.test.tsx"], 6 | "extends": "../../tsconfig.base.json" 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/handles/is-inner-handle.ts: -------------------------------------------------------------------------------- 1 | import { INNER_HANDLE_MARKER } from './types'; 2 | 3 | export function isInnerHandle(handle: string | null): boolean { 4 | return handle ? handle.includes(INNER_HANDLE_MARKER) : false; 5 | } 6 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/json-form.module.css: -------------------------------------------------------------------------------- 1 | .json-form-container { 2 | > div { 3 | display: flex; 4 | flex-direction: column; 5 | gap: 1rem; 6 | } 7 | 8 | hr { 9 | width: calc(100% + 2rem); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/icons/src/icon.module.css: -------------------------------------------------------------------------------- 1 | .optional-icon-fade-in { 2 | opacity: 0; 3 | animation: fadeIn 0.1s ease-in forwards; 4 | } 5 | 6 | @keyframes fadeIn { 7 | from { 8 | opacity: 0; 9 | } 10 | to { 11 | opacity: 1; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/group-layout/group-layout.module.css: -------------------------------------------------------------------------------- 1 | .group-header { 2 | padding: 8px 0; 3 | } 4 | 5 | .group-layout { 6 | display: flex; 7 | flex-direction: column; 8 | padding: 20px 0; 9 | gap: 12px; 10 | } 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - './apps/*' 3 | 4 | catalog: 5 | '@phosphor-icons/core': ^2.1.1 6 | '@xyflow/react': ^12.3.6 7 | 'react': ^19.1.0 8 | '@types/react': 19.1.8 9 | 'react-dom': ^19.1.0 10 | 11 | useNodeVersion: 22.12.0 12 | engineStrict: true 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/horizontal-layout/horizontal-layout.module.css: -------------------------------------------------------------------------------- 1 | .horizontal-layout { 2 | display: grid; 3 | grid-auto-flow: column; 4 | grid-auto-columns: 1fr; 5 | gap: 0.5rem; 6 | 7 | & > *:last-child { 8 | justify-self: end; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/diagram.const.ts: -------------------------------------------------------------------------------- 1 | export const FIT_VIEW_DURATION_TIME = 800; 2 | export const FIT_VIEW_MAX_ZOOM = 1; 3 | export const FIT_VIEW_PADDING = 0.5; 4 | 5 | export const SNAP_IS_ACTIVE: boolean = true; 6 | export const SNAP_GRID: [number, number] | undefined = [18, 18]; 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/browser.ts: -------------------------------------------------------------------------------- 1 | export const isSafari = navigator.userAgent.includes('Safari'); 2 | export const isFirefox = navigator.userAgent.includes('Firefox'); 3 | export const isOpera = navigator.userAgent.includes('OPR'); 4 | export const isChrome = navigator.userAgent.includes('Chrome'); 5 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/url.ts: -------------------------------------------------------------------------------- 1 | export function openInNewTab(url: string): void { 2 | const newWindow = window.open(url, '_blank', 'noopener,noreferrer'); 3 | 4 | if (newWindow) { 5 | newWindow.opener = null; // Security: prevents the new page from accessing the window object 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/types/label.ts: -------------------------------------------------------------------------------- 1 | import { LabelProps } from '@/components/form/label/label'; 2 | import { Override } from './utils'; 3 | import { LabelElement as BaseLabelElement } from '@jsonforms/core'; 4 | 5 | export type LabelElement = Override>; 6 | -------------------------------------------------------------------------------- /lint-staged.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('lint-staged').Configuration} 3 | */ 4 | export default { 5 | '*.{ts,tsx,js,json,css}': (files) => `prettier --write --log-level=silent ${files.join(' ')}`, 6 | '*.{ts,tsx}': [(files) => `eslint --max-warnings=0 --fix ${files.join(' ')}`, () => `tsc --noEmit`], 7 | }; 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/shared/shared-properties.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodePropertiesSchema } from '@workflow-builder/types/node-schema'; 2 | 3 | export const sharedProperties: BaseNodePropertiesSchema = { 4 | label: { 5 | type: 'string', 6 | }, 7 | description: { 8 | type: 'string', 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components/optional-hooks.tsx: -------------------------------------------------------------------------------- 1 | import { withOptionalComponentPlugins } from '../adapters/adapter-components'; 2 | 3 | function OptionalHooksComponent() { 4 | return null; 5 | } 6 | 7 | export const OptionalHooks = withOptionalComponentPlugins(OptionalHooksComponent, 'OptionalHooks'); 8 | -------------------------------------------------------------------------------- /apps/icons/assets/gemini-logo.svg: -------------------------------------------------------------------------------- 1 | Gemini -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/components/indicator-dot/indicator-dot.tsx: -------------------------------------------------------------------------------- 1 | import styles from './indicator-dot.module.css'; 2 | import { PropsWithChildren } from 'react'; 3 | 4 | export function IndicatorDot({ children }: PropsWithChildren) { 5 | return
{children}
; 6 | } 7 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/dynamic-conditions-control/dynamic-conditions-form/conditions-form.module.css: -------------------------------------------------------------------------------- 1 | .form { 2 | width: 100%; 3 | } 4 | 5 | .controls-container { 6 | display: flex; 7 | flex-flow: column; 8 | gap: 0.375rem; 9 | } 10 | 11 | .add-button { 12 | margin-top: 1rem; 13 | width: 100%; 14 | } 15 | -------------------------------------------------------------------------------- /apps/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "forceConsistentCasingInFileNames": true, 5 | "strict": true, 6 | "noImplicitOverride": true, 7 | "noPropertyAccessFromIndexSignature": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/icon-placeholder/icon-placeholder.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | border-radius: var(--ax-token-radius-button-s); 6 | outline: 1px dashed var(--ax-ui-stroke-secondary-default); 7 | padding: 0.4375rem; 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/decision-node-template/components/branches-container.module.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-branches-container-gap: 0.25rem; 3 | } 4 | 5 | .branches-container { 6 | display: flex; 7 | justify-content: center; 8 | gap: var(--wb-branches-container-gap); 9 | 10 | &.vertical { 11 | flex-direction: column; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/get-is-valid-layout-directions.ts: -------------------------------------------------------------------------------- 1 | import { LayoutDirection, layoutDirections } from '@workflow-builder/types/common'; 2 | 3 | export function getIsValidLayoutDirections(value?: unknown) { 4 | if (typeof value !== 'string') { 5 | return false; 6 | } 7 | 8 | return layoutDirections.includes(value as LayoutDirection); 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/types/rules.ts: -------------------------------------------------------------------------------- 1 | import { AndCondition, LeafCondition, OrCondition, RuleEffect, SchemaBasedCondition } from '@jsonforms/core'; 2 | 3 | type UISchemaCondition = OrCondition | AndCondition | LeafCondition | SchemaBasedCondition; 4 | 5 | export type UISchemaRule = { 6 | effect: keyof typeof RuleEffect; 7 | condition: UISchemaCondition; 8 | }; 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/i18n/plugin-components.ts: -------------------------------------------------------------------------------- 1 | import { registerComponentDecorator } from '@/features/plugins-core/adapters/adapter-components'; 2 | import { LanguageSelector } from './components/language-selector/language-selector'; 3 | 4 | registerComponentDecorator('OptionalAppBarControls', { 5 | content: LanguageSelector, 6 | priority: 10, 7 | place: 'before', 8 | }); 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/ai-agent/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { AiAgentNodeSchema } from './schema'; 3 | import { statusOptions } from '../shared/general-information'; 4 | 5 | export const defaultPropertiesData: NodeDataProperties = { 6 | status: statusOptions.active.value, 7 | }; 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/components/watermark/watermark.tsx: -------------------------------------------------------------------------------- 1 | import WatermarkImage from '../../assets/watermark.svg?react'; 2 | import styles from './watermark.module.css'; 3 | 4 | export function Watermark() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/get-is-valid-from-properties.ts: -------------------------------------------------------------------------------- 1 | import { FlatError } from '@workflow-builder/types/node-schema'; 2 | 3 | export function getIsValidFromProperties(properties: unknown): boolean | undefined { 4 | if (!properties) { 5 | return; 6 | } 7 | 8 | const errors = (properties as { errors: FlatError[] })?.errors || []; 9 | 10 | return errors.length === 0; 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/conditional/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { ConditionalNodeSchema } from './schema'; 3 | 4 | export const defaultPropertiesData: NodeDataProperties = { 5 | label: 'node.conditional.label', 6 | description: 'node.conditional.description', 7 | conditionsArray: [], 8 | }; 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components/optional-app-bar-toolbar.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { withOptionalComponentPlugins } from '../adapters/adapter-components'; 3 | 4 | function OptionalWrapper({ children }: PropsWithChildren) { 5 | return children; 6 | } 7 | 8 | export const OptionalAppBarTools = withOptionalComponentPlugins(OptionalWrapper, 'OptionalAppBarTools'); 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components/optional-footer-content.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { withOptionalComponentPlugins } from '../adapters/adapter-components'; 3 | 4 | function OptionalWrapper({ children }: PropsWithChildren) { 5 | return children; 6 | } 7 | 8 | export const OptionalFooterContent = withOptionalComponentPlugins(OptionalWrapper, 'OptionalFooterContent'); 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/store/README.md: -------------------------------------------------------------------------------- 1 | # actions.ts 2 | If you perform an action that checks something once, global actions can be imported and called directly at that point without additional hooks, they don't mutate. 3 | 4 | https://zustand.docs.pmnd.rs/guides/practice-with-no-store-actions 5 | 6 | https://github.com/synergycodes/reactflow-app-alt-stores-demo 7 | 8 | www.youtube.com/watch?v=41FsulrcrQg 9 | 10 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components/optional-app-bar-controls.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { withOptionalComponentPlugins } from '../adapters/adapter-components'; 3 | 4 | function OptionalWrapper({ children }: PropsWithChildren) { 5 | return children; 6 | } 7 | 8 | export const OptionalAppBarControls = withOptionalComponentPlugins(OptionalWrapper, 'OptionalAppBarControls'); 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components/optional-edge-properties.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import { withOptionalComponentPlugins } from '../adapters/adapter-components'; 3 | 4 | function OptionalWrapper({ children }: PropsWithChildren) { 5 | return children; 6 | } 7 | 8 | export const OptionalEdgeProperties = withOptionalComponentPlugins(OptionalWrapper, 'OptionalEdgeProperties'); 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/node-info-wrapper/node-wrapper-info.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.75rem; 5 | border-radius: 0.5rem; 6 | padding: 0.625rem; 7 | border: 1px solid var(--ax-ui-stroke-primary-default); 8 | color: var(--ax-txt-primary-default); 9 | 10 | composes: ax-public-h10 from global; 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/decision/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { DecisionNodeSchema } from './schema'; 3 | import { statusOptions } from '../shared/general-information'; 4 | 5 | export const defaultPropertiesData: NodeDataProperties = { 6 | status: statusOptions.active.value, 7 | decisionBranches: [], 8 | }; 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/components/app-loader/app-loader-container.tsx: -------------------------------------------------------------------------------- 1 | import { Loader } from '@/components/loader/loader'; 2 | import { useIntegrationStore } from '../../stores/use-integration-store'; 3 | 4 | export function AppLoaderContainer() { 5 | const isLoading = useIntegrationStore((store) => store.savingStatus === 'disabled'); 6 | 7 | return ; 8 | } 9 | -------------------------------------------------------------------------------- /apps/tools/README.md: -------------------------------------------------------------------------------- 1 | # @workflow-builder/tools 2 | 3 | A collection of scripts and utilities for automating project-specific development tasks 4 | 5 | ## Available Tools 6 | 7 | ### Scripts 8 | 9 | - **collect-decision-logs**: Compiles a list of `*.decision-log.md` files from the project directory and its subdirectories 10 | 11 | 12 | ## Example of usage 13 | 14 | ```bash tools/deployment/scripts/prepare-community-version.sh`` -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/palette-container-lazy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Suspense } from 'react'; 3 | 4 | const PaletteContainer = React.lazy(() => 5 | import('./palette-container').then((module) => ({ default: module.PaletteContainer })), 6 | ); 7 | 8 | export function PaletteContainerLazy() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/plugin-i18n.ts: -------------------------------------------------------------------------------- 1 | import { registerPluginTranslation } from '@/features/plugins-core/adapters/adapter-i18n'; 2 | 3 | import * as translationEN from './locales/en/translation.json'; 4 | import * as translationPL from './locales/pl/translation.json'; 5 | 6 | registerPluginTranslation({ 7 | en: { 8 | translation: translationEN, 9 | }, 10 | pl: { 11 | translation: translationPL, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/components/node-section/node-section.tsx: -------------------------------------------------------------------------------- 1 | import styles from './node-section.module.css'; 2 | import { PropsWithChildren } from 'react'; 3 | 4 | type Props = PropsWithChildren<{ label: string }>; 5 | 6 | export function NodeSection({ label, children }: Props) { 7 | return ( 8 |
9 | {label} 10 | {children} 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-app-bar-background: var(--ax-ui-bg-primary-default); 3 | --wb-app-bar-border-radius: var(--ax-token-radius-shell); 4 | --wb-app-bar-border-color: var(--ax-ui-stroke-primary-default); 5 | --wb-app-bar-folder-name-color: var(--ax-txt-tertiary-default); 6 | --wb-app-bar-diagram-title-color: var(--ax-txt-primary-default); 7 | --wb-app-bar-logo-color: var(--ax-txt-primary-default); 8 | } 9 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/get-nodes-definitions-by-type.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | 3 | type NodesDefinitionsBySubType = { 4 | [subType: string]: PaletteItem; 5 | }; 6 | 7 | export function getNodesDefinitionsByType(palette: PaletteItem[]) { 8 | return palette.reduce((stack: NodesDefinitionsBySubType, item) => { 9 | stack[item.type] = item; 10 | 11 | return stack; 12 | }, {}); 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/render-elements.tsx: -------------------------------------------------------------------------------- 1 | import { JsonFormsDispatch } from '@jsonforms/react'; 2 | import { BaseLayoutElement, LayoutProps } from '../types/layouts'; 3 | import { UISchemaElement } from '@jsonforms/core'; 4 | 5 | export function renderElements({ uischema: { elements } }: LayoutProps) { 6 | return elements.map((child, index) => ); 7 | } 8 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/modals/no-access/no-access.module.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-no-access-color: var(--ax-txt-primary-default); 3 | --wb-no-access-sub-title-color: var(--ax-txt-secondary-default); 4 | } 5 | 6 | .container { 7 | width: 100%; 8 | display: flex; 9 | flex-direction: column; 10 | gap: 0.5rem; 11 | color: var(--wb-no-access-color); 12 | 13 | .sub-title { 14 | color: var(--wb-no-access-sub-title-color); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/node-info-wrapper/node-wrapper-info.tsx: -------------------------------------------------------------------------------- 1 | import styles from './node-wrapper-info.module.css'; 2 | import { PropsWithChildren } from 'react'; 3 | 4 | export function NodeInfoWrapper({ label, children }: PropsWithChildren<{ label: string }>) { 5 | return ( 6 |
7 | {label} 8 | {children} 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/palette/components/items/palette-items.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.5rem; 5 | box-sizing: border-box; 6 | 7 | .item { 8 | border-radius: 0.75rem; 9 | cursor: grab; 10 | outline-offset: -1px; 11 | outline: 1px solid transparent; 12 | } 13 | 14 | .disabled { 15 | cursor: default; 16 | user-select: none; 17 | opacity: 0.5; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/handles/types.ts: -------------------------------------------------------------------------------- 1 | import { HandleType } from '@xyflow/react'; 2 | 3 | export const INNER_HANDLE_MARKER = 'inner'; 4 | 5 | type InnerHandleMarker = typeof INNER_HANDLE_MARKER; 6 | type NodeId = string; 7 | type NodeEntityId = string; 8 | 9 | type InnerHandleId = `${OuterHandleId}:${InnerHandleMarker}:${NodeEntityId}`; 10 | type OuterHandleId = `${NodeId}:${HandleType}`; 11 | export type HandleId = InnerHandleId | OuterHandleId; 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/snackbar/snackbar-container.tsx: -------------------------------------------------------------------------------- 1 | import { Snackbar } from '@synergycodes/overflow-ui'; 2 | import { SnackbarProvider } from 'notistack'; 3 | 4 | export function SnackbarContainer() { 5 | return ( 6 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-effect-change.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | export default function useEffectChange(callback: () => void, dependencies: unknown[]) { 4 | const isInited = useRef(false); 5 | 6 | useEffect(() => { 7 | if (!isInited.current) { 8 | isInited.current = true; 9 | 10 | return; 11 | } 12 | 13 | return callback(); 14 | }, dependencies); // eslint-disable-line react-hooks/exhaustive-deps 15 | } 16 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "help": { 4 | "helpSupport": "Help & Support", 5 | "header": "Unlock Full Product Access", 6 | "title": "Reach out to learn more", 7 | "subtitle": "Connect with us for full access details.", 8 | "tooltipElk": "Refresh layout", 9 | "tooltipUndo": "Undo", 10 | "tooltipRedo": "Redo", 11 | "tooltipOpen": "Open" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/tool-info/tool-info.module.css: -------------------------------------------------------------------------------- 1 | .tools-container { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 0.25rem; 5 | 6 | &:empty { 7 | display: none; 8 | } 9 | } 10 | 11 | .icon-container { 12 | display: flex; 13 | gap: 0.75rem; 14 | align-items: center; 15 | 16 | .icon-placeholder-long { 17 | width: 100%; 18 | padding: 0.375rem; 19 | gap: 0.5rem; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/properties-bar/properties-bar-container-lazy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Suspense } from 'react'; 3 | 4 | const PropertiesBarContainer = React.lazy(() => 5 | import('./properties-bar-container').then((module) => ({ default: module.PropertiesBarContainer })), 6 | ); 7 | 8 | export function PropertiesBarContainerLazy() { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/functions/open-help-modal.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { SalesContact } from '../modals/sales-contact/sales-contact'; 3 | import { openModal } from '@/features/modals/stores/use-modal-store'; 4 | import { Info } from '@phosphor-icons/react'; 5 | 6 | export function openHelpModal() { 7 | openModal({ 8 | content: , 9 | icon: , 10 | title: i18n.t('plugins.help.helpSupport'), 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workflow-builder/tools", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "collect-decision-logs": "tsx ./src/scripts/collect-decision-logs.ts", 8 | "typecheck": "tsc --noEmit", 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "^22.12.0", 14 | "chalk": "^5.4.1", 15 | "remeda": "^2.19.2" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/frontend/src/app/components/sidebar/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-sidebar-vertical-padding: 0.75rem; 3 | --wb-sidebar-horizontal-padding: 1rem; 4 | --wb-sidebar-background: var(--ax-ui-bg-primary-default); 5 | --wb-sidebar-text-color: var(--ax-txt-primary-default); 6 | --wb-sidebar-border-color: var(--ax-ui-stroke-primary-default); 7 | --wb-sidebar-border-radius: 0.75rem; 8 | --wb-sidebar-content-gap: 1.25rem; 9 | 10 | --wb-sidebar-expanded-width: 20rem; 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/i18n.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-imports */ 2 | 3 | // The plugin used to manage switching between other plugins that usually don't work together 4 | import '@/plugins/__demo/plugin-i18n'; 5 | 6 | import '@/plugins/download-pdf/plugin-i18n'; 7 | import '@/plugins/elk-layout/plugin-i18n'; 8 | import '@/plugins/reshapable-edges/plugin-i18n'; 9 | import '@/plugins/undo-redo/plugin-i18n'; 10 | 11 | import '@/plugins/help/plugin-i18n'; 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/ai-agent-node-template/components/icon-placeholder/icon-placeholder.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import styles from './icon-placeholder.module.css'; 3 | 4 | import { PropsWithChildren } from 'react'; 5 | 6 | type Props = { 7 | className?: string; 8 | }; 9 | 10 | export function IconPlaceholder({ className, children }: PropsWithChildren) { 11 | return
{children}
; 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/components/watermark/watermark.module.css: -------------------------------------------------------------------------------- 1 | .watermark { 2 | position: absolute; 3 | bottom: 1.875rem; 4 | right: 1.875rem; 5 | height: 3.07vw; 6 | min-height: 1.2rem; 7 | 8 | cursor: pointer; 9 | 10 | transition: opacity 0.3s ease; 11 | opacity: 0.3; 12 | &:hover { 13 | opacity: 1; 14 | } 15 | 16 | z-index: 5; 17 | 18 | :global(html[data-theme='dark']) & { 19 | path { 20 | fill: white; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/__demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | **Workflow Builder** includes plugins that can often be used independently but are not typically used together. For example, the "_Avoid Nodes Edges_" plugin (which automatically finds the best path for an edge between two nodes) and the "_Reshapable edges_" plugin (which allows manual manipulation of edges) are usually not combined. 4 | 5 | The role of this plugin is to add additional options that allow switching between different modes. 6 | -------------------------------------------------------------------------------- /apps/frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom/client'; 3 | import TagManager from 'react-gtm-module'; 4 | 5 | import { App } from './app/app'; 6 | import './app/features/i18n'; 7 | 8 | TagManager.initialize({ 9 | gtmId: import.meta.env.GTM_ID, 10 | }); 11 | 12 | const root = ReactDOM.createRoot(document.querySelector('#root') as HTMLElement); 13 | 14 | root.render( 15 | 16 | 17 | , 18 | ); 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/components/footer-support-button.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from 'react-i18next'; 2 | import { Button } from '@synergycodes/overflow-ui'; 3 | import { openHelpModal } from '../functions/open-help-modal'; 4 | 5 | export function FooterSupportButton() { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/components/import-export/export-modal/open-export-modal.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { Icon } from '@workflow-builder/icons'; 3 | import { openModal } from '@/features/modals/stores/use-modal-store'; 4 | import { ExportModal } from './export-modal'; 5 | 6 | export function openExportModal() { 7 | openModal({ 8 | content: , 9 | icon: , 10 | title: i18n.t('importExport.export'), 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/modals/plugin-components.ts: -------------------------------------------------------------------------------- 1 | import { registerComponentDecorator } from '@/features/plugins-core/adapters/adapter-components'; 2 | import { DiagramContainer } from '../diagram/diagram'; 3 | import { ModalProvider } from './providers/modal-provider'; 4 | 5 | type DiagramContainerProps = React.ComponentProps; 6 | 7 | registerComponentDecorator('DiagramContainer', { 8 | content: ModalProvider, 9 | place: 'after', 10 | }); 11 | -------------------------------------------------------------------------------- /apps/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workflow-builder/types", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "typecheck": "tsc --noEmit", 7 | "lint": "eslint" 8 | }, 9 | "exports": { 10 | "./*": "./src/*.ts" 11 | }, 12 | "devDependencies": { 13 | "@workflow-builder/icons": "workspace:*", 14 | "@phosphor-icons/core": "catalog:", 15 | "react": "catalog:", 16 | "@types/react": "catalog:", 17 | "@xyflow/react": "catalog:" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/components/import-export/import-modal/open-import-modal.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { Icon } from '@workflow-builder/icons'; 3 | import { openModal } from '@/features/modals/stores/use-modal-store'; 4 | import { ImportModal } from './import-modal'; 5 | 6 | export function openImportModal() { 7 | openModal({ 8 | content: , 9 | icon: , 10 | title: i18n.t('importExport.import'), 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/edges/get-edge-z-index.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from '@xyflow/react'; 2 | import { isInnerHandle } from '../handles/is-inner-handle'; 3 | 4 | export function getEdgeZIndex(connection: Connection): number | undefined { 5 | const isOverlayEdge = isInnerHandle(connection.sourceHandle) || isInnerHandle(connection.targetHandle); 6 | 7 | return isOverlayEdge ? OVERLAY_Z_INDEX : BASE_Z_INDEX; 8 | } 9 | 10 | const OVERLAY_Z_INDEX = 1001; 11 | const BASE_Z_INDEX = undefined; 12 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/properties-bar/components/properties-bar/render-component.tsx: -------------------------------------------------------------------------------- 1 | import { SingleSelectedElement } from '../../use-single-selected-element'; 2 | import { PropertiesBarItem } from './properties-bar.types'; 3 | 4 | export function renderComponent( 5 | componentMap: PropertiesBarItem[], 6 | selection: SingleSelectedElement, 7 | selectedTab: string, 8 | ) { 9 | return componentMap.find(({ when }) => when({ selection, selectedTab }))?.component({ selection, selectedTab }); 10 | } 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/locales/pl/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "help": { 4 | "helpSupport": "Pomoc i wsparcie", 5 | "header": "Otrzymaj pełny dostęp do produktu", 6 | "title": "Skontaktuj się, aby dowiedzieć się więcej", 7 | "subtitle": "Uzyskaj szczegółowe informacje o pełnym dostępie.", 8 | "tooltipElk": "Odśwież layout", 9 | "tooltipUndo": "Cofnij", 10 | "tooltipRedo": "Ponów", 11 | "tooltipOpen": "Otwórz" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/update-keys.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export function updateKeys(data: any) { 3 | function updateKey(object: any) { 4 | if (object && typeof object === 'object') { 5 | for (const key in object) { 6 | if (key === 'key') { 7 | object[key] = crypto.randomUUID(); 8 | } else { 9 | updateKey(object[key]); 10 | } 11 | } 12 | } 13 | } 14 | 15 | updateKey(data); 16 | 17 | return data; 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:mainline-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY . . 6 | 7 | COPY --chown=nginx:nginx ./tools/deployment/nginx/nginx.conf /etc/nginx/nginx.conf 8 | COPY --chown=nginx:nginx ./tools/deployment/nginx/default.conf /etc/nginx/conf.d/default.conf 9 | COPY --chown=nginx:nginx ./dist/apps/frontend /usr/share/nginx/html 10 | 11 | RUN touch /var/run/nginx.pid && \ 12 | chown -R nginx:nginx /var/run/nginx.pid && \ 13 | chown -R nginx:nginx /var/cache/nginx 14 | 15 | USER nginx 16 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/delay/delay.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { DelayNodeSchema, schema } from './schema'; 4 | import { uischema } from './uischema'; 5 | 6 | export const delay: PaletteItem = { 7 | label: 'node.delay.label', 8 | description: 'node.delay.description', 9 | type: 'delay', 10 | icon: 'Timer', 11 | defaultPropertiesData, 12 | schema, 13 | uischema, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-command-handler-keyboard.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { useEffect } from 'react'; 3 | 4 | import { CommandHandler } from './use-command-handler'; 5 | import { useKeyPress } from './use-key-press'; 6 | 7 | export function useCommandHandlerKeyboard(commandHandler: CommandHandler) { 8 | const a = useKeyPress('a', { withControlOrMeta: true }); 9 | 10 | useEffect(() => { 11 | if (a) { 12 | commandHandler.selectAll(); 13 | } 14 | }, [a]); 15 | } 16 | -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-translate-if-possible.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | 4 | export function useTranslateIfPossible() { 5 | const { t, i18n } = useTranslation(); 6 | 7 | const translateIfPossible = useCallback( 8 | (value = '') => { 9 | if (value && i18n.exists(value)) { 10 | return t(value) as string; 11 | } 12 | 13 | return; 14 | }, 15 | [i18n, t], 16 | ); 17 | 18 | return translateIfPossible; 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/notification/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { NotificationNodeSchema } from './schema'; 3 | 4 | export const defaultPropertiesData: NodeDataProperties = { 5 | label: 'node.notification.label', 6 | description: 'node.notification.description', 7 | status: 'active', 8 | sendEmail: { 9 | priority: 'normal', 10 | retryOnFailure: false, 11 | retries: 3, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/action/action.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { ActionNodeSchema, schema } from './schema'; 4 | import { uischema } from './uischema'; 5 | 6 | export const action: PaletteItem = { 7 | type: 'action', 8 | icon: 'PlayCircle', 9 | label: 'node.action.label', 10 | description: 'node.action.description', 11 | defaultPropertiesData, 12 | schema, 13 | uischema, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/handles/get-handle-id.ts: -------------------------------------------------------------------------------- 1 | import { HandleType } from '@xyflow/react'; 2 | import { HandleId } from './types'; 3 | 4 | export function getHandleId({ handleType, nodeId, innerId }: GetHandleIdOptions): HandleId { 5 | const idBase = `${nodeId}:${handleType}` as const; 6 | 7 | if (!innerId) { 8 | return idBase; 9 | } 10 | 11 | return `${idBase}:inner:${innerId}`; 12 | } 13 | 14 | type GetHandleIdOptions = { 15 | nodeId: string; 16 | handleType: HandleType; 17 | innerId?: string; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/properties-bar/components/header/properties-bar-header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | gap: 0.5rem; 6 | 7 | .text-container { 8 | display: flex; 9 | flex-direction: column; 10 | flex-grow: 1; 11 | 12 | p { 13 | display: -webkit-box; 14 | -webkit-line-clamp: 2; 15 | line-clamp: 2; 16 | -webkit-box-orient: vertical; 17 | overflow: hidden; 18 | margin: 0; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/modals/no-access/no-access.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import styles from './no-access.module.css'; 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | export function NoAccess() { 6 | const { t } = useTranslation(); 7 | 8 | return ( 9 |
10 | {t('plugins.help.title')} 11 | {t('plugins.help.subtitle')} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/delay/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { DelayNodeSchema } from './schema'; 3 | 4 | export const defaultPropertiesData: NodeDataProperties = { 5 | label: 'node.delay.label', 6 | description: 'node.delay.description', 7 | status: 'active', 8 | duration: { 9 | timeUnits: 'none', 10 | delayAmount: 3, 11 | maxWaitTime: '24', 12 | expression: 'order.processing_time * 2', 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/edges/label-edge/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-edge-color: var(--ax-colors-gray-500); /* missing token */ 3 | --wb-edge-color-temporary: var(--ax-colors-gray-500-50); /* missing token */ 4 | --wb-edge-color-hover: var(--ax-colors-gray-600); /* missing token */ 5 | --wb-edge-color-select: var(--ax-colors-acc1-500); /* missing token */ 6 | 7 | --wb-edge-label-color: var(--ax-colors-gray-500); /* missing token */ 8 | --wb-edge-label-background: var(--ax-colors-gray-200); /* missing token */ 9 | } 10 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/trigger/trigger.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { schema, TriggerNodeSchema } from './schema'; 4 | import { uischema } from './uischema'; 5 | 6 | export const triggerNode: PaletteItem = { 7 | label: 'node.trigger.label', 8 | description: 'node.trigger.description', 9 | type: 'trigger', 10 | icon: 'Lightning', 11 | defaultPropertiesData, 12 | schema, 13 | uischema, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/scripts/preview-build.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'node:path'; 3 | import { fileURLToPath } from 'node:url'; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = path.dirname(__filename); 7 | 8 | const app = express(); 9 | const root = path.join(__dirname, '../../../dist/apps/frontend'); 10 | const port = 6007; 11 | 12 | app.use('/', express.static(root)); 13 | 14 | app.listen(port, () => { 15 | console.log(`✅ Workflow Builder is now available at http://localhost:${port}/`); 16 | }); 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/diagram-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from 'react'; 2 | import styles from '../../app.module.css'; 3 | import { useCommandHandler } from '@/hooks/use-command-handler'; 4 | import { useCommandHandlerKeyboard } from '@/hooks/use-command-handler-keyboard'; 5 | 6 | export function DiagramWrapper({ children }: PropsWithChildren) { 7 | const commandHandler = useCommandHandler(); 8 | useCommandHandlerKeyboard(commandHandler); 9 | 10 | return
{children}
; 11 | } 12 | -------------------------------------------------------------------------------- /apps/icons/assets/tree-structure-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/conditional/conditional.ts: -------------------------------------------------------------------------------- 1 | import { uischema } from './uischema'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { ConditionalNodeSchema, schema } from './schema'; 4 | import { PaletteItem } from '@workflow-builder/types/common'; 5 | 6 | export const conditional: PaletteItem = { 7 | label: 'node.conditional.label', 8 | description: 'node.conditional.description', 9 | type: 'conditional', 10 | icon: 'ListChecks', 11 | defaultPropertiesData, 12 | schema, 13 | uischema, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/components/toggle-dark-mode/toggle-dark-mode.tsx: -------------------------------------------------------------------------------- 1 | import { IconSwitch } from '@synergycodes/overflow-ui'; 2 | import { MoonStars, Sun } from '@phosphor-icons/react'; 3 | 4 | import { useTheme } from '@/hooks/use-theme'; 5 | 6 | export function ToggleDarkMode() { 7 | const { theme, toggleTheme } = useTheme(); 8 | 9 | return ( 10 | } 14 | IconChecked={} 15 | variant="secondary" 16 | /> 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/position-utils.ts: -------------------------------------------------------------------------------- 1 | import { SNAP_GRID, SNAP_IS_ACTIVE } from '@/features/diagram/diagram.const'; 2 | 3 | export function snapToGridIfNeeded(node?: { x?: number; y?: number }) { 4 | const { x = 0, y = 0 } = node || {}; 5 | 6 | if (SNAP_IS_ACTIVE) { 7 | const gridSizeX = SNAP_GRID?.[0] ?? 0; 8 | const gridSizeY = SNAP_GRID?.[1] ?? 0; 9 | 10 | return { 11 | x: Math.round(x / gridSizeX) * gridSizeX, 12 | y: Math.round(y / gridSizeY) * gridSizeY, 13 | }; 14 | } 15 | 16 | return { 17 | x, 18 | y, 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/i18n/use-detect-language-change.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | 4 | export function useDetectLanguageChange() { 5 | const { i18n } = useTranslation(); 6 | 7 | useEffect(() => { 8 | function handleLanguageChange(language: string) { 9 | document.documentElement.lang = language; 10 | } 11 | 12 | i18n.on('languageChanged', handleLanguageChange); 13 | 14 | return () => { 15 | i18n.off('languageChanged', handleLanguageChange); 16 | }; 17 | }, [i18n]); 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/notification/notification.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { NotificationNodeSchema, schema } from './schema'; 4 | import { uischema } from './uischema'; 5 | 6 | export const notification: PaletteItem = { 7 | label: 'node.notification.label', 8 | description: 'node.notification.description', 9 | type: 'notification', 10 | icon: 'PaperPlaneRight', 11 | defaultPropertiesData, 12 | schema, 13 | uischema, 14 | }; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/ai-agent/ai-agent.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { schema } from './schema'; 3 | import { defaultPropertiesData } from './default-properties-data'; 4 | import { NodeType } from '@workflow-builder/types/node-types'; 5 | import { uischema } from './uischema'; 6 | 7 | export const aiAgent: PaletteItem = { 8 | label: 'AI Agent', 9 | description: 'AI Agent Node', 10 | type: 'ai-agent', 11 | icon: 'AiAgent', 12 | templateType: NodeType.AiNode, 13 | defaultPropertiesData, 14 | schema, 15 | uischema, 16 | }; 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/get-node-types-object.ts: -------------------------------------------------------------------------------- 1 | import { NodeType } from '@workflow-builder/types/node-types'; 2 | import { NodeContainer } from './nodes/node-container'; 3 | import { NodeTypes } from '@xyflow/react'; 4 | import { AiNodeContainer } from './nodes/ai-node-container'; 5 | import { DecisionNodeContainer } from './nodes/decision-node-container'; 6 | 7 | export function getNodeTypesObject(): NodeTypes { 8 | return { 9 | [NodeType.Node]: NodeContainer, 10 | [NodeType.AiNode]: AiNodeContainer, 11 | [NodeType.DecisionNode]: DecisionNodeContainer, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/get-node-definition.ts: -------------------------------------------------------------------------------- 1 | import { paletteData } from '@/data/palette'; 2 | import { getNodesDefinitionsByType } from './get-nodes-definitions-by-type'; 3 | import { WorkflowBuilderNode } from '@workflow-builder/types/node-data'; 4 | import { PaletteItem } from '@workflow-builder/types/common'; 5 | 6 | const nodesDefinitionsByType = getNodesDefinitionsByType(paletteData); 7 | 8 | export function getNodeDefinition(node?: WorkflowBuilderNode): PaletteItem | undefined { 9 | const dataType = node?.data?.type || ''; 10 | 11 | return nodesDefinitionsByType[dataType]; 12 | } 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/app-bar-container-lazy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Suspense } from 'react'; 3 | 4 | const AppBarContainer = React.lazy(() => 5 | import('./app-bar-container').then((module) => ({ default: module.AppBarContainer })), 6 | ); 7 | 8 | /* 9 | It prevents the sidebars from moving if they loaded earlier. 10 | */ 11 | const expectedAppBarHeight = '62px'; 12 | 13 | export function AppBarContainerLazy() { 14 | return ( 15 | }> 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/modals/delete-confirmation/delete-confirmation.module.css: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | flex-direction: column; 4 | gap: 1rem; 5 | 6 | color: var(--ax-txt-secondary-default); 7 | 8 | b { 9 | font-weight: 700; 10 | } 11 | 12 | composes: ax-public-p9 from global; 13 | user-select: none; 14 | } 15 | 16 | .checkbox-wrapper { 17 | display: flex; 18 | align-items: center; 19 | gap: 0.5rem; 20 | 21 | & > label { 22 | cursor: pointer; 23 | } 24 | } 25 | 26 | .buttons { 27 | width: 100%; 28 | display: flex; 29 | justify-content: space-between; 30 | } 31 | -------------------------------------------------------------------------------- /apps/frontend/src/app/utils/validation/flat-errors.ts: -------------------------------------------------------------------------------- 1 | import { FlatError } from '@workflow-builder/types/node-schema'; 2 | import { ErrorObject } from 'ajv'; 3 | 4 | export function flatErrors( 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | errors: ErrorObject, unknown>[] | undefined | null, 7 | ): FlatError[] { 8 | return errors 9 | ? errors.map((error) => ({ 10 | keyword: error.keyword, 11 | instancePath: error.instancePath, 12 | schemaPath: error.schemaPath, 13 | message: error.message, 14 | })) 15 | : []; 16 | } 17 | -------------------------------------------------------------------------------- /apps/icons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@workflow-builder/icons", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsx ./src/generate-icons.ts", 8 | "typecheck": "tsc --noEmit", 9 | "lint": "eslint", 10 | "lint:fix": "eslint --fix", 11 | "prepare": "pnpm build" 12 | }, 13 | "devDependencies": { 14 | "@phosphor-icons/core": "catalog:", 15 | "@svgr/core": "^8.1.0", 16 | "@types/react": "catalog:", 17 | "react": "catalog:" 18 | }, 19 | "dependencies": { 20 | "@types/node": "^22.12.0", 21 | "clsx": "^2.1.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/trigger/default-properties-data.ts: -------------------------------------------------------------------------------- 1 | import { NodeDataProperties } from '@/features/json-form/types/default-properties'; 2 | import { TriggerNodeSchema } from './schema'; 3 | 4 | export const defaultPropertiesData: NodeDataProperties = { 5 | label: 'node.trigger.label', 6 | description: 'node.trigger.description', 7 | status: 'active', 8 | timeSchedule: { 9 | allDay: false, 10 | frequency: 'none', 11 | allDayFrequency: 'none', 12 | }, 13 | retrySettings: { 14 | interval: 'every15min', 15 | retries: '5', 16 | timeout: '30Min', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/palette.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { triggerNode } from './nodes/trigger/trigger'; 3 | import { action } from './nodes/action/action'; 4 | import { conditional } from './nodes/conditional/conditional'; 5 | import { notification } from './nodes/notification/notification'; 6 | import { delay } from './nodes/delay/delay'; 7 | import { decision } from './nodes/decision/decision'; 8 | import { aiAgent } from './nodes/ai-agent/ai-agent'; 9 | 10 | export const paletteData: PaletteItem[] = [triggerNode, action, delay, conditional, decision, notification, aiAgent]; 11 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/functions/open-no-access-modal.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { Icon } from '@workflow-builder/icons'; 3 | import { NoAccess } from '../modals/no-access/no-access'; 4 | import { SalesContact } from '../modals/sales-contact/sales-contact'; 5 | import { openModal } from '@/features/modals/stores/use-modal-store'; 6 | 7 | export function openNoAccessModal() { 8 | openModal({ 9 | content: , 10 | footer: , 11 | footerVariant: 'separated', 12 | icon: , 13 | title: i18n.t('plugins.help.header'), 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/app-bar-container.tsx: -------------------------------------------------------------------------------- 1 | import styles from './app-bar.module.css'; 2 | import './variables.css'; 3 | 4 | import { noop } from '@/utils/noop'; 5 | 6 | import { Toolbar } from './components/toolbar/toolbar'; 7 | import { ProjectSelection } from './components/project-selection/project-selection'; 8 | import { Controls } from './components/controls/controls'; 9 | 10 | export function AppBarContainer() { 11 | return ( 12 |
13 | 14 | 15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/layout-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { IndicatorDot } from '../components/indicator-dot/indicator-dot'; 2 | import { BaseLayoutElement, LayoutProps } from '../types/layouts'; 3 | 4 | type Props = LayoutProps & { 5 | children: React.ReactNode; 6 | hasErrors?: boolean; 7 | }; 8 | 9 | export function LayoutWrapper({ children, visible, hasErrors = false }: Props) { 10 | if (!visible) { 11 | return; 12 | } 13 | 14 | return ( 15 | <> 16 | {hasErrors && {children}} 17 | {!hasErrors && children} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-remove-elements.ts: -------------------------------------------------------------------------------- 1 | import { Selection } from '@workflow-builder/types/selection'; 2 | import { useReactFlow } from '@xyflow/react'; 3 | 4 | export function useRemoveElements() { 5 | const { deleteElements } = useReactFlow(); 6 | 7 | function removeElements(selectedElement?: Selection) { 8 | if (!selectedElement) { 9 | return; 10 | } 11 | 12 | deleteElements({ 13 | nodes: selectedElement.node ? [selectedElement.node] : undefined, 14 | edges: selectedElement.edge ? [selectedElement.edge] : undefined, 15 | }); 16 | } 17 | 18 | return { removeElements }; 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/components/app-bar/get-app-bar-button.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { Icon, WBIcon } from '@workflow-builder/icons'; 3 | import { NavButton } from '@synergycodes/overflow-ui'; 4 | import { openNoAccessModal } from '../../functions/open-no-access-modal'; 5 | 6 | export function getAppBarButton(icon: WBIcon, tooltip?: Parameters[0]) { 7 | return function mockAppBarButton() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/components/form/label/label.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import styles from './label.module.css'; 3 | 4 | import { Asterisk } from '@phosphor-icons/react'; 5 | import { ItemSize } from '@synergycodes/overflow-ui'; 6 | 7 | export type LabelProps = { 8 | label: string; 9 | required?: boolean; 10 | size?: ItemSize; 11 | }; 12 | 13 | export function Label({ label, required, size = 'medium' }: LabelProps) { 14 | return ( 15 | 16 | {required && } 17 | {label} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/modals/template-selector/open-template-selector-modal.tsx: -------------------------------------------------------------------------------- 1 | import i18n from 'i18next'; 2 | import { Icon } from '@workflow-builder/icons'; 3 | import useStore from '@/store/store'; 4 | import { openModal } from '@/features/modals/stores/use-modal-store'; 5 | import { TemplateSelector } from './template-selector'; 6 | 7 | export function openTemplateSelectorModal() { 8 | openModal({ 9 | content: , 10 | icon: , 11 | title: i18n.t('plugins.help.header'), 12 | onModalClosed: () => useStore.getState().setDiagramModel(undefined, { skipIfNotEmpty: true }), 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/decision/decision.ts: -------------------------------------------------------------------------------- 1 | import { PaletteItem } from '@workflow-builder/types/common'; 2 | import { defaultPropertiesData } from './default-properties-data'; 3 | import { DecisionNodeSchema, schema } from './schema'; 4 | import { uischema } from './uischema'; 5 | import { NodeType } from '@workflow-builder/types/node-types'; 6 | 7 | export const decision: PaletteItem = { 8 | label: 'node.decision.label', 9 | description: 'node.decision.description', 10 | type: 'decision', 11 | icon: 'ArrowsSplit', 12 | templateType: NodeType.DecisionNode, 13 | defaultPropertiesData, 14 | schema, 15 | uischema, 16 | }; 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/components/placeholder-button/placeholder-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@synergycodes/overflow-ui'; 2 | import styles from './placeholder-button.module.css'; 3 | import { PlusCircle } from '@phosphor-icons/react'; 4 | 5 | type Props = { 6 | label: string; 7 | } & Omit, 'children'>; 8 | 9 | export function PlaceholderButton({ label, size = 'extra-small', ...props }: Props) { 10 | return ( 11 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/frontend/src/app/plugins/help/README.md: -------------------------------------------------------------------------------- 1 | # Commercial License Notice 2 | 3 | If you have been granted a **commercial license** for this software, you are permitted to remove the directory: `plugins/no-access`. 4 | 5 | Doing so will result in an application without the "_Unlock Full Product Access_" popup and watermark in the corner. 6 | 7 | ## Plugins in Workflow Builder 8 | 9 | The application can run without this folder by using stub files. If you want to remove references to the stubs, you can also delete the two lines where they are imported. 10 | 11 | - `apps/frontend/src/app/features/plugins-core/i18n.ts` 12 | - `apps/frontend/src/app/features/plugins-core/index.ts` 13 | -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-theme.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useCallback } from 'react'; 2 | 3 | const THEME_KEY = 'wb-theme'; 4 | type Theme = 'dark' | 'light'; 5 | 6 | export function useTheme() { 7 | const [theme, setTheme] = useState(() => { 8 | return (localStorage.getItem(THEME_KEY) || 'light') as Theme; 9 | }); 10 | 11 | useEffect(() => { 12 | document.documentElement.dataset.theme = theme; 13 | localStorage.setItem(THEME_KEY, theme); 14 | }, [theme]); 15 | 16 | const toggleTheme = useCallback(() => { 17 | setTheme((previous) => (previous === 'light' ? 'dark' : 'light')); 18 | }, []); 19 | 20 | return { theme, toggleTheme }; 21 | } 22 | -------------------------------------------------------------------------------- /apps/icons/src/create-icon-entry.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'node:fs'; 2 | import { outputDirectory } from '../config.json'; 3 | 4 | const REACT_IMPORT_CODE = "import React from 'react';"; 5 | 6 | export async function createIconEntry(keys: string[]) { 7 | const iconTypesCode = `export type WBIcon = ${keys.map((name) => `'${name}'`).join(' | ')};`; 8 | 9 | const importMapCode = `export const iconMap = { 10 | ${keys.map((key) => `${key}: React.lazy(() => import('./${key}')),`).join('\n')} 11 | };`; 12 | 13 | const entryFileCode = [REACT_IMPORT_CODE, iconTypesCode, importMapCode].join('\n'); 14 | 15 | writeFileSync(`${outputDirectory}/index.ts`, entryFileCode); 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/tasks.json 23 | !.vscode/launch.json 24 | !.vscode/extensions.json 25 | 26 | # misc 27 | /.sass-cache 28 | /connect.lock 29 | /coverage 30 | /libpeerconnection.log 31 | npm-debug.log 32 | yarn-error.log 33 | testem.log 34 | /typings 35 | 36 | # System Files 37 | .DS_Store 38 | Thumbs.db 39 | 40 | .nx/ 41 | 42 | **/.env.local 43 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/components/toggle-read-only-mode/toggle-read-only-mode.tsx: -------------------------------------------------------------------------------- 1 | import { IconSwitch } from '@synergycodes/overflow-ui'; 2 | import { PencilSimple, PencilSimpleSlash } from '@phosphor-icons/react'; 3 | 4 | import useStore from '@/store/store'; 5 | 6 | export function ToggleReadyOnlyMode() { 7 | const isReadOnlyMode = useStore((store) => store.isReadOnlyMode); 8 | const setToggleReadOnlyMode = useStore((store) => store.setToggleReadOnlyMode); 9 | 10 | return ( 11 | } 15 | IconChecked={} 16 | /> 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/data/nodes/conditional/schema.ts: -------------------------------------------------------------------------------- 1 | import { NodeSchema } from '@workflow-builder/types/node-schema'; 2 | import { sharedProperties } from '../shared/shared-properties'; 3 | 4 | export const schema = { 5 | properties: { 6 | ...sharedProperties, 7 | conditionsArray: { 8 | type: 'array', 9 | items: { 10 | type: 'object', 11 | properties: { 12 | x: { type: 'string' }, 13 | comparisonOperator: { type: 'string' }, 14 | y: { type: 'string' }, 15 | logicalOperator: { type: 'string' }, 16 | }, 17 | }, 18 | }, 19 | }, 20 | } satisfies NodeSchema; 21 | 22 | export type ConditionalNodeSchema = typeof schema; 23 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/components/with-integration.tsx: -------------------------------------------------------------------------------- 1 | import { withIntegrationThroughApi } from './integration-variants/with-integration-through-api'; 2 | import { withIntegrationThroughLocalStorage } from './integration-variants/with-integration-through-local-storage'; 3 | import { withIntegrationThroughProps } from './integration-variants/with-integration-through-props'; 4 | 5 | const hocByStrategy = { 6 | API: withIntegrationThroughApi, 7 | LOCAL_STORAGE: withIntegrationThroughLocalStorage, 8 | PROPS: withIntegrationThroughProps, 9 | } as const; 10 | 11 | /* 12 | Pick the hocByStrategy that fits your usage best. 13 | */ 14 | export const withIntegration = hocByStrategy.LOCAL_STORAGE; 15 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/dynamic-conditions-control/dynamic-condition-modal-footer/condition-modal-footer.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@synergycodes/overflow-ui'; 2 | import { useTranslation } from 'react-i18next'; 3 | 4 | type Props = { 5 | closeModal: () => void; 6 | handleConfirm: () => void; 7 | }; 8 | 9 | export function ConditionModalFooter({ closeModal, handleConfirm }: Props) { 10 | const { t } = useTranslation(); 11 | return ( 12 | <> 13 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/handles/get-handle-position.ts: -------------------------------------------------------------------------------- 1 | import { LayoutDirection } from '@workflow-builder/types/common'; 2 | import { HandleType, Position } from '@xyflow/react'; 3 | 4 | export function getHandlePosition({ handleType, direction }: GetHandlePositionOptions) { 5 | return HANDLE_POSITION_MAP[`${handleType}-${direction}`]; 6 | } 7 | 8 | const HANDLE_POSITION_MAP: Record<`${HandleType}-${LayoutDirection}`, Position> = { 9 | 'source-DOWN': Position.Bottom, 10 | 'source-RIGHT': Position.Right, 11 | 'target-DOWN': Position.Top, 12 | 'target-RIGHT': Position.Left, 13 | }; 14 | 15 | type GetHandlePositionOptions = { 16 | handleType: HandleType; 17 | direction: LayoutDirection; 18 | }; 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/store/slices/diagram-data-modification/remove-elements.ts: -------------------------------------------------------------------------------- 1 | import { Edge, Node } from '@xyflow/react'; 2 | import { GetDiagramState, SetDiagramState } from '@/store/store'; 3 | 4 | export function removeElements( 5 | elements: { nodes?: Node[]; edges?: Edge[] }, 6 | set: SetDiagramState, 7 | get: GetDiagramState, 8 | ) { 9 | const { nodes, edges } = elements; 10 | if (nodes) { 11 | set({ 12 | nodes: get().nodes.filter((node) => !nodes.some((nodeToRemove) => nodeToRemove.id === node.id)), 13 | }); 14 | } 15 | 16 | if (edges) { 17 | set({ 18 | edges: get().edges.filter((edge) => !edges.some((edgeToRemove) => edgeToRemove.id === edge.id)), 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/app-bar/components/toolbar/toolbar.tsx: -------------------------------------------------------------------------------- 1 | import styles from '../../app-bar.module.css'; 2 | import Logo from '../../../../../assets/workflow-builder-logo.svg?react'; 3 | 4 | import { OptionalAppBarTools } from '@/features/plugins-core/components/optional-app-bar-toolbar'; 5 | import { SaveButton } from '@/features/integration/components/save-button/save-button'; 6 | 7 | export function Toolbar() { 8 | return ( 9 |
10 | 11 |
12 | 13 | 14 | 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/store/slices/user-preferences/user-preferences-slice.ts: -------------------------------------------------------------------------------- 1 | import { GetDiagramState, SetDiagramState } from '@/store/store'; 2 | 3 | export type UserPreferencesState = { 4 | shouldSkipShowingConfirmation: boolean; 5 | setShouldSkipShowDeleteConfirmation: (value: boolean) => void; 6 | }; 7 | 8 | export function useUserPreferencesSlice(set: SetDiagramState, _get: GetDiagramState): UserPreferencesState { 9 | return { 10 | shouldSkipShowingConfirmation: false, 11 | setShouldSkipShowDeleteConfirmation: (value: boolean) => { 12 | set((state) => { 13 | const newState = { ...state, shouldSkipShowingConfirmation: value }; 14 | return newState; 15 | }); 16 | }, 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/layouts/vertical-layout/vertical-layout.tsx: -------------------------------------------------------------------------------- 1 | import styles from './vertical-layout.module.css'; 2 | import { LayoutWrapper } from '../layout-wrapper'; 3 | import { renderElements } from '../render-elements'; 4 | import { LayoutProps, VerticalLayoutElement } from '../../types/layouts'; 5 | import { createLayoutRenderer } from '../../utils/rendering'; 6 | 7 | function VerticalLayout(props: LayoutProps) { 8 | return ( 9 | 10 |
{renderElements(props)}
11 |
12 | ); 13 | } 14 | 15 | export const verticalLayoutRenderer = createLayoutRenderer('VerticalLayout', VerticalLayout); 16 | -------------------------------------------------------------------------------- /DECISION-LOGS.md: -------------------------------------------------------------------------------- 1 | # Decision Logs 2 | 3 | - _03.02.2025_: [Using Web Components for Framework-Agnostic Integration](./apps/frontend/src/web-component-wrapper.decision-log.md) 4 | - _05.02.2025_: [Dynamic form generation for node properties](./apps/frontend/src/app/features/json-form/form-generation.decision-log.md) 5 | - _04.03.2025_: [Lazy-loaded Icons](./apps/icons/lazy-loaded-icons-04-03-2025.decision-log.md) 6 | - _08.04.2025_: [Lazy-loaded Icons](./apps/icons/lazy-loaded-icons-08-04-2025.decision-log.md) 7 | - _15.04.2025_: [Internationalization implementation with i18next](./apps/frontend/src/app/features/i18n/i18next.decision-log.md) 8 | - _26.05.2025_: [JSON Form Validation Strategy](./apps/frontend/src/app/features/json-form/form-validation.decision-log.md) -------------------------------------------------------------------------------- /apps/frontend/src/app/hooks/use-fit-view.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import useStore from '@/store/store'; 3 | import { FIT_VIEW_DURATION_TIME, FIT_VIEW_MAX_ZOOM, FIT_VIEW_PADDING } from '@/features/diagram/diagram.const'; 4 | 5 | export function useFitView() { 6 | const reactFlowInstance = useStore((store) => store.reactFlowInstance); 7 | 8 | const fitView = useCallback(() => { 9 | requestAnimationFrame(() => { 10 | if (reactFlowInstance) { 11 | reactFlowInstance.fitView({ 12 | duration: FIT_VIEW_DURATION_TIME, 13 | maxZoom: FIT_VIEW_MAX_ZOOM, 14 | padding: FIT_VIEW_PADDING, 15 | }); 16 | } 17 | }); 18 | }, [reactFlowInstance]); 19 | 20 | return fitView; 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/ai-tools-control/components/add-ai-tool-footer/add-ai-tool-footer.tsx: -------------------------------------------------------------------------------- 1 | import { FORM_TOOLS_NAME } from '../add-ai-tool-form-content/add-ai-tool-form-content'; 2 | import styles from './add-ai-tool-footer.module.css'; 3 | import { Button } from '@synergycodes/overflow-ui'; 4 | 5 | type Props = { 6 | onCancelClick: () => void; 7 | }; 8 | 9 | export function AddAiToolFooter({ onCancelClick }: Props) { 10 | return ( 11 |
12 | 15 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/listeners/node-drag-start-listeners.ts: -------------------------------------------------------------------------------- 1 | import { OnNodeDrag, Node } from '@xyflow/react'; 2 | 3 | const nodeDragStartListeners = new Set(); 4 | 5 | export function addNodeDragStartListener(listener: OnNodeDrag) { 6 | nodeDragStartListeners.add(listener); 7 | } 8 | 9 | export function removeNodeDragStartListener(listener: OnNodeDrag) { 10 | nodeDragStartListeners.delete(listener); 11 | } 12 | 13 | export function destroyNodeDragStartListeners() { 14 | nodeDragStartListeners.clear(); 15 | } 16 | 17 | export function callNodeDragStartListeners(event: React.MouseEvent, node: Node, nodes: Node[]) { 18 | for (const callback of nodeDragStartListeners) { 19 | callback(event, node, nodes); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/integration/utils/show-snackbar.ts: -------------------------------------------------------------------------------- 1 | import { showSnackbar } from '@/utils/show-snackbar'; 2 | import { SnackbarType } from '@synergycodes/overflow-ui'; 3 | import { OnSaveParams } from '../types'; 4 | 5 | export function showSnackbarSaveSuccessIfNeeded(savingParams?: OnSaveParams) { 6 | if (savingParams?.isAutoSave) { 7 | return; 8 | } 9 | 10 | showSnackbar({ 11 | title: 'saveDiagramSuccess', 12 | variant: SnackbarType.SUCCESS, 13 | }); 14 | } 15 | 16 | export function showSnackbarSaveErrorIfNeeded(savingParams?: OnSaveParams) { 17 | if (savingParams?.isAutoSave) { 18 | return; 19 | } 20 | 21 | showSnackbar({ 22 | title: 'saveDiagramError', 23 | variant: SnackbarType.ERROR, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/diagram/nodes/components/node-section/node-section.module.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --wb-node-section-gap: 0.75rem; 3 | --wb-node-section-padding: 0.625rem; 4 | --wb-node-section-border-radius: 0.5rem; 5 | --wb-node-section-border-color: var(--ax-ui-stroke-primary-default); 6 | --wb-node-section-border-width: 0.0625rem; 7 | } 8 | 9 | .container { 10 | display: flex; 11 | flex-direction: column; 12 | gap: var(--wb-node-section-gap); 13 | border-radius: var(--wb-node-section-border-radius); 14 | padding: var(--wb-node-section-padding); 15 | border: var(--wb-node-section-border-width) solid var(--wb-node-section-border-color); 16 | color: var(--ax-txt-primary-default); 17 | 18 | composes: ax-public-h10 from global; 19 | } 20 | -------------------------------------------------------------------------------- /apps/icons/assets/jira-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | jira 4 | 5 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/plugins-core/components.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-imports */ 2 | 3 | // Features using plugins mechanic 4 | import '@/features/i18n/plugin-components'; 5 | import '@/features/modals/plugin-components'; 6 | 7 | // The plugin used to manage switching between other plugins that usually don't work together 8 | import '@/plugins/__demo/plugin-components'; 9 | 10 | import '@/plugins/avoid-nodes-edges/plugin-components'; 11 | import '@/plugins/copy-paste/plugin-components'; 12 | import '@/plugins/elk-layout/plugin-components'; 13 | import '@/plugins/reshapable-edges/plugin-components'; 14 | import '@/plugins/undo-redo/plugin-components'; 15 | import '@/plugins/widgets/plugin-components'; 16 | 17 | import '@/plugins/help/plugin-components'; 18 | -------------------------------------------------------------------------------- /apps/frontend/src/app/features/json-form/controls/label-control/label-control.tsx: -------------------------------------------------------------------------------- 1 | import { JsonFormsRendererRegistryEntry, LabelProps } from '@jsonforms/core'; 2 | import { LabelElement } from '../../types/label'; 3 | import { withJsonFormsLabelProps } from '@jsonforms/react'; 4 | import { createTester } from '../../utils/rendering'; 5 | import { Label } from '@/components/form/label/label'; 6 | 7 | function LabelRendererComponent({ uischema }: LabelProps) { 8 | const { text, size, required } = uischema as LabelElement; 9 | 10 | return