Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c52c976aca |
+1
-2
@@ -34,8 +34,7 @@
|
||||
"@types/qs": "6.9.16",
|
||||
"@wyw-in-js/transform@npm:0.6.0": "patch:@wyw-in-js/transform@npm%3A0.7.0#~/.yarn/patches/@wyw-in-js-transform-npm-0.7.0-ba641dc99f.patch",
|
||||
"@wyw-in-js/transform@npm:0.7.0": "patch:@wyw-in-js/transform@npm%3A0.7.0#~/.yarn/patches/@wyw-in-js-transform-npm-0.7.0-ba641dc99f.patch",
|
||||
"@opentelemetry/api": "1.9.1",
|
||||
"chokidar": "^3.6.0"
|
||||
"@opentelemetry/api": "1.9.1"
|
||||
},
|
||||
"version": "0.2.1",
|
||||
"nx": {},
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"twenty-client-sdk": "2.2.0",
|
||||
"twenty-sdk": "2.2.0"
|
||||
"twenty-client-sdk": "0.9.0",
|
||||
"twenty-sdk": "0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.7.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useRecordId } from 'twenty-sdk/front-component';
|
||||
import { useSelectedRecordIds } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -72,7 +72,8 @@ const CardDisplay = ({
|
||||
};
|
||||
|
||||
const PostCardPreview = () => {
|
||||
const recordId = useRecordId();
|
||||
const selectedRecordIds = useSelectedRecordIds();
|
||||
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
|
||||
const [postCard, setPostCard] = useState<PostCardRecord | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
+3
-7
@@ -1,11 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
enqueueSnackbar,
|
||||
unmountFrontComponent,
|
||||
updateProgress,
|
||||
useRecordId,
|
||||
} from 'twenty-sdk/front-component';
|
||||
import { useSelectedRecordIds, updateProgress, enqueueSnackbar, unmountFrontComponent } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
@@ -15,7 +10,8 @@ const SYSTEM_PROMPT =
|
||||
'text, nothing else — no greeting label, no sign-off label, just the message.';
|
||||
|
||||
const GeneratePostCardEffect = () => {
|
||||
const recordId = useRecordId();
|
||||
const selectedRecordIds = useSelectedRecordIds();
|
||||
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (recordId === null) {
|
||||
|
||||
+22
-14
@@ -1,42 +1,50 @@
|
||||
import { useEffect } from 'react';
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import {
|
||||
enqueueSnackbar,
|
||||
unmountFrontComponent,
|
||||
updateProgress,
|
||||
useRecordId,
|
||||
} from 'twenty-sdk/front-component';
|
||||
import { useSelectedRecordIds, updateProgress, enqueueSnackbar, unmountFrontComponent } from 'twenty-sdk/front-component';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { POST_CARD_UNIVERSAL_IDENTIFIER } from '../objects/post-card.object';
|
||||
|
||||
const SendPostCardsEffect = () => {
|
||||
const recordId = useRecordId();
|
||||
const selectedRecordIds = useSelectedRecordIds();
|
||||
|
||||
useEffect(() => {
|
||||
const send = async () => {
|
||||
try {
|
||||
if (selectedRecordIds.length === 0) {
|
||||
await enqueueSnackbar({
|
||||
message: 'No postcards selected',
|
||||
variant: 'error',
|
||||
});
|
||||
await unmountFrontComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
await updateProgress(0.1);
|
||||
const client = new CoreApiClient();
|
||||
|
||||
await updateProgress(0.3);
|
||||
|
||||
if (recordId) {
|
||||
for (let i = 0; i < selectedRecordIds.length; i++) {
|
||||
await client.mutation({
|
||||
updatePostCard: {
|
||||
__args: {
|
||||
id: recordId,
|
||||
id: selectedRecordIds[i],
|
||||
data: { status: 'SENT' },
|
||||
},
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
await enqueueSnackbar({
|
||||
message: `Postcard sent`,
|
||||
variant: 'success',
|
||||
});
|
||||
await updateProgress(0.3 + (0.7 * (i + 1)) / selectedRecordIds.length);
|
||||
}
|
||||
|
||||
const count = selectedRecordIds.length;
|
||||
|
||||
await enqueueSnackbar({
|
||||
message: `${count} postcard${count > 1 ? 's' : ''} sent`,
|
||||
variant: 'success',
|
||||
});
|
||||
|
||||
await unmountFrontComponent();
|
||||
} catch (error) {
|
||||
const message =
|
||||
@@ -48,7 +56,7 @@ const SendPostCardsEffect = () => {
|
||||
};
|
||||
|
||||
send();
|
||||
}, [recordId]);
|
||||
}, [selectedRecordIds]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -3317,8 +3317,8 @@ __metadata:
|
||||
oxlint: "npm:^0.16.0"
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
twenty-client-sdk: "npm:2.2.0"
|
||||
twenty-sdk: "npm:2.2.0"
|
||||
twenty-client-sdk: "npm:0.9.0"
|
||||
twenty-sdk: "npm:0.9.0"
|
||||
typescript: "npm:^5.9.3"
|
||||
vite-tsconfig-paths: "npm:^4.2.1"
|
||||
vitest: "npm:^3.1.1"
|
||||
@@ -4059,21 +4059,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-client-sdk@npm:2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "twenty-client-sdk@npm:2.2.0"
|
||||
"twenty-client-sdk@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "twenty-client-sdk@npm:0.9.0"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
esbuild: "npm:^0.25.0"
|
||||
graphql: "npm:^16.8.1"
|
||||
checksum: 10c0/90122593efa53440ae386960211a2c274b13a410942bf6f9bc35cf952ae55fe83280e29074677fd284fa3c79069fc578980db06a92a143c08e043f865dbbbd3c
|
||||
checksum: 10c0/4b42a6622a9852fc3eca50c1131b116c5602af006ee5f1d3b4f1e97721bbc8ef5c2f3b60d9dee3ca9ca8c3c7ad4b292a7b55007b779b4c042997dbc29940ba54
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"twenty-sdk@npm:2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "twenty-sdk@npm:2.2.0"
|
||||
"twenty-sdk@npm:0.9.0":
|
||||
version: 0.9.0
|
||||
resolution: "twenty-sdk@npm:0.9.0"
|
||||
dependencies:
|
||||
"@genql/cli": "npm:^3.0.3"
|
||||
"@genql/runtime": "npm:^2.10.0"
|
||||
@@ -4093,7 +4093,7 @@ __metadata:
|
||||
react: "npm:^19.0.0"
|
||||
react-dom: "npm:^19.0.0"
|
||||
tinyglobby: "npm:^0.2.15"
|
||||
twenty-client-sdk: "npm:2.2.0"
|
||||
twenty-client-sdk: "npm:0.9.0"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
@@ -4101,7 +4101,7 @@ __metadata:
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.cjs
|
||||
checksum: 10c0/8978b4b0aa5ea282c8f76799347d9d1812901ec609bc2bd9270261a6bc5589ad361f6102a06900a4511a29a8378cd3811c2c0bad9f01cb8adadabca6d96dfd0b
|
||||
checksum: 10c0/27f93e5edac3265f819abacc853598435718020258a95d55518562e086e42daabae784964b42005d8e4ff30d1d6f13a12a38c656e18c60bad98da3f579a8e1c3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -2872,7 +2872,6 @@ enum AllMetadataName {
|
||||
fieldPermission
|
||||
frontComponent
|
||||
webhook
|
||||
applicationVariable
|
||||
connectionProvider
|
||||
}
|
||||
|
||||
|
||||
@@ -2495,7 +2495,7 @@ export interface CollectionHash {
|
||||
__typename: 'CollectionHash'
|
||||
}
|
||||
|
||||
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'applicationVariable' | 'connectionProvider'
|
||||
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook' | 'connectionProvider'
|
||||
|
||||
export interface MinimalObjectMetadata {
|
||||
id: Scalars['UUID']
|
||||
@@ -8854,7 +8854,6 @@ export const enumAllMetadataName = {
|
||||
fieldPermission: 'fieldPermission' as const,
|
||||
frontComponent: 'frontComponent' as const,
|
||||
webhook: 'webhook' as const,
|
||||
applicationVariable: 'applicationVariable' as const,
|
||||
connectionProvider: 'connectionProvider' as const
|
||||
}
|
||||
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
See the [defineApplication accordion](/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
#### Recommended screenshot dimensions
|
||||
|
||||
The marketplace renders `screenshots` in a fixed `8:5` container (for example, `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Screenshots of any aspect ratio are displayed in full and are never cropped, but anything significantly taller or narrower than `8:5` will show empty bands on the sides.
|
||||
</Note>
|
||||
|
||||
### Publish
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -15,7 +15,7 @@ Front-Komponenten können an zwei Stellen innerhalb von Twenty gerendert werden:
|
||||
|
||||
## Einfaches Beispiel
|
||||
|
||||
Der schnellste Weg, eine Front-Komponente in Aktion zu sehen, ist, sie als **Befehlsmenüeintrag** zu registrieren. Verwende `defineCommandMenuItem` in einer separaten Datei, damit die Komponente als Schnellaktionsschaltfläche oben rechts auf der Seite erscheint:
|
||||
The quickest way to see a front component in action is to register it with a **command menu item**. Use `defineCommandMenuItem` in a separate file to make the component appear as a quick-action button in the top-right corner of the page:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -350,7 +350,7 @@ export default defineFrontComponent({
|
||||
|
||||
## defineCommandMenuItem
|
||||
|
||||
Verwende `defineCommandMenuItem`, um eine Front-Komponente im Befehlsmenü (Cmd+K) zu registrieren. Wenn `isPinned` `true` ist, erscheint sie außerdem als Schnellaktionsschaltfläche oben rechts auf der Seite.
|
||||
Use `defineCommandMenuItem` to register a front component in the command menu (Cmd+K). Wenn `isPinned` `true` ist, erscheint sie außerdem als Schnellaktionsschaltfläche oben rechts auf der Seite.
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -370,7 +370,7 @@ export default defineCommandMenuItem({
|
||||
| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `universalIdentifier` | Ja | Stabile eindeutige ID für den Befehl |
|
||||
| `label` | Ja | Vollständiges Label, das im Befehlsmenü (Cmd+K) angezeigt wird |
|
||||
| `frontComponentUniversalIdentifier` | Ja | Der `universalIdentifier` der Front-Komponente, die dieser Befehl öffnet |
|
||||
| `frontComponentUniversalIdentifier` | Ja | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | Nein | Kürzeres Label, das auf der angehefteten Schnellaktionsschaltfläche angezeigt wird |
|
||||
| `icon` | Nein | Neben dem Label angezeigter Icon-Name (z. B. 'IconBolt', 'IconSend') |
|
||||
| `isPinned` | Nein | Bei `true` wird der Befehl als Schnellaktionsschaltfläche oben rechts auf der Seite angezeigt |
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
Siehe das [defineApplication-Akkordeon](/l/de/developers/extend/apps/building#defineentity-functions) auf der Seite Building Apps für die vollständige Liste der Marktplatzfelder (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` usw.).
|
||||
|
||||
#### Empfohlene Abmessungen für Screenshots
|
||||
|
||||
Der Marktplatz stellt `screenshots` in einem festen `8:5`-Container dar (zum Beispiel `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Screenshots mit beliebigem Seitenverhältnis werden vollständig angezeigt und niemals beschnitten, aber alles, was deutlich höher oder schmaler als `8:5` ist, zeigt an den Seiten leere Balken.
|
||||
</Note>
|
||||
|
||||
### Veröffentlichen
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -15,7 +15,7 @@ Os componentes de front-end podem ser renderizados em dois locais dentro do Twen
|
||||
|
||||
## Exemplo básico
|
||||
|
||||
A maneira mais rápida de ver um componente de front-end em ação é registrá-lo como um **item do menu de comando**. Use `defineCommandMenuItem` em um arquivo separado para fazer o componente aparecer como um botão de ação rápida no canto superior direito da página:
|
||||
The quickest way to see a front component in action is to register it with a **command menu item**. Use `defineCommandMenuItem` in a separate file to make the component appear as a quick-action button in the top-right corner of the page:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -350,7 +350,7 @@ export default defineFrontComponent({
|
||||
|
||||
## defineCommandMenuItem
|
||||
|
||||
Use `defineCommandMenuItem` para registrar um componente de front-end no menu de comando (Cmd+K). Se `isPinned` for `true`, ele também aparece como um botão de ação rápida no canto superior direito da página.
|
||||
Use `defineCommandMenuItem` to register a front component in the command menu (Cmd+K). Se `isPinned` for `true`, ele também aparece como um botão de ação rápida no canto superior direito da página.
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -370,7 +370,7 @@ export default defineCommandMenuItem({
|
||||
| --------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | Sim | ID exclusivo e estável para o comando |
|
||||
| `label` | Sim | Rótulo completo exibido no menu de comandos (Cmd+K) |
|
||||
| `frontComponentUniversalIdentifier` | Sim | O `universalIdentifier` do componente de front-end que este comando abre |
|
||||
| `frontComponentUniversalIdentifier` | Sim | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | Não | Rótulo mais curto exibido no botão fixado de ação rápida |
|
||||
| `icon` | Não | Nome do ícone exibido ao lado do rótulo (por exemplo, `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | Não | Quando `true`, mostra o comando como um botão de ação rápida no canto superior direito da página |
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
Veja o [acordeão de defineApplication](/l/pt/developers/extend/apps/building#defineentity-functions) na página Building Apps para a lista completa de campos do marketplace (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
#### Dimensões recomendadas para capturas de tela
|
||||
|
||||
O marketplace renderiza `screenshots` em um contêiner fixo de `8:5` (por exemplo, `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Capturas de tela de qualquer proporção são exibidas por completo e nunca são cortadas, mas qualquer coisa significativamente mais alta ou mais estreita que `8:5` exibirá faixas vazias nas laterais.
|
||||
</Note>
|
||||
|
||||
### Publicar
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -15,7 +15,7 @@ Componentele frontale pot fi afișate în două locații în cadrul Twenty:
|
||||
|
||||
## Exemplu de bază
|
||||
|
||||
Cel mai rapid mod de a vedea o componentă front-end în acțiune este să o înregistrați ca **element din meniul de comenzi**. Folosiți `defineCommandMenuItem` într-un fișier separat pentru ca componenta să apară ca buton de acțiune rapidă în colțul din dreapta sus al paginii:
|
||||
The quickest way to see a front component in action is to register it with a **command menu item**. Use `defineCommandMenuItem` in a separate file to make the component appear as a quick-action button in the top-right corner of the page:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -350,7 +350,7 @@ export default defineFrontComponent({
|
||||
|
||||
## defineCommandMenuItem
|
||||
|
||||
Folosiți `defineCommandMenuItem` pentru a înregistra o componentă front-end în meniul de comenzi (Cmd+K). Dacă `isPinned` este `true`, apare și ca buton de acțiune rapidă în colțul din dreapta sus al paginii.
|
||||
Use `defineCommandMenuItem` to register a front component in the command menu (Cmd+K). Dacă `isPinned` este `true`, apare și ca buton de acțiune rapidă în colțul din dreapta sus al paginii.
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -370,7 +370,7 @@ export default defineCommandMenuItem({
|
||||
| --------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `universalIdentifier` | Da | ID unic stabil pentru comandă |
|
||||
| `label` | Da | Etichetă completă afișată în meniul de comenzi (Cmd+K) |
|
||||
| `frontComponentUniversalIdentifier` | Da | `universalIdentifier` al componentei front-end pe care această comandă o deschide |
|
||||
| `frontComponentUniversalIdentifier` | Da | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | Nu | Etichetă mai scurtă afișată pe butonul de acțiune rapidă fixat |
|
||||
| `icon` | Nu | Numele pictogramei afișat lângă etichetă (de ex. `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | Nu | Când este `true`, afișează comanda ca buton de acțiune rapidă în colțul din dreapta sus al paginii |
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
Vezi [acordeonul defineApplication](/l/ro/developers/extend/apps/building#defineentity-functions) din pagina Building Apps pentru lista completă de câmpuri ale marketplace-ului (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
#### Dimensiuni recomandate pentru capturi de ecran
|
||||
|
||||
marketplace-ul redă `screenshots` într-un container fix cu raport `8:5` (de exemplu, `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Capturile de ecran cu orice raport de aspect sunt afișate integral și nu sunt niciodată decupate, însă orice este semnificativ mai înalt sau mai îngust decât `8:5` va afișa benzi goale pe laterale.
|
||||
</Note>
|
||||
|
||||
### Publicare
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -15,7 +15,7 @@ icon: window-maximize
|
||||
|
||||
## Простой пример
|
||||
|
||||
Самый быстрый способ увидеть фронтенд-компонент в действии — зарегистрировать его в качестве **пункта меню команд**. Используйте `defineCommandMenuItem` в отдельном файле, чтобы компонент отображался как кнопка быстрого действия в правом верхнем углу страницы:
|
||||
The quickest way to see a front component in action is to register it with a **command menu item**. Use `defineCommandMenuItem` in a separate file to make the component appear as a quick-action button in the top-right corner of the page:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -350,7 +350,7 @@ export default defineFrontComponent({
|
||||
|
||||
## defineCommandMenuItem
|
||||
|
||||
Используйте `defineCommandMenuItem`, чтобы зарегистрировать фронтенд-компонент в меню команд (Cmd+K). Если `isPinned` имеет значение `true`, команда также отображается как кнопка быстрого действия в правом верхнем углу страницы.
|
||||
Use `defineCommandMenuItem` to register a front component in the command menu (Cmd+K). Если `isPinned` имеет значение `true`, команда также отображается как кнопка быстрого действия в правом верхнем углу страницы.
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -370,7 +370,7 @@ export default defineCommandMenuItem({
|
||||
| --------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | Да | Стабильный уникальный идентификатор для команды |
|
||||
| `label` | Да | Полная метка, отображаемая в меню команд (Cmd+K) |
|
||||
| `frontComponentUniversalIdentifier` | Да | `universalIdentifier` фронтенд-компонента, который открывается этой командой |
|
||||
| `frontComponentUniversalIdentifier` | Да | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | Нет | Короткая метка, отображаемая на закреплённой кнопке быстрого действия |
|
||||
| `icon` | Нет | Имя значка, отображаемое рядом с меткой (например, `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | Нет | При значении `true` показывает команду как кнопку быстрого действия в правом верхнем углу страницы |
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
См. [аккордеон defineApplication](/l/ru/developers/extend/apps/building#defineentity-functions) на странице «Создание приложений» для полного списка полей маркетплейса (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl` и т. д.).
|
||||
|
||||
#### Рекомендуемые размеры скриншотов
|
||||
|
||||
Маркетплейс отображает `screenshots` в контейнере с фиксированным соотношением сторон `8:5` (например, `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Скриншоты с любым соотношением сторон отображаются полностью и никогда не обрезаются, но всё, что значительно выше или уже, чем `8:5`, будет иметь пустые поля по бокам.
|
||||
</Note>
|
||||
|
||||
### Публикация
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -15,7 +15,7 @@ icon: window-maximize
|
||||
|
||||
## Basit örnek
|
||||
|
||||
Bir ön uç bileşenini çalışırken görmenin en hızlı yolu, onu bir **komut menüsü öğesi** olarak kaydetmektir. Bileşenin sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak görünmesini sağlamak için ayrı bir dosyada `defineCommandMenuItem` kullanın:
|
||||
The quickest way to see a front component in action is to register it with a **command menu item**. Use `defineCommandMenuItem` in a separate file to make the component appear as a quick-action button in the top-right corner of the page:
|
||||
|
||||
```tsx src/front-components/hello-world.tsx
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
@@ -350,7 +350,7 @@ export default defineFrontComponent({
|
||||
|
||||
## defineCommandMenuItem
|
||||
|
||||
Bir ön uç bileşenini komut menüsüne (Cmd+K) kaydetmek için `defineCommandMenuItem` kullanın. `isPinned` `true` ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür.
|
||||
Use `defineCommandMenuItem` to register a front component in the command menu (Cmd+K). `isPinned` `true` ise, sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak da görünür.
|
||||
|
||||
```ts src/command-menu-items/open-dashboard.command-menu-item.ts
|
||||
import { defineCommandMenuItem } from 'twenty-sdk/define';
|
||||
@@ -370,7 +370,7 @@ export default defineCommandMenuItem({
|
||||
| --------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `universalIdentifier` | Evet | Komut için kalıcı benzersiz kimlik |
|
||||
| `label` | Evet | Komut menüsünde (Cmd+K) gösterilen tam etiket |
|
||||
| `frontComponentUniversalIdentifier` | Evet | Bu komutun açtığı ön uç bileşeninin `universalIdentifier` değeri |
|
||||
| `frontComponentUniversalIdentifier` | Evet | The `universalIdentifier` of the front component this command opens |
|
||||
| `shortLabel` | Hayır | Sabitlenmiş hızlı işlem düğmesinde görüntülenen daha kısa etiket |
|
||||
| `icon` | Hayır | Etiketin yanında görüntülenen simge adı (örn. `'IconBolt'`, `'IconSend'`) |
|
||||
| `isPinned` | Hayır | `true` olduğunda, komutu sayfanın sağ üst köşesinde bir hızlı işlem düğmesi olarak gösterir |
|
||||
|
||||
@@ -43,7 +43,7 @@ Uygulamanızın eşitleme yapacağı bir Twenty sunucusuna ihtiyacı vardır. Su
|
||||
> **Yerel bir Twenty örneği kurmak ister misiniz?**
|
||||
|
||||
* **Evet (önerilir)** — `twentycrm/twenty-app-dev` Docker imajını çeker ve `2020` portunda başlatır. Önce Docker'ın çalıştığından emin olun.
|
||||
* **Hayır** — Zaten bağlanmak istediğiniz bir Twenty sunucunuz varsa bunu seçin. Bunu daha sonra `yarn twenty remote add` ile bağlayabilirsiniz.
|
||||
* **Hayır** — Bağlanmak istediğiniz zaten bir Twenty sunucunuz varsa bunu seçin. Bunu daha sonra `yarn twenty remote add` ile bağlayabilirsiniz.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/start-instance.png" alt="Yerel örnek başlatılsın mı?" />
|
||||
@@ -58,7 +58,7 @@ Sunucu çalışır duruma geldiğinde, oturum açmak için bir tarayıcı açıl
|
||||
<img src="/images/docs/developers/extends/apps/login.png" alt="Twenty oturum açma ekranı" />
|
||||
</div>
|
||||
|
||||
Sonraki ekranda **Authorize**'a tıklayın — bu işlem CLI'nin çalışma alanınıza erişmesine izin verir.
|
||||
Sonraki ekranda **Authorize**'a tıklayın — bu işlem CLI'ın çalışma alanınıza erişmesine izin verir.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<img src="/images/docs/developers/extends/apps/authorize.png" alt="Twenty CLI yetkilendirme ekranı" />
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
Pazar yeri alanlarının tam listesi için Uygulama Oluşturma sayfasındaki [defineApplication akordeonu](/l/tr/developers/extend/apps/building#defineentity-functions)'na bakın (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, vb.).
|
||||
|
||||
#### Önerilen ekran görüntüsü boyutları
|
||||
|
||||
Marketplace, `screenshots`'ı sabit `8:5` oranlı bir kapsayıcıda görüntüler (örneğin, `1600×1000 px`).
|
||||
|
||||
<Note>
|
||||
Herhangi bir en-boy oranındaki ekran görüntüleri eksiksiz görüntülenir ve asla kırpılmaz, ancak `8:5`'ten belirgin ölçüde daha uzun veya daha dar olanlarda yanlarda boş bantlar görünür.
|
||||
</Note>
|
||||
|
||||
### Yayımla
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -198,14 +198,6 @@ export default defineApplication({
|
||||
|
||||
See the [defineApplication accordion](/l/zh/developers/extend/apps/building#defineentity-functions) in the Building Apps page for the full list of marketplace fields (`author`, `category`, `aboutDescription`, `websiteUrl`, `termsUrl`, etc.).
|
||||
|
||||
#### 建议的屏幕截图尺寸
|
||||
|
||||
该市场会在固定的 `8:5` 容器中渲染 `screenshots`(例如,`1600×1000 px`)。
|
||||
|
||||
<Note>
|
||||
任意纵横比的屏幕截图都会完整显示,且绝不会被裁剪,但相对于 `8:5` 明显更高或更窄的图片,两侧会出现空白边。
|
||||
</Note>
|
||||
|
||||
### Publish
|
||||
|
||||
```bash filename="Terminal"
|
||||
|
||||
@@ -185,7 +185,6 @@ export type AiSystemPromptSection = {
|
||||
|
||||
export enum AllMetadataName {
|
||||
agent = 'agent',
|
||||
applicationVariable = 'applicationVariable',
|
||||
commandMenuItem = 'commandMenuItem',
|
||||
connectionProvider = 'connectionProvider',
|
||||
fieldMetadata = 'fieldMetadata',
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Toepassing suksesvol opgegradeer."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "تمت ترقية التطبيق بنجاح."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplicació actualitzada correctament."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplikace byla úspěšně aktualizována."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Applikationen blev opgraderet med succes."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr "Anwendung erfolgreich deinstalliert."
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Anwendung erfolgreich aktualisiert."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Η εφαρμογή αναβαθμίστηκε με επιτυχία."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2162,11 +2162,6 @@ msgstr "Application successfully uninstalled."
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Application upgraded successfully."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr "application variable"
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplicación actualizada con éxito."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Sovellus päivitetty onnistuneesti."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Application mise à niveau avec succès."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "היישום שודרג בהצלחה."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Az alkalmazás sikeresen frissítve."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Applicazione aggiornata con successo."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "アプリケーションのアップグレードに成功しました。"
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "응용 프로그램이 성공적으로 업그레이드되었습니다."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Applicatie succesvol geüpgraded."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Applikasjonen ble oppgradert vellykket."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplikacja została pomyślnie zaktualizowana."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2162,11 +2162,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr "Aplicação desinstalada com sucesso."
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplicação atualizada com sucesso."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplicação atualizada com sucesso."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Aplicația a fost actualizată cu succes."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
Binary file not shown.
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Апликација је успешно надограђена."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Applikationen uppgraderades framgångsrikt."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Uygulama başarıyla yükseltildi."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Застосунок успішно оновлено."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "Đã nâng cấp ứng dụng thành công."
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "应用程序升级成功。"
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
@@ -2167,11 +2167,6 @@ msgstr ""
|
||||
msgid "Application upgraded successfully."
|
||||
msgstr "應用程式升級成功。"
|
||||
|
||||
#. js-lingui-id: Okob8q
|
||||
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
|
||||
msgid "application variable"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: fL7WXr
|
||||
#: src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx
|
||||
#: src/pages/settings/layout/components/SettingsLayoutDetailScaffold.tsx
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
import { type FieldFunctionOptions } from '@apollo/client/cache';
|
||||
|
||||
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
|
||||
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
|
||||
import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
|
||||
import { type RecordGqlOperationOrderBy } from 'twenty-shared/types';
|
||||
|
||||
type NewEntry = {
|
||||
edge: RecordGqlRefEdge;
|
||||
record: RecordGqlNode;
|
||||
};
|
||||
|
||||
type BuildSortedConnectionEdgesArgs = {
|
||||
currentEdges: readonly RecordGqlRefEdge[];
|
||||
newEntries: readonly NewEntry[];
|
||||
orderBy: RecordGqlOperationOrderBy | undefined;
|
||||
readField: FieldFunctionOptions['readField'];
|
||||
};
|
||||
|
||||
export const buildSortedConnectionEdges = ({
|
||||
currentEdges,
|
||||
newEntries,
|
||||
orderBy,
|
||||
readField,
|
||||
}: BuildSortedConnectionEdgesArgs): RecordGqlRefEdge[] => {
|
||||
const firstEdges: RecordGqlRefEdge[] = [];
|
||||
const lastEdges: RecordGqlRefEdge[] = [];
|
||||
const sortableEdges: RecordGqlRefEdge[] = [];
|
||||
|
||||
for (const { edge, record } of newEntries) {
|
||||
if (record.position === 'first') {
|
||||
firstEdges.push(edge);
|
||||
} else if (record.position === 'last') {
|
||||
lastEdges.push(edge);
|
||||
} else {
|
||||
sortableEdges.push(edge);
|
||||
}
|
||||
}
|
||||
|
||||
let middleEdges: RecordGqlRefEdge[];
|
||||
|
||||
if (Array.isArray(orderBy) && orderBy.length > 0) {
|
||||
middleEdges = sortCachedObjectEdges({
|
||||
edges: [...currentEdges, ...sortableEdges],
|
||||
orderBy,
|
||||
readCacheField: readField,
|
||||
});
|
||||
} else {
|
||||
middleEdges = [...sortableEdges, ...currentEdges];
|
||||
}
|
||||
|
||||
return [...firstEdges, ...middleEdges, ...lastEdges];
|
||||
};
|
||||
+100
-59
@@ -1,7 +1,6 @@
|
||||
import { type ApolloCache, type StoreObject } from '@apollo/client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { buildSortedConnectionEdges } from '@/apollo/optimistic-effect/utils/buildSortedConnectionEdges';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { type RecordGqlRefEdge } from '@/object-record/cache/types/RecordGqlRefEdge';
|
||||
@@ -120,78 +119,120 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
hasPreviousPage?: boolean;
|
||||
}>('pageInfo', rootQueryCachedObjectRecordConnection);
|
||||
|
||||
const newEntries = recordsToCreate.flatMap<{
|
||||
edge: RecordGqlRefEdge;
|
||||
record: RecordGqlNode;
|
||||
}>((recordToCreate) => {
|
||||
if (!isNonEmptyString(recordToCreate.id)) {
|
||||
return [];
|
||||
}
|
||||
const nextRootQueryCachedRecordEdges = rootQueryCachedRecordEdges
|
||||
? [...rootQueryCachedRecordEdges]
|
||||
: [];
|
||||
|
||||
if (
|
||||
isDefined(rootQueryFilter) &&
|
||||
shouldMatchRootQueryFilter === true &&
|
||||
!isRecordMatchingFilter({
|
||||
record: recordToCreate,
|
||||
filter: rootQueryFilter,
|
||||
objectMetadataItem,
|
||||
})
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const nextQueryCachedPageInfo = isDefined(rootQueryCachedPageInfo)
|
||||
? { ...rootQueryCachedPageInfo }
|
||||
: {};
|
||||
|
||||
const node = toReference(recordToCreate);
|
||||
const hasAddedRecords = recordsToCreate
|
||||
.map((recordToCreate) => {
|
||||
if (isNonEmptyString(recordToCreate.id)) {
|
||||
if (
|
||||
isDefined(rootQueryFilter) &&
|
||||
shouldMatchRootQueryFilter === true
|
||||
) {
|
||||
const recordToCreateMatchesThisRootQueryFilter =
|
||||
isRecordMatchingFilter({
|
||||
record: recordToCreate,
|
||||
filter: rootQueryFilter,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
if (!isDefined(node)) {
|
||||
return [];
|
||||
}
|
||||
if (!recordToCreateMatchesThisRootQueryFilter) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const recordAlreadyInCache = rootQueryCachedRecordEdges?.some(
|
||||
(cachedEdge) =>
|
||||
cache.identify(node) === cache.identify(cachedEdge.node),
|
||||
);
|
||||
const recordToCreateReference = toReference(recordToCreate);
|
||||
|
||||
if (recordAlreadyInCache === true) {
|
||||
return [];
|
||||
}
|
||||
if (!recordToCreateReference) {
|
||||
throw new Error(
|
||||
`Failed to create reference for record with id: ${recordToCreate.id}`,
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
edge: {
|
||||
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
|
||||
node,
|
||||
cursor: encodeCursor(recordToCreate),
|
||||
},
|
||||
record: recordToCreate,
|
||||
},
|
||||
];
|
||||
});
|
||||
const recordAlreadyInCache = rootQueryCachedRecordEdges?.some(
|
||||
(cachedEdge) => {
|
||||
return (
|
||||
cache.identify(recordToCreateReference) ===
|
||||
cache.identify(cachedEdge.node)
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (newEntries.length === 0) {
|
||||
if (isDefined(recordToCreateReference) && !recordAlreadyInCache) {
|
||||
const cursor = encodeCursor(recordToCreate);
|
||||
|
||||
const edge = {
|
||||
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
|
||||
node: recordToCreateReference,
|
||||
cursor,
|
||||
};
|
||||
|
||||
if (
|
||||
!isDefined(recordToCreate.position) ||
|
||||
recordToCreate.position === 'first'
|
||||
) {
|
||||
nextRootQueryCachedRecordEdges.unshift(edge);
|
||||
nextQueryCachedPageInfo.startCursor = cursor;
|
||||
} else if (recordToCreate.position === 'last') {
|
||||
nextRootQueryCachedRecordEdges.push(edge);
|
||||
nextQueryCachedPageInfo.endCursor = cursor;
|
||||
} else if (typeof recordToCreate.position === 'number') {
|
||||
let index = Math.round(
|
||||
nextRootQueryCachedRecordEdges.length *
|
||||
recordToCreate.position,
|
||||
);
|
||||
|
||||
if (recordToCreate.position < 0) {
|
||||
index = Math.max(
|
||||
0,
|
||||
nextRootQueryCachedRecordEdges.length +
|
||||
Math.round(recordToCreate.position),
|
||||
);
|
||||
} else if (recordToCreate.position > 1) {
|
||||
index = nextRootQueryCachedRecordEdges.length;
|
||||
}
|
||||
|
||||
index = Math.max(
|
||||
0,
|
||||
Math.min(index, nextRootQueryCachedRecordEdges.length),
|
||||
);
|
||||
|
||||
nextRootQueryCachedRecordEdges.splice(index, 0, edge);
|
||||
|
||||
if (index === 0) {
|
||||
nextQueryCachedPageInfo.startCursor = cursor;
|
||||
} else if (
|
||||
index ===
|
||||
nextRootQueryCachedRecordEdges.length - 1
|
||||
) {
|
||||
nextQueryCachedPageInfo.endCursor = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.some((hasAddedRecord) => hasAddedRecord);
|
||||
|
||||
if (!hasAddedRecords) {
|
||||
return rootQueryCachedObjectRecordConnection;
|
||||
}
|
||||
|
||||
const sortedEdges = buildSortedConnectionEdges({
|
||||
currentEdges: rootQueryCachedRecordEdges ?? [],
|
||||
newEntries,
|
||||
orderBy: rootQueryVariables?.orderBy,
|
||||
readField,
|
||||
});
|
||||
|
||||
return {
|
||||
...rootQueryCachedObjectRecordConnection,
|
||||
edges: sortedEdges,
|
||||
edges: nextRootQueryCachedRecordEdges,
|
||||
totalCount: isDefined(rootQueryCachedRecordTotalCount)
|
||||
? rootQueryCachedRecordTotalCount + newEntries.length
|
||||
? rootQueryCachedRecordTotalCount + 1
|
||||
: undefined,
|
||||
pageInfo: {
|
||||
...(rootQueryCachedPageInfo ?? {}),
|
||||
startCursor:
|
||||
sortedEdges[0]?.cursor ?? rootQueryCachedPageInfo?.startCursor,
|
||||
endCursor:
|
||||
sortedEdges[sortedEdges.length - 1]?.cursor ??
|
||||
rootQueryCachedPageInfo?.endCursor,
|
||||
},
|
||||
pageInfo: nextQueryCachedPageInfo,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
-1
@@ -48,7 +48,6 @@ export const useMetadataErrorHandler = () => {
|
||||
navigationMenuItem: t`navigation menu item`,
|
||||
webhook: t`webhook`,
|
||||
viewSort: t`view sort`,
|
||||
applicationVariable: t`application variable`,
|
||||
connectionProvider: t`connection provider`,
|
||||
} as const satisfies Record<AllMetadataName, string>;
|
||||
|
||||
|
||||
+35
-4
@@ -1,11 +1,20 @@
|
||||
import { PageLayoutWidgetInvalidConfigDisplay } from '@/page-layout/widgets/components/PageLayoutWidgetInvalidConfigDisplay';
|
||||
import { WidgetSkeletonLoader } from '@/page-layout/widgets/components/WidgetSkeletonLoader';
|
||||
import { GraphWidgetAggregateChartRenderer } from '@/page-layout/widgets/graph/graph-widget-aggregate-chart/components/GraphWidgetAggregateChartRenderer';
|
||||
import { GraphWidgetBarChartRenderer } from '@/page-layout/widgets/graph/graph-widget-bar-chart/components/GraphWidgetBarChartRenderer';
|
||||
import { GraphWidgetLineChartRenderer } from '@/page-layout/widgets/graph/graph-widget-line-chart/components/GraphWidgetLineChartRenderer';
|
||||
import { GraphWidgetPieChartRenderer } from '@/page-layout/widgets/graph/graph-widget-pie-chart/components/GraphWidgetPieChartRenderer';
|
||||
import { useCurrentWidget } from '@/page-layout/widgets/hooks/useCurrentWidget';
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { WidgetConfigurationType } from '~/generated-metadata/graphql';
|
||||
|
||||
const GraphWidgetGaugeChart = lazy(() =>
|
||||
import(
|
||||
'@/page-layout/widgets/graph/graph-widget-gauge-chart/components/GraphWidgetGaugeChart'
|
||||
).then((module) => ({
|
||||
default: module.GraphWidgetGaugeChart,
|
||||
})),
|
||||
);
|
||||
|
||||
export const GraphWidget = () => {
|
||||
const widget = useCurrentWidget();
|
||||
|
||||
@@ -15,6 +24,31 @@ export const GraphWidget = () => {
|
||||
case WidgetConfigurationType.AGGREGATE_CHART:
|
||||
return <GraphWidgetAggregateChartRenderer />;
|
||||
|
||||
case WidgetConfigurationType.GAUGE_CHART: {
|
||||
const gaugeData = {
|
||||
value: 0.7,
|
||||
min: 0,
|
||||
max: 1,
|
||||
label: 'Progress',
|
||||
};
|
||||
|
||||
return (
|
||||
<Suspense fallback={<WidgetSkeletonLoader />}>
|
||||
<GraphWidgetGaugeChart
|
||||
data={{
|
||||
value: gaugeData.value,
|
||||
min: gaugeData.min,
|
||||
max: gaugeData.max,
|
||||
label: gaugeData.label,
|
||||
}}
|
||||
displayType="percentage"
|
||||
showValue
|
||||
id={`gauge-chart-${widget.id}`}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
case WidgetConfigurationType.PIE_CHART:
|
||||
return <GraphWidgetPieChartRenderer />;
|
||||
|
||||
@@ -24,9 +58,6 @@ export const GraphWidget = () => {
|
||||
case WidgetConfigurationType.LINE_CHART:
|
||||
return <GraphWidgetLineChartRenderer />;
|
||||
|
||||
case WidgetConfigurationType.GAUGE_CHART:
|
||||
return <PageLayoutWidgetInvalidConfigDisplay />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
-3
@@ -29,7 +29,8 @@ const StyledScreenshotsContainer = styled.div`
|
||||
|
||||
const StyledScreenshotImage = styled.img`
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -42,7 +43,6 @@ const StyledScreenshotThumbnails = styled.div`
|
||||
|
||||
const StyledThumbnail = styled.div<{ isSelected?: boolean }>`
|
||||
align-items: center;
|
||||
aspect-ratio: 8 / 5;
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
border: 1px solid
|
||||
${({ isSelected }) =>
|
||||
@@ -53,6 +53,7 @@ const StyledThumbnail = styled.div<{ isSelected?: boolean }>`
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 0 0 96px;
|
||||
height: 56px;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -63,7 +64,8 @@ const StyledThumbnail = styled.div<{ isSelected?: boolean }>`
|
||||
|
||||
const StyledThumbnailImage = styled.img`
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ const graphTypeOptions = [
|
||||
GraphType.LINE,
|
||||
GraphType.PIE,
|
||||
GraphType.AGGREGATE,
|
||||
GraphType.GAUGE,
|
||||
];
|
||||
|
||||
const StyledChartTypeSelectionContainer = styled.div`
|
||||
|
||||
+45
-115
@@ -41,35 +41,13 @@ export const SSEQuerySubscribeEffect = () => {
|
||||
const requiredQueryListeners = useAtomStateValue(requiredQueryListenersState);
|
||||
const activeQueryListeners = useAtomStateValue(activeQueryListenersState);
|
||||
|
||||
const handleError = useCallback(
|
||||
(error: unknown) => {
|
||||
if (CombinedGraphQLErrors.is(error)) {
|
||||
const extensions = getGraphqlErrorExtensionsFromError(error);
|
||||
|
||||
if (
|
||||
isGracefullyHandledEventStreamError({
|
||||
subCode: extensions?.subCode,
|
||||
code: extensions?.code,
|
||||
})
|
||||
) {
|
||||
store.set(activeQueryListenersState.atom, []);
|
||||
store.set(shouldDestroyEventStreamState.atom, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unhandled error for event stream: ${error.message}`);
|
||||
}
|
||||
},
|
||||
[store],
|
||||
);
|
||||
|
||||
const syncAdditions = useCallback(async () => {
|
||||
const updateQueryListeners = useCallback(async () => {
|
||||
if (!isDefined(sseEventStreamId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requiredQueryListeners = store.get(requiredQueryListenersState.atom);
|
||||
|
||||
const activeQueryListeners = store.get(activeQueryListenersState.atom);
|
||||
|
||||
const queryListenersToAdd = requiredQueryListeners.filter(
|
||||
@@ -79,9 +57,12 @@ export const SSEQuerySubscribeEffect = () => {
|
||||
),
|
||||
);
|
||||
|
||||
if (queryListenersToAdd.length === 0) {
|
||||
return;
|
||||
}
|
||||
const queryListenersToRemove = activeQueryListeners.filter(
|
||||
(listener) =>
|
||||
!requiredQueryListeners.some(
|
||||
(requiredListener) => requiredListener.queryId === listener.queryId,
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
for (const queryListenerToAdd of queryListenersToAdd) {
|
||||
@@ -102,46 +83,7 @@ export const SSEQuerySubscribeEffect = () => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const currentActive = store.get(activeQueryListenersState.atom);
|
||||
|
||||
store.set(activeQueryListenersState.atom, [
|
||||
...currentActive,
|
||||
...queryListenersToAdd,
|
||||
]);
|
||||
}, [addQueryToEventStream, handleError, sseEventStreamId, store]);
|
||||
|
||||
const syncRemovals = useCallback(async () => {
|
||||
if (!isDefined(sseEventStreamId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const freshRequiredQueryListeners = store.get(
|
||||
requiredQueryListenersState.atom,
|
||||
);
|
||||
const activeQueryListeners = store.get(activeQueryListenersState.atom);
|
||||
|
||||
const queryListenersToRemove = activeQueryListeners.filter(
|
||||
(listener) =>
|
||||
!freshRequiredQueryListeners.some(
|
||||
(requiredListener) => requiredListener.queryId === listener.queryId,
|
||||
),
|
||||
);
|
||||
|
||||
if (queryListenersToRemove.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const removedQueryIds = new Set(
|
||||
queryListenersToRemove.map((listener) => listener.queryId),
|
||||
);
|
||||
|
||||
try {
|
||||
for (const queryListenerToRemove of queryListenersToRemove) {
|
||||
const result = await removeQueryFromEventStream({
|
||||
variables: {
|
||||
@@ -160,72 +102,60 @@ export const SSEQuerySubscribeEffect = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
if (CombinedGraphQLErrors.is(error)) {
|
||||
const extensions = getGraphqlErrorExtensionsFromError(error);
|
||||
|
||||
return;
|
||||
if (
|
||||
isGracefullyHandledEventStreamError({
|
||||
subCode: extensions?.subCode,
|
||||
code: extensions?.code,
|
||||
})
|
||||
) {
|
||||
store.set(activeQueryListenersState.atom, []);
|
||||
store.set(shouldDestroyEventStreamState.atom, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(`Unhandled error for event stream: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const currentActive = store.get(activeQueryListenersState.atom);
|
||||
store.set(activeQueryListenersState.atom, requiredQueryListeners);
|
||||
}, [
|
||||
addQueryToEventStream,
|
||||
removeQueryFromEventStream,
|
||||
sseEventStreamId,
|
||||
store,
|
||||
]);
|
||||
|
||||
store.set(
|
||||
activeQueryListenersState.atom,
|
||||
currentActive.filter(
|
||||
(listener) => !removedQueryIds.has(listener.queryId),
|
||||
),
|
||||
);
|
||||
}, [handleError, removeQueryFromEventStream, sseEventStreamId, store]);
|
||||
|
||||
const debouncedSyncAdditions = useDebouncedCallback(syncAdditions, 1000, {
|
||||
leading: true,
|
||||
});
|
||||
|
||||
const debouncedSyncRemovals = useDebouncedCallback(syncRemovals, 200, {
|
||||
leading: false,
|
||||
});
|
||||
const debouncedUpdateQueryListeners = useDebouncedCallback(
|
||||
updateQueryListeners,
|
||||
1000,
|
||||
{ leading: true },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNonEmptyString(sseEventStreamId) || !sseEventStreamReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
const areDifferent = compareArraysOfObjectsByProperty(
|
||||
requiredQueryListeners,
|
||||
activeQueryListeners,
|
||||
'queryId',
|
||||
);
|
||||
const areRequiredQueryListenersDifferentFromActiveQueryListeners =
|
||||
compareArraysOfObjectsByProperty(
|
||||
requiredQueryListeners,
|
||||
activeQueryListeners,
|
||||
'queryId',
|
||||
);
|
||||
|
||||
if (!areDifferent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasAdditions = requiredQueryListeners.some(
|
||||
(listener) =>
|
||||
!activeQueryListeners.some(
|
||||
(activeListener) => activeListener.queryId === listener.queryId,
|
||||
),
|
||||
);
|
||||
|
||||
const hasRemovals = activeQueryListeners.some(
|
||||
(listener) =>
|
||||
!requiredQueryListeners.some(
|
||||
(requiredListener) => requiredListener.queryId === listener.queryId,
|
||||
),
|
||||
);
|
||||
|
||||
if (hasAdditions) {
|
||||
debouncedSyncAdditions();
|
||||
}
|
||||
|
||||
if (hasRemovals) {
|
||||
debouncedSyncRemovals();
|
||||
if (areRequiredQueryListenersDifferentFromActiveQueryListeners) {
|
||||
debouncedUpdateQueryListeners();
|
||||
}
|
||||
}, [
|
||||
sseEventStreamId,
|
||||
sseEventStreamReady,
|
||||
requiredQueryListeners,
|
||||
activeQueryListeners,
|
||||
debouncedSyncAdditions,
|
||||
debouncedSyncRemovals,
|
||||
debouncedUpdateQueryListeners,
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
-2
@@ -6,7 +6,6 @@ import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldM
|
||||
import { SidePanelFooter } from '@/ui/layout/side-panel/components/SidePanelFooter';
|
||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||
import { type WorkflowFormAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowRunSSESubscribeEffect } from '@/workflow/workflow-diagram/components/WorkflowRunSSESubscribeEffect';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { useUpdateWorkflowRunStep } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowRunStep';
|
||||
import { WorkflowFormFieldInput } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowFormFieldInput';
|
||||
@@ -101,7 +100,6 @@ export const WorkflowEditActionFormFiller = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkflowRunSSESubscribeEffect workflowRunId={workflowRunId} />
|
||||
<WorkflowStepBody>
|
||||
{formData.map((field) => {
|
||||
if (field.type === 'RECORD') {
|
||||
|
||||
@@ -76,14 +76,3 @@ export type FunctionExecutionResult = {
|
||||
stackTrace: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ChokidarFsEvent =
|
||||
| 'add'
|
||||
| 'addDir'
|
||||
| 'change'
|
||||
| 'unlink'
|
||||
| 'unlinkDir'
|
||||
| 'ready'
|
||||
| 'raw'
|
||||
| 'error'
|
||||
| 'all';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path, { relative } from 'path';
|
||||
import chokidar, { type FSWatcher } from 'chokidar';
|
||||
import { type EventName } from 'chokidar/handler.js';
|
||||
import { ASSETS_DIR } from 'twenty-shared/application';
|
||||
import { type ChokidarFsEvent } from '@/cli/types';
|
||||
|
||||
export type ManifestWatcherOptions = {
|
||||
appPath: string;
|
||||
@@ -13,10 +13,7 @@ const IGNORED_DIRECTORY_NAMES = new Set(['node_modules', 'dist', '.twenty']);
|
||||
|
||||
export class ManifestWatcher {
|
||||
private appPath: string;
|
||||
private handleChangeDetected: (
|
||||
filePath: string,
|
||||
event: ChokidarFsEvent,
|
||||
) => void;
|
||||
private handleChangeDetected: (filePath: string, event: EventName) => void;
|
||||
private verbose: boolean;
|
||||
private watcher: FSWatcher | null = null;
|
||||
|
||||
|
||||
+2
-5
@@ -10,7 +10,7 @@ import { type ManifestBuildResult } from '@/cli/utilities/build/manifest/manifes
|
||||
import { ManifestWatcher } from '@/cli/utilities/build/manifest/manifest-watcher';
|
||||
import { type OrchestratorState } from '@/cli/utilities/dev/orchestrator/dev-mode-orchestrator-state';
|
||||
import type { Location } from 'esbuild';
|
||||
import { type ChokidarFsEvent } from '@/cli/types';
|
||||
import { type EventName } from 'chokidar/handler.js';
|
||||
import { ASSETS_DIR } from 'twenty-shared/application';
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
@@ -102,10 +102,7 @@ export class StartWatchersOrchestratorStep {
|
||||
]);
|
||||
}
|
||||
|
||||
private handleChangeDetected(
|
||||
sourcePath: string,
|
||||
event: ChokidarFsEvent,
|
||||
): void {
|
||||
private handleChangeDetected(sourcePath: string, event: EventName): void {
|
||||
this.state.addEvent({
|
||||
message: `Change detected: ${sourcePath}`,
|
||||
status: 'info',
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.3.0', 1777966965587)
|
||||
export class TransformApplicationVariableToSyncableEntityFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "core"."IDX_78ae6cfe5f49a76c4bf842ad58"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" DROP CONSTRAINT "IDX_APPLICATION_VARIABLE_KEY_APPLICATION_ID_UNIQUE"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ADD "universalIdentifier" uuid',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" DROP COLUMN "universalIdentifier"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ADD CONSTRAINT "IDX_APPLICATION_VARIABLE_KEY_APPLICATION_ID_UNIQUE" UNIQUE ("key", "applicationId")',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE INDEX "IDX_78ae6cfe5f49a76c4bf842ad58" ON "core"."applicationVariable" ("workspaceId") ',
|
||||
);
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
import { DataSource, QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.3.0', 1777966965588, { type: 'slow' })
|
||||
export class BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand
|
||||
implements SlowInstanceCommand
|
||||
{
|
||||
async runDataMigration(dataSource: DataSource): Promise<void> {
|
||||
await dataSource.query(
|
||||
'DELETE "core"."applicationVariable" WHERE "applicationId" IS NULL',
|
||||
);
|
||||
await dataSource.query(
|
||||
'UPDATE "core"."applicationVariable" SET "universalIdentifier" = gen_random_uuid() WHERE "universalIdentifier" IS NULL',
|
||||
);
|
||||
}
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" DROP CONSTRAINT "FK_51adb49e7f8df35dd23e01c4830"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ALTER COLUMN "applicationId" SET NOT NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ADD CONSTRAINT "FK_51adb49e7f8df35dd23e01c4830" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ALTER COLUMN "universalIdentifier" SET NOT NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'CREATE UNIQUE INDEX "IDX_44ecebdf70cbed17f89527b36b" ON "core"."applicationVariable" ("workspaceId", "universalIdentifier") ',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'DROP INDEX "core"."IDX_44ecebdf70cbed17f89527b36b"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ALTER COLUMN "universalIdentifier" DROP NOT NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" DROP CONSTRAINT "FK_51adb49e7f8df35dd23e01c4830"',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ALTER COLUMN "applicationId" DROP NOT NULL',
|
||||
);
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."applicationVariable" ADD CONSTRAINT "FK_51adb49e7f8df35dd23e01c4830" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION',
|
||||
);
|
||||
}
|
||||
}
|
||||
-2
@@ -3,7 +3,6 @@ import { Module } from '@nestjs/common';
|
||||
import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module';
|
||||
import { DropMessageDirectionFieldCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777400000000-drop-message-direction-field.command';
|
||||
import { BackfillImageIdentifierFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command';
|
||||
import { DeleteGaugeWidgetsCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1798000000000-delete-gauge-widgets.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
@@ -18,7 +17,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
providers: [
|
||||
DropMessageDirectionFieldCommand,
|
||||
BackfillImageIdentifierFieldMetadataIdCommand,
|
||||
DeleteGaugeWidgetsCommand,
|
||||
],
|
||||
})
|
||||
export class V2_3_UpgradeVersionCommandModule {}
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner';
|
||||
import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service';
|
||||
import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@RegisteredWorkspaceCommand('2.3.0', 1798000000000)
|
||||
@Command({
|
||||
name: 'upgrade:2-3:delete-gauge-widgets',
|
||||
description:
|
||||
'Delete all GAUGE_CHART page layout widgets — gauge support has been removed',
|
||||
})
|
||||
export class DeleteGaugeWidgetsCommand extends ActiveOrSuspendedWorkspaceCommandRunner {
|
||||
constructor(
|
||||
protected readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {
|
||||
super(workspaceIteratorService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
const { flatPageLayoutWidgetMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
const gaugeWidgets = Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
widget.universalConfiguration.configurationType ===
|
||||
WidgetConfigurationType.GAUGE_CHART,
|
||||
);
|
||||
|
||||
if (gaugeWidgets.length === 0) {
|
||||
this.logger.log(`No gauge widgets in workspace ${workspaceId}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would delete ${gaugeWidgets.length} gauge widget(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: [],
|
||||
flatEntityToDelete: gaugeWidgets,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to delete gauge widgets in workspace ${workspaceId}:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to delete gauge widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Deleted ${gaugeWidgets.length} gauge widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-4
@@ -26,8 +26,6 @@ import { AddToolAndWorkflowActionTriggerSettingsFastInstanceCommand } from 'src/
|
||||
import { MigrateToolTriggerSettingsSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1797000002000-migrate-tool-trigger-settings';
|
||||
import { ConnectionProviderSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777896012579-connection-provider-syncable-entity';
|
||||
import { RemoveUserDefaultAvatarUrlFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url';
|
||||
import { TransformApplicationVariableToSyncableEntityFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777966965587-transform-application-variable-to-syncable-entity';
|
||||
import { BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-slow-1777966965588-backfill-application-variable-universal-identifier';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -56,6 +54,4 @@ export const INSTANCE_COMMANDS = [
|
||||
MigrateToolTriggerSettingsSlowInstanceCommand,
|
||||
ConnectionProviderSyncableEntityFastInstanceCommand,
|
||||
RemoveUserDefaultAvatarUrlFastInstanceCommand,
|
||||
TransformApplicationVariableToSyncableEntityFastInstanceCommand,
|
||||
BackfillApplicationVariableUniversalIdentifierSlowInstanceCommand,
|
||||
];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user