Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6062cfafaf | ||
![]() |
5263e774d2 | ||
![]() |
22b4a04567 | ||
![]() |
e0d610071d | ||
![]() |
ab0966c005 | ||
![]() |
751eb41e72 | ||
![]() |
f08dc25711 | ||
![]() |
737eb31776 | ||
![]() |
d6abf283bc | ||
![]() |
bac4ab5aa4 | ||
![]() |
b5839390fd | ||
![]() |
d19271dae1 | ||
![]() |
ef5a09314e | ||
![]() |
ba52e298eb | ||
![]() |
b3c3998189 | ||
![]() |
782f9b5c04 | ||
![]() |
3079d8c605 | ||
![]() |
c5202d7b3e | ||
![]() |
fbae83f4de | ||
![]() |
1dc9646894 |
7
packages/backend/src/apps/eventbrite/assets/favicon.svg
Normal file
7
packages/backend/src/apps/eventbrite/assets/favicon.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<circle fill="#F05537" cx="128" cy="128" r="128"></circle>
|
||||
<path d="M117.475323,82.7290398 C136.772428,78.4407943 156.069532,86.3025777 166.790146,101.311437 L81.5017079,120.608542 C84.3605382,102.26438 98.1782181,87.0172853 117.475323,82.7290398 Z M167.266618,153.48509 C160.596014,163.252761 150.351872,170.161601 138.678314,172.782195 C119.38121,177.070441 99.8458692,169.208657 89.1252554,153.961562 L174.651929,134.664457 L188.469609,131.567391 L215.152026,125.611495 C214.91379,119.893834 214.199082,114.176173 213.007903,108.696749 C202.287289,62.7172275 155.354825,33.8906884 108.42236,44.6113021 C61.4898956,55.3319159 32.1868848,101.073201 43.1457344,147.290958 C54.1045839,193.508715 100.798813,222.097018 147.731277,211.376404 C175.366637,205.182272 196.807864,186.599875 207.766714,163.014525 L167.266618,153.48509 L167.266618,153.48509 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,19 @@
|
||||
import { URLSearchParams } from 'url';
|
||||
|
||||
export default async function generateAuthUrl($) {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value;
|
||||
const searchParams = new URLSearchParams({
|
||||
response_type: 'code',
|
||||
client_id: $.auth.data.clientId,
|
||||
redirect_uri: redirectUri,
|
||||
});
|
||||
|
||||
const url = `https://www.eventbrite.com/oauth/authorize?${searchParams.toString()}`;
|
||||
|
||||
await $.auth.set({
|
||||
url,
|
||||
});
|
||||
}
|
46
packages/backend/src/apps/eventbrite/auth/index.js
Normal file
46
packages/backend/src/apps/eventbrite/auth/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import generateAuthUrl from './generate-auth-url.js';
|
||||
import verifyCredentials from './verify-credentials.js';
|
||||
import isStillVerified from './is-still-verified.js';
|
||||
|
||||
export default {
|
||||
fields: [
|
||||
{
|
||||
key: 'oAuthRedirectUrl',
|
||||
label: 'OAuth Redirect URL',
|
||||
type: 'string',
|
||||
required: true,
|
||||
readOnly: true,
|
||||
value: '{WEB_APP_URL}/app/eventbrite/connections/add',
|
||||
placeholder: null,
|
||||
description:
|
||||
'When asked to input a redirect URL in Eventbrite, enter the URL above.',
|
||||
clickToCopy: true,
|
||||
},
|
||||
{
|
||||
key: 'clientId',
|
||||
label: 'API Key',
|
||||
type: 'string',
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
{
|
||||
key: 'clientSecret',
|
||||
label: 'Client Secret',
|
||||
type: 'string',
|
||||
required: true,
|
||||
readOnly: false,
|
||||
value: null,
|
||||
placeholder: null,
|
||||
description: null,
|
||||
clickToCopy: false,
|
||||
},
|
||||
],
|
||||
|
||||
generateAuthUrl,
|
||||
verifyCredentials,
|
||||
isStillVerified,
|
||||
};
|
@@ -0,0 +1,8 @@
|
||||
import getCurrentUser from '../common/get-current-user.js';
|
||||
|
||||
const isStillVerified = async ($) => {
|
||||
const currentUser = await getCurrentUser($);
|
||||
return !!currentUser.id;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,42 @@
|
||||
import getCurrentUser from '../common/get-current-user.js';
|
||||
|
||||
const verifyCredentials = async ($) => {
|
||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
||||
(field) => field.key == 'oAuthRedirectUrl'
|
||||
);
|
||||
const redirectUri = oauthRedirectUrlField.value;
|
||||
const { data } = await $.http.post(
|
||||
'https://www.eventbrite.com/oauth/token',
|
||||
{
|
||||
grant_type: 'authorization_code',
|
||||
client_id: $.auth.data.clientId,
|
||||
client_secret: $.auth.data.clientSecret,
|
||||
code: $.auth.data.code,
|
||||
redirect_uri: redirectUri,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
await $.auth.set({
|
||||
accessToken: data.access_token,
|
||||
tokenType: data.token_type,
|
||||
});
|
||||
|
||||
const currentUser = await getCurrentUser($);
|
||||
|
||||
const screenName = [currentUser.name, currentUser.emails[0].email]
|
||||
.filter(Boolean)
|
||||
.join(' @ ');
|
||||
|
||||
await $.auth.set({
|
||||
clientId: $.auth.data.clientId,
|
||||
clientSecret: $.auth.data.clientSecret,
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
export default verifyCredentials;
|
@@ -0,0 +1,9 @@
|
||||
const addAuthHeader = ($, requestConfig) => {
|
||||
if ($.auth.data?.accessToken) {
|
||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
|
||||
}
|
||||
|
||||
return requestConfig;
|
||||
};
|
||||
|
||||
export default addAuthHeader;
|
@@ -0,0 +1,6 @@
|
||||
const getCurrentUser = async ($) => {
|
||||
const { data: currentUser } = await $.http.get('/v3/users/me');
|
||||
return currentUser;
|
||||
};
|
||||
|
||||
export default getCurrentUser;
|
@@ -0,0 +1,4 @@
|
||||
import listEvents from './list-events/index.js';
|
||||
import listOrganizations from './list-organizations/index.js';
|
||||
|
||||
export default [listEvents, listOrganizations];
|
@@ -0,0 +1,44 @@
|
||||
export default {
|
||||
name: 'List events',
|
||||
key: 'listEvents',
|
||||
|
||||
async run($) {
|
||||
const events = {
|
||||
data: [],
|
||||
};
|
||||
const organizationId = $.step.parameters.organizationId;
|
||||
|
||||
if (!organizationId) {
|
||||
return events;
|
||||
}
|
||||
|
||||
const params = {
|
||||
continuation: undefined,
|
||||
order_by: 'created_desc',
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get(
|
||||
`/v3/organizations/${organizationId}/events/`,
|
||||
{
|
||||
params,
|
||||
}
|
||||
);
|
||||
|
||||
if (data.pagination.has_more_items) {
|
||||
params.continuation = data.pagination.continuation;
|
||||
}
|
||||
|
||||
if (data.events) {
|
||||
for (const event of data.events) {
|
||||
events.data.push({
|
||||
value: event.id,
|
||||
name: `${event.name.text} (${event.status})`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.continuation);
|
||||
|
||||
return events;
|
||||
},
|
||||
};
|
@@ -0,0 +1,35 @@
|
||||
export default {
|
||||
name: 'List organizations',
|
||||
key: 'listOrganizations',
|
||||
|
||||
async run($) {
|
||||
const organizations = {
|
||||
data: [],
|
||||
};
|
||||
|
||||
const params = {
|
||||
continuation: undefined,
|
||||
};
|
||||
|
||||
do {
|
||||
const { data } = await $.http.get('/v3/users/me/organizations', {
|
||||
params,
|
||||
});
|
||||
|
||||
if (data.pagination.has_more_items) {
|
||||
params.continuation = data.pagination.continuation;
|
||||
}
|
||||
|
||||
if (data.organizations) {
|
||||
for (const organization of data.organizations) {
|
||||
organizations.data.push({
|
||||
value: organization.id,
|
||||
name: organization.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (params.continuation);
|
||||
|
||||
return organizations;
|
||||
},
|
||||
};
|
20
packages/backend/src/apps/eventbrite/index.js
Normal file
20
packages/backend/src/apps/eventbrite/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import defineApp from '../../helpers/define-app.js';
|
||||
import addAuthHeader from './common/add-auth-header.js';
|
||||
import auth from './auth/index.js';
|
||||
import dynamicData from './dynamic-data/index.js';
|
||||
import triggers from './triggers/index.js';
|
||||
|
||||
export default defineApp({
|
||||
name: 'Eventbrite',
|
||||
key: 'eventbrite',
|
||||
baseUrl: 'https://www.eventbrite.com',
|
||||
apiBaseUrl: 'https://www.eventbriteapi.com',
|
||||
iconUrl: '{BASE_URL}/apps/eventbrite/assets/favicon.svg',
|
||||
authDocUrl: '{DOCS_URL}/apps/eventbrite/connection',
|
||||
primaryColor: 'F05537',
|
||||
supportsConnections: true,
|
||||
beforeRequest: [addAuthHeader],
|
||||
auth,
|
||||
dynamicData,
|
||||
triggers,
|
||||
});
|
4
packages/backend/src/apps/eventbrite/triggers/index.js
Normal file
4
packages/backend/src/apps/eventbrite/triggers/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import newAttendeeCheckIn from './new-attendee-check-in/index.js';
|
||||
import newEvents from './new-events/index.js';
|
||||
|
||||
export default [newAttendeeCheckIn, newEvents];
|
@@ -0,0 +1,120 @@
|
||||
import Crypto from 'crypto';
|
||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New attendee check in',
|
||||
key: 'newAttendeeCheckIn',
|
||||
type: 'webhook',
|
||||
description: "Triggers when an attendee's barcode is scanned in.",
|
||||
arguments: [
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organizationId',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: '',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listOrganizations',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Event',
|
||||
key: 'eventId',
|
||||
type: 'dropdown',
|
||||
required: false,
|
||||
description: '',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listEvents',
|
||||
},
|
||||
{
|
||||
name: 'parameters.organizationId',
|
||||
value: '{parameters.organizationId}',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const dataItem = {
|
||||
raw: $.request.body,
|
||||
meta: {
|
||||
internalId: Crypto.randomUUID(),
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async testRun($) {
|
||||
const eventId = $.step.parameters.eventId;
|
||||
const organizationId = $.step.parameters.organizationId;
|
||||
|
||||
const params = {
|
||||
event_id: eventId,
|
||||
};
|
||||
|
||||
const {
|
||||
data: { orders },
|
||||
} = await $.http.get(`/v3/events/${eventId}/orders/`, params);
|
||||
|
||||
if (orders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const computedWebhookEvent = {
|
||||
config: {
|
||||
action: 'barcode.checked_in',
|
||||
user_id: organizationId,
|
||||
webhook_id: '11111111',
|
||||
endpoint_url: $.webhookUrl,
|
||||
},
|
||||
api_url: orders[0].resource_uri,
|
||||
};
|
||||
|
||||
const dataItem = {
|
||||
raw: computedWebhookEvent,
|
||||
meta: {
|
||||
internalId: computedWebhookEvent.user_id,
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async registerHook($) {
|
||||
const organizationId = $.step.parameters.organizationId;
|
||||
const eventId = $.step.parameters.eventId;
|
||||
|
||||
const payload = {
|
||||
endpoint_url: $.webhookUrl,
|
||||
actions: 'attendee.checked_in',
|
||||
event_id: eventId,
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(
|
||||
`/v3/organizations/${organizationId}/webhooks/`,
|
||||
payload
|
||||
);
|
||||
|
||||
await $.flow.setRemoteWebhookId(data.id);
|
||||
},
|
||||
|
||||
async unregisterHook($) {
|
||||
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
|
||||
},
|
||||
});
|
@@ -0,0 +1,98 @@
|
||||
import Crypto from 'crypto';
|
||||
import defineTrigger from '../../../../helpers/define-trigger.js';
|
||||
|
||||
export default defineTrigger({
|
||||
name: 'New events',
|
||||
key: 'newEvents',
|
||||
type: 'webhook',
|
||||
description:
|
||||
'Triggers when a new event is published and live within an organization.',
|
||||
arguments: [
|
||||
{
|
||||
label: 'Organization',
|
||||
key: 'organizationId',
|
||||
type: 'dropdown',
|
||||
required: true,
|
||||
description: '',
|
||||
variables: true,
|
||||
source: {
|
||||
type: 'query',
|
||||
name: 'getDynamicData',
|
||||
arguments: [
|
||||
{
|
||||
name: 'key',
|
||||
value: 'listOrganizations',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
async run($) {
|
||||
const dataItem = {
|
||||
raw: $.request.body,
|
||||
meta: {
|
||||
internalId: Crypto.randomUUID(),
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async testRun($) {
|
||||
const organizationId = $.step.parameters.organizationId;
|
||||
|
||||
const params = {
|
||||
orderBy: 'created_desc',
|
||||
status: 'all',
|
||||
};
|
||||
|
||||
const {
|
||||
data: { events },
|
||||
} = await $.http.get(`/v3/organizations/${organizationId}/events/`, params);
|
||||
|
||||
if (events.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const computedWebhookEvent = {
|
||||
config: {
|
||||
action: 'event.published',
|
||||
user_id: events[0].organization_id,
|
||||
webhook_id: '11111111',
|
||||
endpoint_url: $.webhookUrl,
|
||||
},
|
||||
api_url: events[0].resource_uri,
|
||||
};
|
||||
|
||||
const dataItem = {
|
||||
raw: computedWebhookEvent,
|
||||
meta: {
|
||||
internalId: computedWebhookEvent.user_id,
|
||||
},
|
||||
};
|
||||
|
||||
$.pushTriggerItem(dataItem);
|
||||
},
|
||||
|
||||
async registerHook($) {
|
||||
const organizationId = $.step.parameters.organizationId;
|
||||
|
||||
const payload = {
|
||||
endpoint_url: $.webhookUrl,
|
||||
actions: 'event.published',
|
||||
event_id: '',
|
||||
};
|
||||
|
||||
const { data } = await $.http.post(
|
||||
`/v3/organizations/${organizationId}/webhooks/`,
|
||||
payload
|
||||
);
|
||||
|
||||
await $.flow.setRemoteWebhookId(data.id);
|
||||
},
|
||||
|
||||
async unregisterHook($) {
|
||||
await $.http.delete(`/v3/webhooks/${$.flow.remoteWebhookId}/`);
|
||||
},
|
||||
});
|
@@ -52,7 +52,7 @@ const appConfig = {
|
||||
isDev: appEnv === 'development',
|
||||
isTest: appEnv === 'test',
|
||||
isProd: appEnv === 'production',
|
||||
version: '0.11.0',
|
||||
version: '0.12.0',
|
||||
postgresDatabase: process.env.POSTGRES_DATABASE || 'automatisch_development',
|
||||
postgresSchema: process.env.POSTGRES_SCHEMA || 'public',
|
||||
postgresPort: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
|
@@ -10,7 +10,7 @@ describe('GET /api/v1/automatisch/version', () => {
|
||||
|
||||
const expectedPayload = {
|
||||
data: {
|
||||
version: '0.11.0',
|
||||
version: '0.12.0',
|
||||
},
|
||||
meta: {
|
||||
count: 1,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
export async function up(knex) {
|
||||
return knex.schema.alterTable('datastore', (table) => {
|
||||
table.text('value').alter();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex) {
|
||||
return knex.schema.alterTable('datastore', (table) => {
|
||||
table.string('value').alter();
|
||||
});
|
||||
}
|
@@ -113,6 +113,15 @@ export default defineConfig({
|
||||
{ text: 'Connection', link: '/apps/dropbox/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Eventbrite',
|
||||
collapsible: true,
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: 'Triggers', link: '/apps/eventbrite/triggers' },
|
||||
{ text: 'Connection', link: '/apps/eventbrite/connection' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Filter',
|
||||
collapsible: true,
|
||||
|
18
packages/docs/pages/apps/eventbrite/connection.md
Normal file
18
packages/docs/pages/apps/eventbrite/connection.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Eventbrite
|
||||
|
||||
:::info
|
||||
This page explains the steps you need to follow to set up the Eventbrite
|
||||
connection in Automatisch. If any of the steps are outdated, please let us know!
|
||||
:::
|
||||
|
||||
1. Go to your Eventbrite account settings.
|
||||
2. Click on the **Developer Links**, and click on the **API Keys** button.
|
||||
3. Click on the **Create API Key** button.
|
||||
4. Fill the form.
|
||||
5. Copy **OAuth Redirect URL** from Automatisch to **OAuth Redirect URI** field in the form.
|
||||
6. After filling the form, click on the **Create Key** button.
|
||||
7. Click on the **Show API key, client secret and tokens** in the middle of the page.
|
||||
8. Copy the **API Key** value to the `API Key` field on Automatisch.
|
||||
9. Copy the **Client secret** value to the `Client Secret` field on Automatisch.
|
||||
10. Click **Submit** button on Automatisch.
|
||||
11. Congrats! Start using your new Eventbrite connection within the flows.
|
14
packages/docs/pages/apps/eventbrite/triggers.md
Normal file
14
packages/docs/pages/apps/eventbrite/triggers.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
favicon: /favicons/eventbrite.svg
|
||||
items:
|
||||
- name: New attendee check in
|
||||
desc: Triggers when an attendee's barcode is scanned in.
|
||||
- name: New events
|
||||
desc: Triggers when a new event is published and live within an organization.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import CustomListing from '../../components/CustomListing.vue'
|
||||
</script>
|
||||
|
||||
<CustomListing />
|
@@ -11,6 +11,7 @@ The following integrations are currently supported by Automatisch.
|
||||
- [Discord](/apps/discord/actions)
|
||||
- [Disqus](/apps/disqus/triggers)
|
||||
- [Dropbox](/apps/dropbox/actions)
|
||||
- [Eventbrite](/apps/eventbrite/triggers)
|
||||
- [Filter](/apps/filter/actions)
|
||||
- [Flickr](/apps/flickr/triggers)
|
||||
- [Formatter](/apps/formatter/actions)
|
||||
|
7
packages/docs/pages/public/favicons/eventbrite.svg
Normal file
7
packages/docs/pages/public/favicons/eventbrite.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<circle fill="#F05537" cx="128" cy="128" r="128"></circle>
|
||||
<path d="M117.475323,82.7290398 C136.772428,78.4407943 156.069532,86.3025777 166.790146,101.311437 L81.5017079,120.608542 C84.3605382,102.26438 98.1782181,87.0172853 117.475323,82.7290398 Z M167.266618,153.48509 C160.596014,163.252761 150.351872,170.161601 138.678314,172.782195 C119.38121,177.070441 99.8458692,169.208657 89.1252554,153.961562 L174.651929,134.664457 L188.469609,131.567391 L215.152026,125.611495 C214.91379,119.893834 214.199082,114.176173 213.007903,108.696749 C202.287289,62.7172275 155.354825,33.8906884 108.42236,44.6113021 C61.4898956,55.3319159 32.1868848,101.073201 43.1457344,147.290958 C54.1045839,193.508715 100.798813,222.097018 147.731277,211.376404 C175.366637,205.182272 196.807864,186.599875 207.766714,163.014525 L167.266618,153.48509 L167.266618,153.48509 Z" fill="#FFFFFF" fill-rule="nonzero"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@@ -7,6 +7,7 @@
|
||||
"@apollo/client": "^3.6.9",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@casl/react": "^3.1.0",
|
||||
"@dagrejs/dagre": "^1.1.2",
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@hookform/resolvers": "^2.8.8",
|
||||
@@ -32,6 +33,7 @@
|
||||
"react-router-dom": "^6.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-window": "^1.8.9",
|
||||
"reactflow": "^11.11.2",
|
||||
"slate": "^0.94.1",
|
||||
"slate-history": "^0.93.0",
|
||||
"slate-react": "^0.94.2",
|
||||
|
@@ -36,7 +36,7 @@ function AdminApplicationSettings(props) {
|
||||
|
||||
const handleSubmit = async (values) => {
|
||||
try {
|
||||
if (!appConfig.data) {
|
||||
if (!appConfig?.data) {
|
||||
await createAppConfig({
|
||||
variables: {
|
||||
input: { key: props.appKey, ...values },
|
||||
@@ -69,6 +69,7 @@ function AdminApplicationSettings(props) {
|
||||
}),
|
||||
[appConfig?.data],
|
||||
);
|
||||
|
||||
return (
|
||||
<Form
|
||||
defaultValues={defaultValues}
|
||||
|
@@ -165,6 +165,7 @@ function ChooseAppAndEventSubstep(props) {
|
||||
value={getOption(appOptions, step.appKey) || null}
|
||||
onChange={onAppChange}
|
||||
data-test="choose-app-autocomplete"
|
||||
componentsProps={{ popper: { className: 'nowheel' } }}
|
||||
/>
|
||||
|
||||
{step.appKey && (
|
||||
@@ -227,6 +228,7 @@ function ChooseAppAndEventSubstep(props) {
|
||||
value={getOption(actionOrTriggerOptions, step.key) || null}
|
||||
onChange={onEventChange}
|
||||
data-test="choose-event-autocomplete"
|
||||
componentsProps={{ popper: { className: 'nowheel' } }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@@ -240,6 +240,7 @@ function ChooseConnectionSubstep(props) {
|
||||
onChange={handleChange}
|
||||
loading={isAppConnectionsLoading}
|
||||
data-test="choose-connection-autocomplete"
|
||||
componentsProps={{ popper: { className: 'nowheel' } }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
|
@@ -32,9 +32,11 @@ function ControlledAutocomplete(props) {
|
||||
...autocompleteProps
|
||||
} = props;
|
||||
let dependsOnValues = [];
|
||||
|
||||
if (dependsOn?.length) {
|
||||
dependsOnValues = watch(dependsOn);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const hasDependencies = dependsOnValues.length;
|
||||
const allDepsSatisfied = dependsOnValues.every(Boolean);
|
||||
@@ -44,6 +46,7 @@ function ControlledAutocomplete(props) {
|
||||
resetField(name);
|
||||
}
|
||||
}, dependsOnValues);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
rules={{ required }}
|
||||
|
@@ -47,6 +47,7 @@ const CustomOptions = (props) => {
|
||||
},
|
||||
},
|
||||
]}
|
||||
className="nowheel"
|
||||
>
|
||||
<Paper elevation={5} sx={{ width: '100%' }}>
|
||||
<Tabs
|
||||
|
@@ -8,6 +8,7 @@ import Tooltip from '@mui/material/Tooltip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew';
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
import { EditorProvider } from 'contexts/Editor';
|
||||
import EditableTypography from 'components/EditableTypography';
|
||||
@@ -20,6 +21,9 @@ import * as URLS from 'config/urls';
|
||||
import { TopBar } from './style';
|
||||
import useFlow from 'hooks/useFlow';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import EditorNew from 'components/EditorNew/EditorNew';
|
||||
|
||||
const useNewFlowEditor = process.env.REACT_APP_USE_NEW_FLOW_EDITOR === 'true';
|
||||
|
||||
export default function EditorLayout() {
|
||||
const { flowId } = useParams();
|
||||
@@ -131,15 +135,28 @@ export default function EditorLayout() {
|
||||
</Button>
|
||||
</Box>
|
||||
</TopBar>
|
||||
<Stack direction="column" height="100%">
|
||||
<Container maxWidth="md">
|
||||
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
||||
{!flow && !isFlowLoading && 'not found'}
|
||||
|
||||
{flow && <Editor flow={flow} />}
|
||||
</EditorProvider>
|
||||
</Container>
|
||||
</Stack>
|
||||
{useNewFlowEditor ? (
|
||||
<Stack direction="column" height="100%" flexGrow={1}>
|
||||
<Stack direction="column" flexGrow={1}>
|
||||
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
||||
<ReactFlowProvider>
|
||||
{!flow && !isFlowLoading && 'not found'}
|
||||
{flow && <EditorNew flow={flow} />}
|
||||
</ReactFlowProvider>
|
||||
</EditorProvider>
|
||||
</Stack>
|
||||
</Stack>
|
||||
) : (
|
||||
<Stack direction="column" height="100%">
|
||||
<Container maxWidth="md">
|
||||
<EditorProvider value={{ readOnly: !!flow?.active }}>
|
||||
{!flow && !isFlowLoading && 'not found'}
|
||||
{flow && <Editor flow={flow} />}
|
||||
</EditorProvider>
|
||||
</Container>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<Snackbar
|
||||
data-test="flow-cannot-edit-info-snackbar"
|
||||
|
79
packages/web/src/components/EditorNew/Edge/Edge.jsx
Normal file
79
packages/web/src/components/EditorNew/Edge/Edge.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { EdgeLabelRenderer, getStraightPath } from 'reactflow';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { CREATE_STEP } from 'graphql/mutations/create-step';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function Edge({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
source,
|
||||
data: { flowId, setCurrentStepId, flowActive, layouted },
|
||||
}) {
|
||||
const [createStep, { loading: creationInProgress }] =
|
||||
useMutation(CREATE_STEP);
|
||||
const queryClient = useQueryClient();
|
||||
const [edgePath, labelX, labelY] = getStraightPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
targetY,
|
||||
});
|
||||
|
||||
const addStep = async (previousStepId) => {
|
||||
const mutationInput = {
|
||||
previousStep: {
|
||||
id: previousStepId,
|
||||
},
|
||||
flow: {
|
||||
id: flowId,
|
||||
},
|
||||
};
|
||||
|
||||
const createdStep = await createStep({
|
||||
variables: { input: mutationInput },
|
||||
});
|
||||
|
||||
const createdStepId = createdStep.data.createStep.id;
|
||||
setCurrentStepId(createdStepId);
|
||||
await queryClient.invalidateQueries({ queryKey: ['flows', flowId] });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EdgeLabelRenderer>
|
||||
<IconButton
|
||||
onClick={() => addStep(source)}
|
||||
color="primary"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
visibility: layouted ? 'visible' : 'hidden',
|
||||
}}
|
||||
disabled={creationInProgress || flowActive}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</EdgeLabelRenderer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Edge.propTypes = {
|
||||
sourceX: PropTypes.number.isRequired,
|
||||
sourceY: PropTypes.number.isRequired,
|
||||
targetX: PropTypes.number.isRequired,
|
||||
targetY: PropTypes.number.isRequired,
|
||||
source: PropTypes.string.isRequired,
|
||||
data: PropTypes.shape({
|
||||
flowId: PropTypes.string.isRequired,
|
||||
setCurrentStepId: PropTypes.func.isRequired,
|
||||
flowActive: PropTypes.bool.isRequired,
|
||||
layouted: PropTypes.bool,
|
||||
}).isRequired,
|
||||
};
|
258
packages/web/src/components/EditorNew/EditorNew.jsx
Normal file
258
packages/web/src/components/EditorNew/EditorNew.jsx
Normal file
@@ -0,0 +1,258 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { FlowPropType } from 'propTypes/propTypes';
|
||||
import ReactFlow, { useNodesState, useEdgesState, addEdge } from 'reactflow';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { UPDATE_STEP } from 'graphql/mutations/update-step';
|
||||
|
||||
import { useAutoLayout } from './useAutoLayout';
|
||||
import { useScrollBoundries } from './useScrollBoundries';
|
||||
import FlowStepNode from './FlowStepNode/FlowStepNode';
|
||||
import Edge from './Edge/Edge';
|
||||
import InvisibleNode from './InvisibleNode/InvisibleNode';
|
||||
import { EditorWrapper } from './style';
|
||||
|
||||
const nodeTypes = { flowStep: FlowStepNode, invisible: InvisibleNode };
|
||||
|
||||
const edgeTypes = {
|
||||
addNodeEdge: Edge,
|
||||
};
|
||||
|
||||
const INVISIBLE_NODE_ID = 'invisible-node';
|
||||
|
||||
const generateEdgeId = (sourceId, targetId) => `${sourceId}-${targetId}`;
|
||||
|
||||
const EditorNew = ({ flow }) => {
|
||||
const [triggerStep] = flow.steps;
|
||||
const [currentStepId, setCurrentStepId] = useState(triggerStep.id);
|
||||
|
||||
const [updateStep] = useMutation(UPDATE_STEP);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
useAutoLayout();
|
||||
useScrollBoundries();
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params) => setEdges((eds) => addEdge(params, eds)),
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const openNextStep = useCallback(
|
||||
(nextStep) => () => {
|
||||
setCurrentStepId(nextStep?.id);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const onStepChange = useCallback(
|
||||
async (step) => {
|
||||
const mutationInput = {
|
||||
id: step.id,
|
||||
key: step.key,
|
||||
parameters: step.parameters,
|
||||
connection: {
|
||||
id: step.connection?.id,
|
||||
},
|
||||
flow: {
|
||||
id: flow.id,
|
||||
},
|
||||
};
|
||||
|
||||
if (step.appKey) {
|
||||
mutationInput.appKey = step.appKey;
|
||||
}
|
||||
|
||||
await updateStep({
|
||||
variables: { input: mutationInput },
|
||||
});
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['steps', step.id, 'connection'],
|
||||
});
|
||||
await queryClient.invalidateQueries({ queryKey: ['flows', flow.id] });
|
||||
},
|
||||
[flow.id, updateStep, queryClient],
|
||||
);
|
||||
|
||||
const generateEdges = useCallback((flow, prevEdges) => {
|
||||
const newEdges =
|
||||
flow.steps
|
||||
.map((step, i) => {
|
||||
const sourceId = step.id;
|
||||
const targetId = flow.steps[i + 1]?.id;
|
||||
const edge = prevEdges?.find(
|
||||
(edge) => edge.id === generateEdgeId(sourceId, targetId),
|
||||
);
|
||||
if (targetId) {
|
||||
return {
|
||||
id: generateEdgeId(sourceId, targetId),
|
||||
source: sourceId,
|
||||
target: targetId,
|
||||
type: 'addNodeEdge',
|
||||
data: {
|
||||
flowId: flow.id,
|
||||
flowActive: flow.active,
|
||||
setCurrentStepId,
|
||||
layouted: !!edge,
|
||||
},
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((edge) => !!edge) || [];
|
||||
|
||||
const lastStep = flow.steps[flow.steps.length - 1];
|
||||
|
||||
return lastStep
|
||||
? [
|
||||
...newEdges,
|
||||
{
|
||||
id: generateEdgeId(lastStep.id, INVISIBLE_NODE_ID),
|
||||
source: lastStep.id,
|
||||
target: INVISIBLE_NODE_ID,
|
||||
type: 'addNodeEdge',
|
||||
data: {
|
||||
flowId: flow.id,
|
||||
flowActive: flow.active,
|
||||
setCurrentStepId,
|
||||
layouted: false,
|
||||
},
|
||||
},
|
||||
]
|
||||
: newEdges;
|
||||
}, []);
|
||||
|
||||
const generateNodes = useCallback(
|
||||
(flow, prevNodes) => {
|
||||
const newNodes = flow.steps.map((step, index) => {
|
||||
const node = prevNodes?.find(({ id }) => id === step.id);
|
||||
const collapsed = currentStepId !== step.id;
|
||||
return {
|
||||
id: step.id,
|
||||
type: 'flowStep',
|
||||
position: {
|
||||
x: node ? node.position.x : 0,
|
||||
y: node ? node.position.y : 0,
|
||||
},
|
||||
zIndex: collapsed ? 0 : 1,
|
||||
data: {
|
||||
step,
|
||||
index: index,
|
||||
flowId: flow.id,
|
||||
collapsed,
|
||||
openNextStep: openNextStep(flow.steps[index + 1]),
|
||||
onOpen: () => setCurrentStepId(step.id),
|
||||
onClose: () => setCurrentStepId(null),
|
||||
onChange: onStepChange,
|
||||
layouted: !!node,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const prevInvisibleNode = nodes.find((node) => node.type === 'invisible');
|
||||
|
||||
return [
|
||||
...newNodes,
|
||||
{
|
||||
id: INVISIBLE_NODE_ID,
|
||||
type: 'invisible',
|
||||
position: {
|
||||
x: prevInvisibleNode ? prevInvisibleNode.position.x : 0,
|
||||
y: prevInvisibleNode ? prevInvisibleNode.position.y : 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
[currentStepId, nodes, onStepChange, openNextStep],
|
||||
);
|
||||
|
||||
const updateNodesData = useCallback(
|
||||
(steps) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
const step = steps.find((step) => step.id === node.id);
|
||||
if (step) {
|
||||
return { ...node, data: { ...node.data, step: { ...step } } };
|
||||
}
|
||||
return node;
|
||||
}),
|
||||
);
|
||||
},
|
||||
[setNodes],
|
||||
);
|
||||
|
||||
const updateEdgesData = useCallback(
|
||||
(flow) => {
|
||||
setEdges((edges) =>
|
||||
edges.map((edge) => {
|
||||
return {
|
||||
...edge,
|
||||
data: { ...edge.data, flowId: flow.id, flowActive: flow.active },
|
||||
};
|
||||
}),
|
||||
);
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setNodes(
|
||||
nodes.map((node) => {
|
||||
if (node.type === 'flowStep') {
|
||||
const collapsed = currentStepId !== node.data.step.id;
|
||||
return {
|
||||
...node,
|
||||
zIndex: collapsed ? 0 : 1,
|
||||
data: {
|
||||
...node.data,
|
||||
collapsed,
|
||||
},
|
||||
};
|
||||
}
|
||||
return node;
|
||||
}),
|
||||
);
|
||||
}, [currentStepId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (flow.steps.length + 1 !== nodes.length) {
|
||||
const newNodes = generateNodes(flow, nodes);
|
||||
const newEdges = generateEdges(flow, edges);
|
||||
|
||||
setNodes(newNodes);
|
||||
setEdges(newEdges);
|
||||
} else {
|
||||
updateNodesData(flow.steps);
|
||||
updateEdgesData(flow);
|
||||
}
|
||||
}, [flow]);
|
||||
|
||||
return (
|
||||
<EditorWrapper direction="column">
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
panOnScroll
|
||||
panOnScrollMode="vertical"
|
||||
panOnDrag={false}
|
||||
zoomOnScroll={false}
|
||||
zoomOnPinch={false}
|
||||
zoomOnDoubleClick={false}
|
||||
panActivationKeyCode={null}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
/>
|
||||
</EditorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
EditorNew.propTypes = {
|
||||
flow: FlowPropType.isRequired,
|
||||
};
|
||||
|
||||
export default EditorNew;
|
@@ -0,0 +1,72 @@
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { Box } from '@mui/material';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FlowStep from 'components/FlowStep';
|
||||
import { StepPropType } from 'propTypes/propTypes';
|
||||
|
||||
import { NodeWrapper, NodeInnerWrapper } from './style.js';
|
||||
|
||||
function FlowStepNode({
|
||||
data: {
|
||||
step,
|
||||
index,
|
||||
flowId,
|
||||
collapsed,
|
||||
openNextStep,
|
||||
onOpen,
|
||||
onClose,
|
||||
onChange,
|
||||
layouted,
|
||||
},
|
||||
}) {
|
||||
return (
|
||||
<NodeWrapper
|
||||
className="nodrag"
|
||||
sx={{
|
||||
visibility: layouted ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<NodeInnerWrapper>
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Top}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
<FlowStep
|
||||
step={step}
|
||||
index={index + 1}
|
||||
collapsed={collapsed}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
onChange={onChange}
|
||||
flowId={flowId}
|
||||
onContinue={openNextStep}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Bottom}
|
||||
isConnectable={false}
|
||||
style={{ visibility: 'hidden' }}
|
||||
/>
|
||||
</NodeInnerWrapper>
|
||||
</NodeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
FlowStepNode.propTypes = {
|
||||
data: PropTypes.shape({
|
||||
step: StepPropType.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
flowId: PropTypes.string.isRequired,
|
||||
collapsed: PropTypes.bool.isRequired,
|
||||
openNextStep: PropTypes.func.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
layouted: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default FlowStepNode;
|
14
packages/web/src/components/EditorNew/FlowStepNode/style.js
Normal file
14
packages/web/src/components/EditorNew/FlowStepNode/style.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const NodeWrapper = styled(Box)(({ theme }) => ({
|
||||
width: '100vw',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(0, 2.5),
|
||||
}));
|
||||
|
||||
export const NodeInnerWrapper = styled(Box)(({ theme }) => ({
|
||||
maxWidth: 900,
|
||||
flex: 1,
|
||||
}));
|
@@ -0,0 +1,19 @@
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
// This node is used for adding an edge with add node button after the last flow step node
|
||||
function InvisibleNode() {
|
||||
return (
|
||||
<Box
|
||||
maxWidth={900}
|
||||
width="100vw"
|
||||
className="nodrag"
|
||||
sx={{ visibility: 'hidden' }}
|
||||
>
|
||||
<Handle type="target" position={Position.Top} isConnectable={false} />
|
||||
Invisible node
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default InvisibleNode;
|
13
packages/web/src/components/EditorNew/style.js
Normal file
13
packages/web/src/components/EditorNew/style.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Stack } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
||||
export const EditorWrapper = styled(Stack)(({ theme }) => ({
|
||||
flexGrow: 1,
|
||||
'& > div': {
|
||||
flexGrow: 1,
|
||||
},
|
||||
|
||||
'& .react-flow__pane, & .react-flow__node': {
|
||||
cursor: 'auto !important',
|
||||
},
|
||||
}));
|
69
packages/web/src/components/EditorNew/useAutoLayout.js
Normal file
69
packages/web/src/components/EditorNew/useAutoLayout.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import Dagre from '@dagrejs/dagre';
|
||||
import { usePrevious } from 'hooks/usePrevious';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useNodesInitialized, useNodes, useReactFlow } from 'reactflow';
|
||||
|
||||
const getLayoutedElements = (nodes, edges) => {
|
||||
const graph = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
|
||||
graph.setGraph({
|
||||
rankdir: 'TB',
|
||||
marginy: 60,
|
||||
ranksep: 64,
|
||||
});
|
||||
edges.forEach((edge) => graph.setEdge(edge.source, edge.target));
|
||||
nodes.forEach((node) => graph.setNode(node.id, node));
|
||||
|
||||
Dagre.layout(graph);
|
||||
|
||||
return {
|
||||
nodes: nodes.map((node) => {
|
||||
const { x, y, width, height } = graph.node(node.id);
|
||||
return {
|
||||
...node,
|
||||
position: { x: x - width / 2, y: y - height / 2 },
|
||||
};
|
||||
}),
|
||||
edges,
|
||||
};
|
||||
};
|
||||
|
||||
export const useAutoLayout = () => {
|
||||
const nodes = useNodes();
|
||||
const prevNodes = usePrevious(nodes);
|
||||
const nodesInitialized = useNodesInitialized();
|
||||
const { getEdges, setNodes, setEdges } = useReactFlow();
|
||||
|
||||
const onLayout = useCallback(
|
||||
(nodes, edges) => {
|
||||
const layoutedElements = getLayoutedElements(nodes, edges);
|
||||
|
||||
setNodes([
|
||||
...layoutedElements.nodes.map((node) => ({
|
||||
...node,
|
||||
data: { ...node.data, layouted: true },
|
||||
})),
|
||||
]);
|
||||
setEdges([
|
||||
...layoutedElements.edges.map((edge) => ({
|
||||
...edge,
|
||||
data: { ...edge.data, layouted: true },
|
||||
})),
|
||||
]);
|
||||
},
|
||||
[setEdges, setNodes],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const shouldAutoLayout =
|
||||
nodesInitialized &&
|
||||
!isEqual(
|
||||
nodes.map(({ width, height }) => ({ width, height })),
|
||||
prevNodes.map(({ width, height }) => ({ width, height })),
|
||||
);
|
||||
|
||||
if (shouldAutoLayout) {
|
||||
onLayout(nodes, getEdges());
|
||||
}
|
||||
}, [nodes]);
|
||||
};
|
13
packages/web/src/components/EditorNew/useScrollBoundries.js
Normal file
13
packages/web/src/components/EditorNew/useScrollBoundries.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useViewport, useReactFlow } from 'reactflow';
|
||||
|
||||
export const useScrollBoundries = () => {
|
||||
const { setViewport } = useReactFlow();
|
||||
const { x, y, zoom } = useViewport();
|
||||
|
||||
useEffect(() => {
|
||||
if (y > 0) {
|
||||
setViewport({ x, y: 0, zoom });
|
||||
}
|
||||
}, [y]);
|
||||
};
|
@@ -28,9 +28,12 @@ function ContextMenu(props) {
|
||||
variables: { input: { id: flowId } },
|
||||
});
|
||||
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['apps', appKey, 'flows'],
|
||||
});
|
||||
if (appKey) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['apps', appKey, 'flows'],
|
||||
});
|
||||
}
|
||||
|
||||
enqueueSnackbar(formatMessage('flow.successfullyDuplicated'), {
|
||||
variant: 'success',
|
||||
SnackbarProps: {
|
||||
@@ -56,9 +59,12 @@ function ContextMenu(props) {
|
||||
},
|
||||
});
|
||||
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['apps', appKey, 'flows'],
|
||||
});
|
||||
if (appKey) {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: ['apps', appKey, 'flows'],
|
||||
});
|
||||
}
|
||||
|
||||
enqueueSnackbar(formatMessage('flow.successfullyDeleted'), {
|
||||
variant: 'success',
|
||||
});
|
||||
@@ -110,7 +116,7 @@ ContextMenu.propTypes = {
|
||||
]).isRequired,
|
||||
onDeleteFlow: PropTypes.func,
|
||||
onDuplicateFlow: PropTypes.func,
|
||||
appKey: PropTypes.string.isRequired,
|
||||
appKey: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
|
@@ -38,20 +38,24 @@ function FlowRow(props) {
|
||||
const contextButtonRef = React.useRef(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const { flow, onDuplicateFlow, onDeleteFlow, appKey } = props;
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const onContextMenuClick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.nativeEvent.stopImmediatePropagation();
|
||||
setAnchorEl(contextButtonRef.current);
|
||||
};
|
||||
|
||||
const createdAt = DateTime.fromMillis(parseInt(flow.createdAt, 10));
|
||||
const updatedAt = DateTime.fromMillis(parseInt(flow.updatedAt, 10));
|
||||
const isUpdated = updatedAt > createdAt;
|
||||
const relativeCreatedAt = createdAt.toRelative();
|
||||
const relativeUpdatedAt = updatedAt.toRelative();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card sx={{ mb: 1 }} data-test="flow-row">
|
||||
@@ -127,7 +131,7 @@ FlowRow.propTypes = {
|
||||
flow: FlowPropType.isRequired,
|
||||
onDeleteFlow: PropTypes.func,
|
||||
onDuplicateFlow: PropTypes.func,
|
||||
appKey: PropTypes.string.isRequired,
|
||||
appKey: PropTypes.string,
|
||||
};
|
||||
|
||||
export default FlowRow;
|
||||
|
@@ -80,6 +80,7 @@ export default function InputCreator(props) {
|
||||
disabled={disabled}
|
||||
showOptionValue={showOptionValue}
|
||||
shouldUnregister={shouldUnregister}
|
||||
componentsProps={{ popper: { className: 'nowheel' } }}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import { StepExecutionsContext } from 'contexts/StepExecutions';
|
||||
import Popper from './Popper';
|
||||
import { processStepWithExecutions } from './data';
|
||||
import { ChildrenWrapper, FakeInput, InputLabelWrapper } from './style';
|
||||
|
||||
const PowerInput = (props) => {
|
||||
const { control } = useFormContext();
|
||||
const {
|
||||
@@ -31,33 +32,41 @@ const PowerInput = (props) => {
|
||||
} = props;
|
||||
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
||||
const editorRef = React.useRef(null);
|
||||
|
||||
const renderElement = React.useCallback(
|
||||
(props) => <Element {...props} />,
|
||||
[],
|
||||
);
|
||||
|
||||
const [editor] = React.useState(() => customizeEditor(createEditor()));
|
||||
|
||||
const [showVariableSuggestions, setShowVariableSuggestions] =
|
||||
React.useState(false);
|
||||
|
||||
const disappearSuggestionsOnShift = (event) => {
|
||||
if (event.code === 'Tab') {
|
||||
setShowVariableSuggestions(false);
|
||||
}
|
||||
};
|
||||
|
||||
const stepsWithVariables = React.useMemo(() => {
|
||||
return processStepWithExecutions(priorStepsWithExecutions);
|
||||
}, [priorStepsWithExecutions]);
|
||||
|
||||
const handleBlur = React.useCallback(
|
||||
(value) => {
|
||||
onBlur?.(value);
|
||||
},
|
||||
[onBlur],
|
||||
);
|
||||
|
||||
const handleVariableSuggestionClick = React.useCallback(
|
||||
(variable) => {
|
||||
insertVariable(editor, variable, stepsWithVariables);
|
||||
},
|
||||
[stepsWithVariables],
|
||||
);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
rules={{ required }}
|
||||
@@ -127,6 +136,7 @@ const PowerInput = (props) => {
|
||||
anchorEl={editorRef.current}
|
||||
data={stepsWithVariables}
|
||||
onSuggestionClick={handleVariableSuggestionClick}
|
||||
className="nowheel"
|
||||
/>
|
||||
|
||||
<FormHelperText variant="outlined">{description}</FormHelperText>
|
||||
|
9
packages/web/src/hooks/usePrevious.js
Normal file
9
packages/web/src/hooks/usePrevious.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export const usePrevious = (value) => {
|
||||
const ref = useRef();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
};
|
@@ -3,7 +3,7 @@ services:
|
||||
name: automatisch-main
|
||||
env: docker
|
||||
dockerfilePath: ./docker/Dockerfile
|
||||
dockerContext: ./docker
|
||||
dockerContext: .
|
||||
repo: https://github.com/automatisch/automatisch
|
||||
autoDeploy: false
|
||||
envVars:
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
name: automatisch-worker
|
||||
env: docker
|
||||
dockerfilePath: ./docker/Dockerfile
|
||||
dockerContext: ./docker
|
||||
dockerContext: .
|
||||
repo: https://github.com/automatisch/automatisch
|
||||
autoDeploy: false
|
||||
envVars:
|
||||
|
384
yarn.lock
384
yarn.lock
@@ -1455,6 +1455,18 @@
|
||||
enabled "2.0.x"
|
||||
kuler "^2.0.0"
|
||||
|
||||
"@dagrejs/dagre@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@dagrejs/dagre/-/dagre-1.1.2.tgz#5ec339979447091f48d2144deed8c70dfadae374"
|
||||
integrity sha512-F09dphqvHsbe/6C2t2unbmpr5q41BNPEfJCdn8Z7aEBpVSy/zFQ/b4SWsweQjWNsYMDvE2ffNUN8X0CeFsEGNw==
|
||||
dependencies:
|
||||
"@dagrejs/graphlib" "2.2.2"
|
||||
|
||||
"@dagrejs/graphlib@2.2.2":
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@dagrejs/graphlib/-/graphlib-2.2.2.tgz#74154d5cb880a23b4fae71034a09b4b5aef06feb"
|
||||
integrity sha512-CbyGpCDKsiTg/wuk79S7Muoj8mghDGAESWGxcSyhHX5jD35vYMBZochYVFzlHxynpE9unpu6O+4ZuhrLxASsOg==
|
||||
|
||||
"@docsearch/css@3.2.1", "@docsearch/css@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.npmjs.org/@docsearch/css/-/css-3.2.1.tgz"
|
||||
@@ -3333,6 +3345,72 @@
|
||||
resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz"
|
||||
integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==
|
||||
|
||||
"@reactflow/background@11.3.12":
|
||||
version "11.3.12"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/background/-/background-11.3.12.tgz#9c9491cce4659bae13074fcdb48ac25664879d3f"
|
||||
integrity sha512-jBuWVb43JQy5h4WOS7G0PU8voGTEJNA+qDmx8/jyBtrjbasTesLNfQvboTGjnQYYiJco6mw5vrtQItAJDNoIqw==
|
||||
dependencies:
|
||||
"@reactflow/core" "11.11.2"
|
||||
classcat "^5.0.3"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@reactflow/controls@11.2.12":
|
||||
version "11.2.12"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/controls/-/controls-11.2.12.tgz#85e2aa5de17e2af28a5ecf6a75bb9c828a20640b"
|
||||
integrity sha512-L9F3+avFRShoprdT+5oOijm5gVsz2rqWCXBzOAgD923L1XFGIspdiHLLf8IlPGsT+mfl0GxbptZhaEeEzl1e3g==
|
||||
dependencies:
|
||||
"@reactflow/core" "11.11.2"
|
||||
classcat "^5.0.3"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@reactflow/core@11.11.2":
|
||||
version "11.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/core/-/core-11.11.2.tgz#c62f78297bda9d2e86a12228617ec3f91fbd4b22"
|
||||
integrity sha512-+GfgyskweL1PsgRSguUwfrT2eDotlFgaKfDLm7x0brdzzPJY2qbCzVetaxedaiJmIli3817iYbILvE9qLKwbRA==
|
||||
dependencies:
|
||||
"@types/d3" "^7.4.0"
|
||||
"@types/d3-drag" "^3.0.1"
|
||||
"@types/d3-selection" "^3.0.3"
|
||||
"@types/d3-zoom" "^3.0.1"
|
||||
classcat "^5.0.3"
|
||||
d3-drag "^3.0.0"
|
||||
d3-selection "^3.0.0"
|
||||
d3-zoom "^3.0.0"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@reactflow/minimap@11.7.12":
|
||||
version "11.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/minimap/-/minimap-11.7.12.tgz#6b2fc671ee17e37ccd3bc038ae8d2121d0ce6291"
|
||||
integrity sha512-SRDU77c2PCF54PV/MQfkz7VOW46q7V1LZNOQlXAp7dkNyAOI6R+tb9qBUtUJOvILB+TCN6pRfD9fQ+2T99bW3Q==
|
||||
dependencies:
|
||||
"@reactflow/core" "11.11.2"
|
||||
"@types/d3-selection" "^3.0.3"
|
||||
"@types/d3-zoom" "^3.0.1"
|
||||
classcat "^5.0.3"
|
||||
d3-selection "^3.0.0"
|
||||
d3-zoom "^3.0.0"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@reactflow/node-resizer@2.2.12":
|
||||
version "2.2.12"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/node-resizer/-/node-resizer-2.2.12.tgz#df82a7dfba883afea6a01a9c8210008a1ddba01f"
|
||||
integrity sha512-6LHJGuI1zHyRrZHw5gGlVLIWnvVxid9WIqw8FMFSg+oF2DuS3pAPwSoZwypy7W22/gDNl9eD1Dcl/OtFtDFQ+w==
|
||||
dependencies:
|
||||
"@reactflow/core" "11.11.2"
|
||||
classcat "^5.0.4"
|
||||
d3-drag "^3.0.0"
|
||||
d3-selection "^3.0.0"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@reactflow/node-toolbar@1.3.12":
|
||||
version "1.3.12"
|
||||
resolved "https://registry.yarnpkg.com/@reactflow/node-toolbar/-/node-toolbar-1.3.12.tgz#89e7aa9d34b6213bb5e64c344d4e2e3cb7af3163"
|
||||
integrity sha512-4kJRvNna/E3y2MZW9/80wTKwkhw4pLJiz3D5eQrD13XcmojSb1rArO9CiwyrI+rMvs5gn6NlCFB4iN1F+Q+lxQ==
|
||||
dependencies:
|
||||
"@reactflow/core" "11.11.2"
|
||||
classcat "^5.0.3"
|
||||
zustand "^4.4.1"
|
||||
|
||||
"@rollup/plugin-babel@^5.2.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz"
|
||||
@@ -3823,6 +3901,216 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/d3-array@*":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5"
|
||||
integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==
|
||||
|
||||
"@types/d3-axis@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-3.0.6.tgz#e760e5765b8188b1defa32bc8bb6062f81e4c795"
|
||||
integrity sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-brush@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-brush/-/d3-brush-3.0.6.tgz#c2f4362b045d472e1b186cdbec329ba52bdaee6c"
|
||||
integrity sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-chord@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-3.0.6.tgz#1706ca40cf7ea59a0add8f4456efff8f8775793d"
|
||||
integrity sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==
|
||||
|
||||
"@types/d3-color@*":
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2"
|
||||
integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==
|
||||
|
||||
"@types/d3-contour@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-contour/-/d3-contour-3.0.6.tgz#9ada3fa9c4d00e3a5093fed0356c7ab929604231"
|
||||
integrity sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==
|
||||
dependencies:
|
||||
"@types/d3-array" "*"
|
||||
"@types/geojson" "*"
|
||||
|
||||
"@types/d3-delaunay@*":
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz#185c1a80cc807fdda2a3fe960f7c11c4a27952e1"
|
||||
integrity sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==
|
||||
|
||||
"@types/d3-dispatch@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz#096efdf55eb97480e3f5621ff9a8da552f0961e7"
|
||||
integrity sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==
|
||||
|
||||
"@types/d3-drag@*", "@types/d3-drag@^3.0.1":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-3.0.7.tgz#b13aba8b2442b4068c9a9e6d1d82f8bcea77fc02"
|
||||
integrity sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-dsv@*":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-3.0.7.tgz#0a351f996dc99b37f4fa58b492c2d1c04e3dac17"
|
||||
integrity sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==
|
||||
|
||||
"@types/d3-ease@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b"
|
||||
integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==
|
||||
|
||||
"@types/d3-fetch@*":
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-fetch/-/d3-fetch-3.0.7.tgz#c04a2b4f23181aa376f30af0283dbc7b3b569980"
|
||||
integrity sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==
|
||||
dependencies:
|
||||
"@types/d3-dsv" "*"
|
||||
|
||||
"@types/d3-force@*":
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-3.0.9.tgz#dd96ccefba4386fe4ff36b8e4ee4e120c21fcf29"
|
||||
integrity sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==
|
||||
|
||||
"@types/d3-format@*":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-3.0.4.tgz#b1e4465644ddb3fdf3a263febb240a6cd616de90"
|
||||
integrity sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==
|
||||
|
||||
"@types/d3-geo@*":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-3.1.0.tgz#b9e56a079449174f0a2c8684a9a4df3f60522440"
|
||||
integrity sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==
|
||||
dependencies:
|
||||
"@types/geojson" "*"
|
||||
|
||||
"@types/d3-hierarchy@*":
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz#6023fb3b2d463229f2d680f9ac4b47466f71f17b"
|
||||
integrity sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==
|
||||
|
||||
"@types/d3-interpolate@*":
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c"
|
||||
integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==
|
||||
dependencies:
|
||||
"@types/d3-color" "*"
|
||||
|
||||
"@types/d3-path@*":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a"
|
||||
integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==
|
||||
|
||||
"@types/d3-polygon@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-3.0.2.tgz#dfae54a6d35d19e76ac9565bcb32a8e54693189c"
|
||||
integrity sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==
|
||||
|
||||
"@types/d3-quadtree@*":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz#d4740b0fe35b1c58b66e1488f4e7ed02952f570f"
|
||||
integrity sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==
|
||||
|
||||
"@types/d3-random@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-3.0.3.tgz#ed995c71ecb15e0cd31e22d9d5d23942e3300cfb"
|
||||
integrity sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==
|
||||
|
||||
"@types/d3-scale-chromatic@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz#fc0db9c10e789c351f4c42d96f31f2e4df8f5644"
|
||||
integrity sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==
|
||||
|
||||
"@types/d3-scale@*":
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb"
|
||||
integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==
|
||||
dependencies:
|
||||
"@types/d3-time" "*"
|
||||
|
||||
"@types/d3-selection@*", "@types/d3-selection@^3.0.3":
|
||||
version "3.0.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe"
|
||||
integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==
|
||||
|
||||
"@types/d3-shape@*":
|
||||
version "3.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72"
|
||||
integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==
|
||||
dependencies:
|
||||
"@types/d3-path" "*"
|
||||
|
||||
"@types/d3-time-format@*":
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-4.0.3.tgz#d6bc1e6b6a7db69cccfbbdd4c34b70632d9e9db2"
|
||||
integrity sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==
|
||||
|
||||
"@types/d3-time@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be"
|
||||
integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==
|
||||
|
||||
"@types/d3-timer@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70"
|
||||
integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==
|
||||
|
||||
"@types/d3-transition@*":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-3.0.8.tgz#677707f5eed5b24c66a1918cde05963021351a8f"
|
||||
integrity sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==
|
||||
dependencies:
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3-zoom@*", "@types/d3-zoom@^3.0.1":
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz#dccb32d1c56b1e1c6e0f1180d994896f038bc40b"
|
||||
integrity sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==
|
||||
dependencies:
|
||||
"@types/d3-interpolate" "*"
|
||||
"@types/d3-selection" "*"
|
||||
|
||||
"@types/d3@^7.4.0":
|
||||
version "7.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/d3/-/d3-7.4.3.tgz#d4550a85d08f4978faf0a4c36b848c61eaac07e2"
|
||||
integrity sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==
|
||||
dependencies:
|
||||
"@types/d3-array" "*"
|
||||
"@types/d3-axis" "*"
|
||||
"@types/d3-brush" "*"
|
||||
"@types/d3-chord" "*"
|
||||
"@types/d3-color" "*"
|
||||
"@types/d3-contour" "*"
|
||||
"@types/d3-delaunay" "*"
|
||||
"@types/d3-dispatch" "*"
|
||||
"@types/d3-drag" "*"
|
||||
"@types/d3-dsv" "*"
|
||||
"@types/d3-ease" "*"
|
||||
"@types/d3-fetch" "*"
|
||||
"@types/d3-force" "*"
|
||||
"@types/d3-format" "*"
|
||||
"@types/d3-geo" "*"
|
||||
"@types/d3-hierarchy" "*"
|
||||
"@types/d3-interpolate" "*"
|
||||
"@types/d3-path" "*"
|
||||
"@types/d3-polygon" "*"
|
||||
"@types/d3-quadtree" "*"
|
||||
"@types/d3-random" "*"
|
||||
"@types/d3-scale" "*"
|
||||
"@types/d3-scale-chromatic" "*"
|
||||
"@types/d3-selection" "*"
|
||||
"@types/d3-shape" "*"
|
||||
"@types/d3-time" "*"
|
||||
"@types/d3-time-format" "*"
|
||||
"@types/d3-timer" "*"
|
||||
"@types/d3-transition" "*"
|
||||
"@types/d3-zoom" "*"
|
||||
|
||||
"@types/debug@^4.1.7":
|
||||
version "4.1.8"
|
||||
resolved "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz"
|
||||
@@ -3913,6 +4201,11 @@
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/geojson@*":
|
||||
version "7946.0.14"
|
||||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.14.tgz#319b63ad6df705ee2a65a73ef042c8271e696613"
|
||||
integrity sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==
|
||||
|
||||
"@types/graceful-fs@^4.1.2":
|
||||
version "4.1.5"
|
||||
resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz"
|
||||
@@ -6044,6 +6337,11 @@ cjs-module-lexer@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz"
|
||||
integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==
|
||||
|
||||
classcat@^5.0.3, classcat@^5.0.4:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/classcat/-/classcat-5.0.5.tgz#8c209f359a93ac302404a10161b501eba9c09c77"
|
||||
integrity sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==
|
||||
|
||||
clean-css@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.npmjs.org/clean-css/-/clean-css-5.2.2.tgz"
|
||||
@@ -6829,6 +7127,68 @@ csstype@^3.1.1:
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz"
|
||||
integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
|
||||
|
||||
"d3-color@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
"d3-dispatch@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz#5fc75284e9c2375c36c839411a0cf550cbfc4d5e"
|
||||
integrity sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==
|
||||
|
||||
"d3-drag@2 - 3", d3-drag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-3.0.0.tgz#994aae9cd23c719f53b5e10e3a0a6108c69607ba"
|
||||
integrity sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-selection "3"
|
||||
|
||||
"d3-ease@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4"
|
||||
integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==
|
||||
|
||||
"d3-interpolate@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
"d3-selection@2 - 3", d3-selection@3, d3-selection@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-3.0.0.tgz#c25338207efa72cc5b9bd1458a1a41901f1e1b31"
|
||||
integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==
|
||||
|
||||
"d3-timer@1 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0"
|
||||
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||
|
||||
"d3-transition@2 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-3.0.1.tgz#6869fdde1448868077fdd5989200cb61b2a1645f"
|
||||
integrity sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
d3-dispatch "1 - 3"
|
||||
d3-ease "1 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-timer "1 - 3"
|
||||
|
||||
d3-zoom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-3.0.0.tgz#d13f4165c73217ffeaa54295cd6969b3e7aee8f3"
|
||||
integrity sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==
|
||||
dependencies:
|
||||
d3-dispatch "1 - 3"
|
||||
d3-drag "2 - 3"
|
||||
d3-interpolate "1 - 3"
|
||||
d3-selection "2 - 3"
|
||||
d3-transition "2 - 3"
|
||||
|
||||
damerau-levenshtein@^1.0.7:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz"
|
||||
@@ -13809,6 +14169,18 @@ react@^18.2.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
reactflow@^11.11.2:
|
||||
version "11.11.2"
|
||||
resolved "https://registry.yarnpkg.com/reactflow/-/reactflow-11.11.2.tgz#4968866a9372e6004ad1e424a2141996f0ba769a"
|
||||
integrity sha512-o1fT3stSdhzW+SedCGNSmEvZvULZygZIMLyW67NcWNZrgwx1wuJfzLg5fuQ0Nzf389wItumZX/zP3zdaPX7lEw==
|
||||
dependencies:
|
||||
"@reactflow/background" "11.3.12"
|
||||
"@reactflow/controls" "11.2.12"
|
||||
"@reactflow/core" "11.11.2"
|
||||
"@reactflow/minimap" "11.7.12"
|
||||
"@reactflow/node-resizer" "2.2.12"
|
||||
"@reactflow/node-toolbar" "1.3.12"
|
||||
|
||||
read-cmd-shim@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz"
|
||||
@@ -15977,6 +16349,11 @@ url-parse-lax@^3.0.0:
|
||||
dependencies:
|
||||
prepend-http "^2.0.0"
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
@@ -16948,3 +17325,10 @@ zen-observable@0.8.15:
|
||||
version "0.8.15"
|
||||
resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz"
|
||||
integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==
|
||||
|
||||
zustand@^4.4.1:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848"
|
||||
integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==
|
||||
dependencies:
|
||||
use-sync-external-store "1.2.0"
|
||||
|
Reference in New Issue
Block a user