Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfef76cd20 | ||
|
|
7c632c4884 | ||
|
|
d2694a0865 | ||
|
|
fdfdc44891 |
+9
-7
@@ -83,11 +83,11 @@
|
||||
"@sentry/types": "^8",
|
||||
"@storybook-community/storybook-addon-cookie": "^5.0.0",
|
||||
"@storybook/addon-coverage": "^3.0.0",
|
||||
"@storybook/addon-docs": "^10.2.13",
|
||||
"@storybook/addon-links": "^10.2.13",
|
||||
"@storybook/addon-vitest": "^10.2.13",
|
||||
"@storybook/addon-docs": "^10.1.11",
|
||||
"@storybook/addon-links": "^10.1.11",
|
||||
"@storybook/addon-vitest": "^10.1.11",
|
||||
"@storybook/icons": "^2.0.1",
|
||||
"@storybook/react-vite": "^10.2.13",
|
||||
"@storybook/react-vite": "^10.1.11",
|
||||
"@storybook/test-runner": "^0.24.2",
|
||||
"@stylistic/eslint-plugin": "^1.5.0",
|
||||
"@swc-node/register": "1.11.1",
|
||||
@@ -159,7 +159,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-storybook": "^10.2.13",
|
||||
"eslint-plugin-storybook": "^10.1.11",
|
||||
"eslint-plugin-unicorn": "^56.0.1",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"http-server": "^14.1.1",
|
||||
@@ -175,9 +175,9 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"source-map-support": "^0.5.20",
|
||||
"storybook": "^10.2.13",
|
||||
"storybook": "^10.1.11",
|
||||
"storybook-addon-mock-date": "2.0.0",
|
||||
"storybook-addon-pseudo-states": "^10.2.13",
|
||||
"storybook-addon-pseudo-states": "^10.1.11",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.2.3",
|
||||
@@ -201,6 +201,8 @@
|
||||
"type-fest": "4.10.1",
|
||||
"typescript": "5.9.2",
|
||||
"graphql-redis-subscriptions/ioredis": "^5.6.0",
|
||||
"prosemirror-view": "1.40.0",
|
||||
"prosemirror-transform": "1.10.4",
|
||||
"@lingui/core": "5.1.2",
|
||||
"@types/qs": "6.9.16"
|
||||
},
|
||||
|
||||
+5
-5
@@ -8,7 +8,7 @@ title: أفضل الممارسات
|
||||
|
||||
تقوم React و Jotai بإدارة الحالة في قاعدة الشيفرة.
|
||||
|
||||
### استخدم ذرات Jotai لتخزين الحالة
|
||||
### استخدم `useAtomState` لتخزين الحالة
|
||||
|
||||
من الجيد إنشاء أكبر عدد ممكن من الذرات لتخزين الحالة الخاصة بك.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
تجنب استخدام `useRef` لتخزين الحالة.
|
||||
|
||||
إذا كنت ترغب في تخزين الحالة، يجب أن تستخدم `useState` أو ذرات Jotai مع `useAtomState`.
|
||||
If you want to store state, you should use `useState` or `useAtomState`.
|
||||
|
||||
انظر [كيفية إدارة إعادة العرض](#managing-re-renders) إذا شعرت أنك بحاجة إلى `useRef` لمنع بعض إعادة العرض من الحدوث.
|
||||
|
||||
@@ -82,7 +82,7 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
|
||||
يمكنك تطبيق نفس الشيء على منطق جلب البيانات، مع الخُطافات Apollo.
|
||||
|
||||
```tsx
|
||||
// ❌ سيئ، سيتسبب في إعادة التصيير حتى إذا لم تتغير البيانات،
|
||||
// ❌ سيّئ، سيتسبب في إعادة التصيير حتى إذا لم تتغير البيانات،
|
||||
// لأن useEffect يحتاج إلى إعادة التقييم
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### استخدم حالات عائلة الذرات والمحددات
|
||||
### استخدم حالات عائلة Jotai ومحددات عائلة Jotai
|
||||
|
||||
تُعد حالات عائلة الذرات والمحددات طريقة رائعة لتجنّب عمليات إعادة التصيير.
|
||||
حالات عائلة Jotai والمحددات تعتبر طريقة رائعة لتجنب إعادة العرض.
|
||||
|
||||
إنها مفيدة عندما تحتاج إلى تخزين قائمة من العناصر.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ module1
|
||||
|
||||
تشمل منطق إدارة الحالة. [Jotai](https://jotai.org) يتولّى ذلك.
|
||||
|
||||
* المحددات: الذرات المشتقة (باستخدام `createAtomSelector`) تحسب قيماً من ذرات أخرى وتخزن نتائجها في الذاكرة مؤقتاً تلقائياً.
|
||||
* المحددات: الذرات المشتقة (باستخدام `createAtomSelector`) تحسب القيم من ذرات أخرى ويتم تخزينها تلقائيًا.
|
||||
|
||||
لا تزال إدارة الحالة المدمجة في React تتولّى الحالة داخل المكوّن.
|
||||
|
||||
|
||||
+2
-2
@@ -49,7 +49,7 @@ title: أوامر الواجهة الأمامية
|
||||
* "[React](https://react.dev/)"
|
||||
* "[Apollo](https://www.apollographql.com/docs/)"
|
||||
* "[GraphQL Codegen](https://the-guild.dev/graphql/codegen)"
|
||||
* [Jotai](https://jotai.org/)
|
||||
* "[Jotai](https://jotai.org/)"
|
||||
* "[TypeScript](https://www.typescriptlang.org/)"
|
||||
|
||||
**الاختبار**
|
||||
@@ -73,7 +73,7 @@ To avoid unnecessary [re-renders](/l/ar/developers/contribute/capabilities/front
|
||||
|
||||
### "إدارة الحالة"
|
||||
|
||||
[Jotai](https://jotai.org/) يتعامل مع إدارة الحالة.
|
||||
"[Jotai](https://jotai.org/) يتعامل مع إدارة الحالة."
|
||||
|
||||
"راجع [أفضل الممارسات](/l/ar/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) لمزيد من المعلومات حول إدارة الحالة."
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
داخليًا، يتم تخزين النطاق المحدد حاليًا في ذرة Jotai مشتركة عبر التطبيق:
|
||||
داخليًا، يتم تخزين النطاق المحدد حاليًا في حالة Jotai مشتركة عبر التطبيق:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
لكن لا يجب التعامل مع هذه الذرة يدويًا! سنرى كيف يمكن استخدامها في القسم التالي.
|
||||
لكن لا يجب التعامل مع هذه الحالة Jotai يدويًا! سنرى كيف يمكن استخدامها في القسم التالي.
|
||||
|
||||
## كيف يعمل داخليًا؟
|
||||
|
||||
قمنا بإنشاء غلاف رقيق فوق [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) والذي يجعله أكثر كفاءة ويتجنب عمليات إعادة التقديم غير الضرورية.
|
||||
|
||||
ونقوم أيضًا بإنشاء ذرة Jotai للتعامل مع حالة نطاق مفاتيح الاختصار وجعلها متاحة في جميع أنحاء التطبيق.
|
||||
ونقوم أيضًا بإنشاء حالة Jotai للتعامل مع حالة نطاق المفتاح وجعلها متاحة في جميع أنحاء التطبيق.
|
||||
|
||||
@@ -52,25 +52,22 @@ npx create-twenty-app@latest my-app --interactive
|
||||
من هنا يمكنك:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# أضف كيانًا جديدًا إلى تطبيقك (موجّه)
|
||||
# Add a new entity to your application (guided)
|
||||
yarn twenty entity:add
|
||||
|
||||
# راقب سجلات وظائف تطبيقك
|
||||
# Watch your application's function logs
|
||||
yarn twenty function:logs
|
||||
|
||||
# نفّذ وظيفة بالاسم
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# نفّذ دالة ما قبل التثبيت
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# نفّذ دالة ما بعد التثبيت
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
# أزل تثبيت التطبيق من مساحة العمل الحالية
|
||||
# Uninstall the application from the current workspace
|
||||
yarn twenty app:uninstall
|
||||
|
||||
# اعرض مساعدة الأوامر
|
||||
# Display commands' help
|
||||
yarn twenty help
|
||||
```
|
||||
|
||||
@@ -83,7 +80,7 @@ yarn twenty help
|
||||
* ينسخ تطبيقًا أساسيًا مصغّرًا إلى `my-twenty-app/`
|
||||
* يضيف اعتمادًا محليًا `twenty-sdk` وتهيئة Yarn 4
|
||||
* ينشئ ملفات ضبط ونصوصًا مرتبطة بـ `twenty` CLI
|
||||
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالتا ما قبل التثبيت وما بعد التثبيت) بالإضافة إلى ملفات أمثلة استنادًا إلى وضع الإنشاء.
|
||||
* يُنشئ الملفات الأساسية (تهيئة التطبيق، دور الدالة الافتراضي، دالة ما بعد التثبيت) بالإضافة إلى ملفات الأمثلة بحسب وضع الإنشاء
|
||||
|
||||
يبدو التطبيق المُنشأ حديثًا باستخدام الوضع الافتراضي `--exhaustive` كما يلي:
|
||||
|
||||
@@ -110,7 +107,6 @@ my-twenty-app/
|
||||
│ └── example-field.ts # تعريف حقل مستقل — مثال
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # دالة منطقية — مثال
|
||||
│ ├── pre-install.ts # دالة منطقية لما قبل التثبيت
|
||||
│ └── post-install.ts # دالة منطقية لما بعد التثبيت
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال
|
||||
```
|
||||
|
||||
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts`، `roles/default-role.ts`، `logic-functions/pre-install.ts`، و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
|
||||
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts` و`roles/default-role.ts` و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
|
||||
|
||||
بشكل عام:
|
||||
|
||||
@@ -139,18 +135,16 @@ my-twenty-app/
|
||||
|
||||
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
|
||||
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| ---------------------------------- | ---------------------------------------------- |
|
||||
| `defineObject()` | تعريفات كائنات مخصصة |
|
||||
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
|
||||
| `definePreInstallLogicFunction()` | دالة منطقية لما قبل التثبيت (تعمل قبل التثبيت) |
|
||||
| `definePostInstallLogicFunction()` | دالة منطقية لما بعد التثبيت (تعمل بعد التثبيت) |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | تعريفات الأدوار |
|
||||
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
|
||||
| `defineView()` | تعريفات العروض المحفوظة |
|
||||
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
|
||||
| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي |
|
||||
| دالة مساعدة | نوع الكيان |
|
||||
| ---------------------------- | ------------------------------------ |
|
||||
| `defineObject()` | تعريفات كائنات مخصصة |
|
||||
| `defineLogicFunction()` | تعريفات الوظائف المنطقية |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | تعريفات الأدوار |
|
||||
| `defineField()` | امتدادات الحقول للكائنات الموجودة |
|
||||
| `defineView()` | تعريفات العروض المحفوظة |
|
||||
| `defineNavigationMenuItem()` | تعريفات عناصر قائمة التنقل |
|
||||
| `defineSkill()` | تعريفات مهارات وكيل الذكاء الاصطناعي |
|
||||
|
||||
<Note>
|
||||
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
|
||||
@@ -215,19 +209,17 @@ yarn twenty auth:status
|
||||
|
||||
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
|
||||
|
||||
| دالة | الغرض |
|
||||
| ---------------------------------- | ---------------------------------------------------- |
|
||||
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
||||
| `definePreInstallLogicFunction()` | تعريف دالة منطقية لما قبل التثبيت (واحدة لكل تطبيق) |
|
||||
| `definePostInstallLogicFunction()` | تعريف دالة منطقية لما بعد التثبيت (واحدة لكل تطبيق) |
|
||||
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
||||
| `defineView()` | تعريف العروض المحفوظة للكائنات |
|
||||
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
|
||||
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
|
||||
| دالة | الغرض |
|
||||
| ---------------------------- | ---------------------------------------------------- |
|
||||
| `defineApplication()` | تهيئة بيانات التعريف للتطبيق (مطلوب، واحد لكل تطبيق) |
|
||||
| `defineObject()` | تعريف كائنات مخصصة مع حقول |
|
||||
| `defineLogicFunction()` | تعريف وظائف منطقية مع معالجات |
|
||||
| `defineFrontComponent()` | عرِّف مكوّنات أمامية لواجهة مستخدم مخصّصة |
|
||||
| `defineRole()` | تهيئة صلاحيات الدور والوصول إلى الكائنات |
|
||||
| `defineField()` | وسّع الكائنات الموجودة بحقول إضافية |
|
||||
| `defineView()` | تعريف العروض المحفوظة للكائنات |
|
||||
| `defineNavigationMenuItem()` | تعريف روابط التنقل في الشريط الجانبي |
|
||||
| `defineSkill()` | عرّف مهارات وكيل الذكاء الاصطناعي |
|
||||
|
||||
تتحقق هذه الدوال من تكوينك وقت البناء وتوفّر إكمالًا تلقائيًا في بيئة التطوير وأمان الأنواع.
|
||||
|
||||
@@ -327,7 +319,6 @@ export default defineObject({
|
||||
* **هوية التطبيق**: المعرفات، اسم العرض، والوصف.
|
||||
* **كيفية تشغيل وظائفه**: الدور الذي تستخدمه للأذونات.
|
||||
* **متغيرات (اختياري)**: أزواج مفتاح-قيمة تُعرض لوظائفك كمتغيرات بيئة.
|
||||
* **(اختياري) دالة ما قبل التثبيت**: دالة منطقية تعمل قبل تثبيت التطبيق.
|
||||
* **(Optional) post-install function**: a logic function that runs after the app is installed.
|
||||
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
@@ -336,21 +327,23 @@ Use `defineApplication()` to define your application configuration:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'تطبيق Twenty الخاص بي',
|
||||
description: 'أول تطبيق لي لـ Twenty',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'الاسم الافتراضي للمستلم للبطاقات البريدية',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ export default defineApplication({
|
||||
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
|
||||
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
|
||||
* يتم اكتشاف دوال ما قبل التثبيت وما بعد التثبيت تلقائيًا أثناء إنشاء ملف البيان. راجع [دوال ما قبل التثبيت](#pre-install-functions) و[دوال ما بعد التثبيت](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
|
||||
|
||||
#### الأدوار والصلاحيات
|
||||
|
||||
@@ -498,44 +491,6 @@ export default defineLogicFunction({
|
||||
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
|
||||
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
|
||||
|
||||
### دوال ما قبل التثبيت
|
||||
|
||||
دالة ما قبل التثبيت هي دالة منطقية تعمل تلقائيًا قبل تثبيت تطبيقك على مساحة عمل. يفيد ذلك في مهام التحقق، وفحص المتطلبات المسبقة، أو تجهيز حالة مساحة العمل قبل متابعة التثبيت الرئيسي.
|
||||
|
||||
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما قبل التثبيت لك في `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
يمكنك أيضًا تنفيذ دالة ما قبل التثبيت يدويًا في أي وقت باستخدام CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* تستخدم دوال ما قبل التثبيت `definePreInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
|
||||
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
|
||||
* يُسمح بدالة ما قبل التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
|
||||
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `preInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
|
||||
* تم ضبط المهلة الافتراضية على 300 ثانية (5 دقائق) للسماح بمهام التحضير الأطول.
|
||||
* لا تحتاج دوال ما قبل التثبيت إلى مُشغِّلات — إذ يستدعيها النظام الأساسي قبل التثبيت أو يدويًا عبر `function:execute --preInstall`.
|
||||
|
||||
### Post-install functions
|
||||
|
||||
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
|
||||
@@ -544,14 +499,16 @@ A post-install function is a logic function that runs automatically after your a
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
يتم ربط الدالة بتطبيقك من خلال الإشارة إلى المعرِّف العالمي الخاص بها في `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* تستخدم دوال ما بعد التثبيت `definePostInstallLogicFunction()` — وهو إصدار متخصص يستبعد إعدادات المُشغِّل (`cronTriggerSettings` و`databaseEventTriggerSettings` و`httpRouteTriggerSettings` و`isTool`).
|
||||
* يتلقى المُعالج `InstallLogicFunctionPayload` يحوي `{ previousVersion: string }` — إصدار التطبيق الذي كان مُثبّتًا سابقًا (أو سلسلة فارغة للتثبيتات الجديدة).
|
||||
* يُسمح بدالة ما بعد التثبيت واحدة فقط لكل تطبيق. سيُنتج إنشاء ملف البيان خطأً إذا تم اكتشاف أكثر من واحدة.
|
||||
* يتم تعيين `universalIdentifier` للدالة تلقائيًا كـ `postInstallLogicFunctionUniversalIdentifier` في بيان التطبيق أثناء الإنشاء — لست بحاجة إلى الإشارة إليه في `defineApplication()`.
|
||||
* دوال ما بعد التثبيت هي دوال منطقية قياسية — فهي تستخدم `defineLogicFunction()` مثل أي دالة أخرى.
|
||||
* حقل `postInstallLogicFunctionUniversalIdentifier` في `defineApplication()` اختياري. إذا تم تجاهله، لن يتم تشغيل أي دالة بعد التثبيت.
|
||||
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
|
||||
* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`.
|
||||
|
||||
@@ -768,7 +734,7 @@ export default defineFrontComponent({
|
||||
يمكنك إنشاء مكوّنات أمامية جديدة بطريقتين:
|
||||
|
||||
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد.
|
||||
* **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### المهارات
|
||||
|
||||
@@ -810,7 +776,7 @@ export default defineSkill({
|
||||
يتم توليد عميلين مضبوطي الأنواع تلقائيًا بواسطة `yarn twenty app:dev` وتخزينهما في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك:
|
||||
|
||||
* **`CoreApiClient`** — يُجري استعلامات إلى نقطة النهاية `/graphql` للحصول على بيانات مساحة العمل
|
||||
* **`MetadataApiClient`** — يستعلم عن نقطة النهاية `/metadata` لتكوين مساحة العمل وتحميل الملفات.
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
يُعاد توليد كلا العميلين تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
|
||||
|
||||
@@ -839,7 +805,7 @@ const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id
|
||||
|
||||
#### رفع الملفات
|
||||
|
||||
يتضمن `MetadataApiClient` المُولَّد طريقة `uploadFile` لإرفاق الملفات بالحقول من نوع ملف ضمن كائنات مساحة العمل الخاصة بك. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. نظرًا لأن عملاء GraphQL القياسيون لا يدعمون تحميل الملفات متعددة الأجزاء افتراضيًا، يوفر العميل هذه الطريقة المخصصة التي تطبق [مواصفة طلب GraphQL متعدد الأجزاء](https://github.com/jaydenseric/graphql-multipart-request-spec) في الخلفية.
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
النقاط الرئيسية:
|
||||
|
||||
* تتوفر طريقة `uploadFile` على `MetadataApiClient` لأن عملية الـ mutation الخاصة بالرفع تُعالَج عبر نقطة النهاية `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* تستخدم `universalIdentifier` الخاص بالحقل (وليس المعرّف الخاص بمساحة العمل)، ليعمل كود الرفع لديك عبر أي مساحة عمل مُثبَّت فيها تطبيقك — بما يتماشى مع كيفية إشارة التطبيقات إلى الحقول في كل مكان آخر.
|
||||
* العنوان `url` المُعاد هو عنوان URL موقّع يمكنك استخدامه للوصول إلى الملف المرفوع.
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Select an option"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Select an option"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,27 +20,27 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Input changed:", text);
|
||||
console.log("تم تغيير الإدخال:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Key pressed:", event.key);
|
||||
console.log("تم ضغط المفتاح:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="اسم المستخدم"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="اسم مستخدم غير صالح"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
},{
|
||||
```
|
||||
|
||||
|
||||
@@ -82,17 +82,17 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("تم تشغيل الدالة onValidate")}
|
||||
minRows={1}
|
||||
placeholder="اكتب تعليقًا"
|
||||
onFocus={() => console.log("تم تشغيل الدالة onFocus")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="المهمة: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
};},{
|
||||
```
|
||||
|
||||
|
||||
|
||||
+8
-8
@@ -8,7 +8,7 @@ Tento dokument popisuje osvědčené postupy, které byste měli dodržovat při
|
||||
|
||||
React a Jotai zajišťují správu stavu v kódu.
|
||||
|
||||
### Použijte atomy Jotai k ukládání stavu
|
||||
### Použijte `useAtomState` k ukládání stavu
|
||||
|
||||
Je dobrým zvykem vytvořit tolik atomů, kolik potřebujete ke správě stavu.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Vyhněte se používání `useRef` k ukládání stavu.
|
||||
|
||||
Pokud chcete ukládat stav, měli byste použít `useState` nebo atomy Jotai s `useAtomState`.
|
||||
If you want to store state, you should use `useState` or `useAtomState`.
|
||||
|
||||
Podívejte se, jak spravovat překreslení, pokud máte pocit, že potřebujete `useRef`, abyste zabránili některým překreslením.
|
||||
|
||||
@@ -82,8 +82,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
|
||||
Stejný postup můžete aplikovat na logiku získávání dat pomocí Apollo hooks.
|
||||
|
||||
```tsx
|
||||
// ❌ Špatně, způsobí překreslení, i když se data nemění,
|
||||
// protože useEffect je třeba znovu vyhodnotit
|
||||
// ❌ Špatně, způsobí překreslení i když se data nemění,
|
||||
// protože useEffect je třeba přehodnotit
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [someDependency] = useAtomState(someDependencyState);
|
||||
@@ -99,12 +99,12 @@ export const PageComponent = () => {
|
||||
|
||||
export const App = () => (
|
||||
<PageComponent />
|
||||
);
|
||||
);},{
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Dobře, nezpůsobí překreslení, pokud se data nemění,
|
||||
// protože useEffect je znovu vyhodnocen v jiné sourozenecké komponentě
|
||||
// protože useEffect je přehodnoceno v další sourozené komponentě
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Použijte rodiny atomů a selektory
|
||||
### Použijte atom family states a atom family selectors
|
||||
|
||||
Rodiny atomů a selektory jsou skvělým způsobem, jak se vyhnout překreslování.
|
||||
Stavy rodiny třísek a selektory jsou skvělý způsob, jak se vyhnout překreslování.
|
||||
|
||||
Jsou užitečné, když potřebujete uložit seznam položek.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Více podrobností naleznete v [Hooks](https://react.dev/learn/reusing-logic-wit
|
||||
|
||||
Obsahuje logiku správy stavů. To řeší [Jotai](https://jotai.org).
|
||||
|
||||
* Selektory: Odvozené atomy (pomocí `createAtomSelector`) odvozují hodnoty z jiných atomů a jsou automaticky memoizovány.
|
||||
* Selektory: Odvozené atomy (pomocí `createAtomSelector`) počítají hodnoty z jiných atomů a jsou automaticky memoizovány.
|
||||
|
||||
Vestavěná správa stavů v Reactu stále spravuje stav uvnitř komponenty.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Interně je aktuálně vybraný rozsah uložen v Jotai atomu, který je sdílen napříč aplikací :
|
||||
Interně je aktuálně vybraný rozsah uložen v Jotai stavu, který je sdílen napříč aplikací :
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Ale tento atom by se nikdy neměl spravovat ručně ! Ukážeme si, jak jej používat v příští sekci.
|
||||
Ale tento Jotai stav by neměl být nikdy řízen ručně! Ukážeme si, jak jej používat v příští sekci.
|
||||
|
||||
## Jak to funguje interně?
|
||||
|
||||
Vytvořili jsme tenkou vrstvu nad [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro), která je výkonnější a vyhýbá se zbytečným překreslením.
|
||||
|
||||
Také vytváříme Jotai atom, abychom mohli řídit stav rozsahu klávesových zkratek a učinit jej dostupným kdekoli v aplikaci.
|
||||
Také jsme vytvořili Jotai stav, abychom mohli řídit stav rozsahu klávesových zkratek a učinit jej dostupným kdekoli v aplikaci.
|
||||
|
||||
@@ -61,9 +61,6 @@ yarn twenty function:logs
|
||||
# Spusťte funkci podle názvu
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Spusťte předinstalační funkci
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Spusťte postinstalační funkci
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
@@ -83,7 +80,7 @@ Když spustíte `npx create-twenty-app@latest my-twenty-app`, scaffolder:
|
||||
* Zkopíruje minimální základní aplikaci do `my-twenty-app/`
|
||||
* Přidá lokální závislost `twenty-sdk` a konfiguraci pro Yarn 4
|
||||
* Vytvoří konfigurační soubory a skripty napojené na `twenty` CLI
|
||||
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, předinstalační a postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
|
||||
* Vygeneruje základní soubory (konfigurace aplikace, výchozí role funkcí, postinstalační funkce) a k nim ukázkové soubory podle zvoleného režimu generování kostry
|
||||
|
||||
Čerstvě vygenerovaná aplikace s výchozím režimem `--exhaustive` vypadá takto:
|
||||
|
||||
@@ -110,7 +107,6 @@ my-twenty-app/
|
||||
│ └── example-field.ts # Ukázková samostatná definice pole
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Ukázková logická funkce
|
||||
│ ├── pre-install.ts # Předinstalační logická funkce
|
||||
│ └── post-install.ts # Postinstalační logická funkce
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Ukázková front-endová komponenta
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # Ukázková definice dovednosti agenta AI
|
||||
```
|
||||
|
||||
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` a `logic-functions/post-install.ts`). S volbou `--interactive` si vyberete, které ukázkové soubory chcete zahrnout.
|
||||
S volbou `--minimal` se vytvoří pouze základní soubory (`application-config.ts`, `roles/default-role.ts` a `logic-functions/post-install.ts`). S volbou `--interactive` si vyberete, které ukázkové soubory chcete zahrnout.
|
||||
|
||||
V kostce:
|
||||
|
||||
@@ -139,18 +135,16 @@ V kostce:
|
||||
|
||||
SDK detekuje entity analýzou vašich souborů TypeScript a hledá volání **`export default define<Entity>({...})`**. Každý typ entity má odpovídající pomocnou funkci exportovanou z `twenty-sdk`:
|
||||
|
||||
| Pomocná funkce | Typ entity |
|
||||
| ---------------------------------- | --------------------------------------------------------- |
|
||||
| `defineObject()` | Definice vlastních objektů |
|
||||
| `defineLogicFunction()` | Definice logických funkcí |
|
||||
| `definePreInstallLogicFunction()` | Předinstalační logická funkce (spouští se před instalací) |
|
||||
| `definePostInstallLogicFunction()` | Postinstalační logická funkce (spouští se po instalaci) |
|
||||
| `defineFrontComponent()` | Definice frontendových komponent |
|
||||
| `defineRole()` | Definice rolí |
|
||||
| `defineField()` | Rozšíření polí u existujících objektů |
|
||||
| `defineView()` | Definice uložených zobrazení |
|
||||
| `defineNavigationMenuItem()` | Definice položek navigační nabídky |
|
||||
| `defineSkill()` | Definice dovedností agenta AI |
|
||||
| Pomocná funkce | Typ entity |
|
||||
| ---------------------------- | ------------------------------------- |
|
||||
| `defineObject()` | Definice vlastních objektů |
|
||||
| `defineLogicFunction()` | Definice logických funkcí |
|
||||
| `defineFrontComponent()` | Definice frontendových komponent |
|
||||
| `defineRole()` | Definice rolí |
|
||||
| `defineField()` | Rozšíření polí u existujících objektů |
|
||||
| `defineView()` | Definice uložených zobrazení |
|
||||
| `defineNavigationMenuItem()` | Definice položek navigační nabídky |
|
||||
| `defineSkill()` | Definice dovedností agenta AI |
|
||||
|
||||
<Note>
|
||||
**Pojmenování souborů je flexibilní.** Detekce entit je založená na AST — SDK prochází vaše zdrojové soubory a hledá vzor `export default define<Entity>({...})`. Soubory a složky můžete organizovat, jak chcete. Seskupování podle typu entity (např. `logic-functions/`, `roles/`) je pouze konvence pro organizaci kódu, nikoli požadavek.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
Pozdější příkazy přidají další soubory a složky:
|
||||
|
||||
* `yarn twenty app:dev` automaticky vygeneruje dva typované API klienty v `node_modules/twenty-sdk/generated`: `CoreApiClient` (pro data pracovního prostoru přes `/graphql`) a `MetadataApiClient` (pro konfiguraci pracovního prostoru a nahrávání souborů přes `/metadata`).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` přidá soubory s definicemi entit do `src/` pro vaše vlastní objekty, funkce, frontové komponenty, role, dovednosti a další.
|
||||
|
||||
## Ověření
|
||||
@@ -215,19 +209,17 @@ twenty-sdk poskytuje typované stavební bloky a pomocné funkce, které použí
|
||||
|
||||
SDK poskytuje pomocné funkce pro definování entit vaší aplikace. Jak je popsáno v [Detekce entit](#entity-detection), musíte použít `export default define<Entity>({...})`, aby byly vaše entity detekovány:
|
||||
|
||||
| Funkce | Účel |
|
||||
| ---------------------------------- | ----------------------------------------------------------------- |
|
||||
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
|
||||
| `defineObject()` | Definice vlastních objektů s poli |
|
||||
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
|
||||
| `definePreInstallLogicFunction()` | Definujte předinstalační logickou funkci (jedna na aplikaci) |
|
||||
| `definePostInstallLogicFunction()` | Definujte postinstalační logickou funkci (jedna na aplikaci) |
|
||||
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
|
||||
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
|
||||
| `defineField()` | Rozšiřte existující objekty o další pole |
|
||||
| `defineView()` | Definujte uložená zobrazení pro objekty |
|
||||
| `defineNavigationMenuItem()` | Definujte odkazy postranní navigace |
|
||||
| `defineSkill()` | Definuje dovednosti agenta AI |
|
||||
| Funkce | Účel |
|
||||
| ---------------------------- | ----------------------------------------------------------------- |
|
||||
| `defineApplication()` | Nakonfigurujte metadata aplikace (povinné, jedno na aplikaci) |
|
||||
| `defineObject()` | Definice vlastních objektů s poli |
|
||||
| `defineLogicFunction()` | Definice logických funkcí s obslužnými funkcemi |
|
||||
| `defineFrontComponent()` | Definujte frontendové komponenty pro vlastní uživatelské rozhraní |
|
||||
| `defineRole()` | Konfigurace oprávnění rolí a přístupu k objektům |
|
||||
| `defineField()` | Rozšiřte existující objekty o další pole |
|
||||
| `defineView()` | Definujte uložená zobrazení pro objekty |
|
||||
| `defineNavigationMenuItem()` | Definujte odkazy postranní navigace |
|
||||
| `defineSkill()` | Definuje dovednosti agenta AI |
|
||||
|
||||
Tyto funkce validují vaši konfiguraci v době sestavení a poskytují automatické doplňování v IDE a typovou bezpečnost.
|
||||
|
||||
@@ -327,7 +319,6 @@ Každá aplikace má jeden soubor `application-config.ts`, který popisuje:
|
||||
* **Identitu aplikace**: identifikátory, zobrazovaný název a popis.
|
||||
* **Jak běží její funkce**: kterou roli používají pro oprávnění.
|
||||
* **(Volitelné) proměnné**: dvojice klíč–hodnota zpřístupněné vašim funkcím jako proměnné prostředí.
|
||||
* **(Volitelná) předinstalační funkce**: logická funkce, která se spouští před instalací aplikace.
|
||||
* **(Volitelná) postinstalační funkce**: logická funkce, která se spouští po instalaci aplikace.
|
||||
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
@@ -336,6 +327,7 @@ Use `defineApplication()` to define your application configuration:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Poznámky:
|
||||
* Pole `universalIdentifier` jsou deterministická ID, která vlastníte; vygenerujte je jednou a udržujte je stabilní napříč synchronizacemi.
|
||||
* `applicationVariables` se stanou proměnnými prostředí pro vaše funkce (například `DEFAULT_RECIPIENT_NAME` je dostupné jako `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` se musí shodovat se souborem role (viz níže).
|
||||
* Předinstalační a postinstalační funkce jsou při sestavování manifestu automaticky detekovány. Viz [Předinstalační funkce](#pre-install-functions) a [Postinstalační funkce](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (volitelné) odkazuje na logickou funkci, která se automaticky spustí po instalaci aplikace. Viz [Postinstalační funkce](#post-install-functions).
|
||||
|
||||
#### Role a oprávnění
|
||||
|
||||
@@ -498,44 +491,6 @@ Poznámky:
|
||||
* Pole `triggers` je volitelné. Funkce bez spouštěčů lze použít jako pomocné funkce volané jinými funkcemi.
|
||||
* V jedné funkci můžete kombinovat více typů spouštěčů.
|
||||
|
||||
### Předinstalační funkce
|
||||
|
||||
Předinstalační funkce je logická funkce, která se automaticky spouští před instalací vaší aplikace v pracovním prostoru. To je užitečné pro validační úlohy, kontrolu předpokladů nebo přípravu stavu pracovního prostoru před zahájením hlavní instalace.
|
||||
|
||||
Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří se pro vás předinstalační funkce v `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Předinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Předinstalační funkce používají `definePreInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci).
|
||||
* Na jednu aplikaci je povolena pouze jedna předinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna.
|
||||
* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `preInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`.
|
||||
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší přípravné úlohy.
|
||||
* Předinstalační funkce nepotřebují spouštěče — platforma je vyvolává před instalací nebo je lze spustit ručně pomocí `function:execute --preInstall`.
|
||||
|
||||
### Postinstalační funkce
|
||||
|
||||
Postinstalační funkce je logická funkce, která se automaticky spouští po instalaci vaší aplikace do pracovního prostoru. To je užitečné pro jednorázové úlohy nastavení, jako je naplnění výchozími daty, vytvoření počátečních záznamů nebo konfigurace nastavení pracovního prostoru.
|
||||
@@ -544,14 +499,16 @@ Když vygenerujete kostru nové aplikace pomocí `create-twenty-app`, vytvoří
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Funkce je připojena do vaší aplikace odkazem na její univerzální identifikátor v `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Postinstalační funkci můžete také kdykoli spustit ručně pomocí CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Postinstalační funkce používají `definePostInstallLogicFunction()` — specializovanou variantu, která vynechává nastavení spouštěčů (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Obslužná funkce (handler) obdrží `InstallLogicFunctionPayload` s `{ previousVersion: string }` — verzi aplikace, která byla dříve nainstalována (nebo prázdný řetězec při čisté instalaci).
|
||||
* Na jednu aplikaci je povolena pouze jedna postinstalační funkce. Sestavení manifestu skončí chybou, pokud je zjištěna více než jedna.
|
||||
* Identifikátor `universalIdentifier` funkce se během sestavení automaticky nastaví v manifestu aplikace jako `postInstallLogicFunctionUniversalIdentifier` — není potřeba jej uvádět v `defineApplication()`.
|
||||
* Postinstalační funkce jsou standardní logické funkce — používají `defineLogicFunction()` stejně jako jakákoli jiná funkce.
|
||||
* Pole `postInstallLogicFunctionUniversalIdentifier` v `defineApplication()` je volitelné. Pokud je vynecháno, po instalaci se nespustí žádná funkce.
|
||||
* Výchozí časový limit je nastaven na 300 sekund (5 minut), aby umožnil delší úlohy nastavení, jako je naplnění daty.
|
||||
* Postinstalační funkce nepotřebují spouštěče — jsou spouštěny platformou během instalace nebo ručně pomocí `function:execute --postInstall`.
|
||||
|
||||
@@ -768,7 +734,7 @@ Hlavní body:
|
||||
Nové frontendové komponenty můžete vytvořit dvěma způsoby:
|
||||
|
||||
* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat novou frontendovou komponentu.
|
||||
* **Ruční**: Vytvořte nový soubor `.tsx` a použijte `defineFrontComponent()`, podle stejného vzoru.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Dovednosti
|
||||
|
||||
@@ -805,12 +771,12 @@ Nové dovednosti můžete vytvářet dvěma způsoby:
|
||||
* **Vygenerované**: Spusťte `yarn twenty entity:add` a zvolte možnost přidat novou dovednost.
|
||||
* **Ruční**: Vytvořte nový soubor a použijte `defineSkill()` podle stejného vzoru.
|
||||
|
||||
### Generované typované klienty
|
||||
### Generated typed clients
|
||||
|
||||
Dva typované klienty jsou automaticky generovány pomocí `yarn twenty app:dev` a ukládají se do `node_modules/twenty-sdk/generated` podle schématu vašeho pracovního prostoru:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — provádí dotazy na endpoint `/graphql` za účelem získání dat pracovního prostoru
|
||||
* **`MetadataApiClient`** — odesílá dotazy na endpoint `/metadata` pro konfiguraci pracovního prostoru a nahrávání souborů
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Oba klienti se automaticky znovu generují pomocí `yarn twenty app:dev` kdykoli se změní vaše objekty nebo pole.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Běhové přihlašovací údaje v logických funkcích
|
||||
|
||||
@@ -839,7 +805,7 @@ Poznámky:
|
||||
|
||||
#### Nahrávání souborů
|
||||
|
||||
Vygenerovaný `MetadataApiClient` obsahuje metodu `uploadFile` pro připojování souborů k polím typu souboru u objektů ve vašem pracovním prostoru. Protože standardní klienti GraphQL nativně nepodporují nahrávání souborů pomocí multipart, klient poskytuje tuto speciální metodu, která interně implementuje [specifikaci multipart požadavků GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Protože standardní klienti GraphQL nativně nepodporují nahrávání souborů pomocí multipart, klient poskytuje tuto speciální metodu, která interně implementuje [specifikaci multipart požadavků GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Hlavní body:
|
||||
|
||||
* Metoda `uploadFile` je k dispozici v `MetadataApiClient`, protože mutaci nahrávání obsluhuje endpoint `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Používá `universalIdentifier` pole (nikoli jeho ID specifické pro pracovní prostor), takže váš kód pro nahrávání funguje ve všech pracovních prostorech, kde je vaše aplikace nainstalována — v souladu s tím, jak aplikace odkazují na pole všude jinde.
|
||||
* Vrácená hodnota `url` je podepsaná adresa URL, kterou můžete použít k přístupu k nahranému souboru.
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Vyberte možnost"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Možnost A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Možnost B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Vyberte možnost"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Možnost A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Možnost B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,27 +20,27 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Změněn vstup:", text);
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Stisknutá klávesa:", event.key);
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Uživatelské jméno"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Neplatné uživatelské jméno"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},{
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("Funkce onValidate spuštěna")}
|
||||
minRows={1}
|
||||
placeholder="Napište komentář"
|
||||
onFocus={() => console.log("Funkce onFocus spuštěna")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Úkol: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+8
-8
@@ -8,7 +8,7 @@ Dieses Dokument beschreibt die besten Praktiken, die Sie beim Arbeiten am Fronte
|
||||
|
||||
React und Jotai übernehmen die Zustandsverwaltung im Code.
|
||||
|
||||
### Verwenden Sie Jotai-Atome, um den Zustand zu speichern
|
||||
### Verwenden Sie `useAtomState`, um den Zustand zu speichern.
|
||||
|
||||
Es ist eine gute Praxis, so viele Atome zu erstellen, wie Sie benötigen, um Ihren Zustand zu speichern.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Vermeiden Sie die Verwendung von `useRef`, um den Zustand zu speichern.
|
||||
|
||||
Wenn Sie den Zustand speichern möchten, sollten Sie `useState` oder Jotai-Atome mit `useAtomState` verwenden.
|
||||
If you want to store state, you should use `useState` or `useAtomState`.
|
||||
|
||||
Sehen Sie sich [an, wie Re-Renderings verwaltet werden können](#managing-re-renders), falls Sie das Gefühl haben, dass Sie `useRef` benötigen, um einige Re-Renderings zu verhindern.
|
||||
|
||||
@@ -82,8 +82,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
|
||||
Dasselbe können Sie auch für die Datenabruflogik mit Apollo-Hooks anwenden.
|
||||
|
||||
```tsx
|
||||
// ❌ Schlecht, verursacht Re-Renders, auch wenn sich die Daten nicht ändern,
|
||||
// weil useEffect neu ausgewertet werden muss
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [someDependency] = useAtomState(someDependencyState);
|
||||
@@ -103,8 +103,8 @@ export const App = () => (
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Gut, verursacht keine Re-Renders, wenn sich die Daten nicht ändern,
|
||||
// weil useEffect in einer anderen Geschwisterkomponente neu ausgewertet wird
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Verwenden Sie Atom-Familienzustände und Selektoren
|
||||
### Verwenden Sie Jotai-Familienzustände und Jotai-Familienselektoren
|
||||
|
||||
Atom-Familienzustände und Selektoren sind eine großartige Möglichkeit, Re-Renders zu vermeiden.
|
||||
Jotai-Familienzustände und -Selektoren sind eine großartige Möglichkeit, Re-Renders zu vermeiden.
|
||||
|
||||
Sie sind nützlich, wenn Sie eine Liste von Elementen speichern müssen.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Intern wird der aktuell ausgewählte Bereich in einem Jotai-Atom gespeichert, das in der gesamten Anwendung geteilt wird:
|
||||
Intern wird der aktuell ausgewählte Bereich in einem Jotai-State gespeichert, der in der gesamten Anwendung geteilt wird:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Aber dieses Atom sollte niemals manuell bearbeitet werden! Wir werden im nächsten Abschnitt sehen, wie man es verwendet.
|
||||
Aber dieser Jotai-State sollte niemals manuell bearbeitet werden! Wir werden im nächsten Abschnitt sehen, wie man es verwendet.
|
||||
|
||||
## Wie funktioniert es intern?
|
||||
|
||||
Wir haben eine dünne Schicht über [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) erstellt, die es leistungsfähiger macht und unnötige Neu-Renderings vermeidet.
|
||||
|
||||
Wir erstellen außerdem ein Jotai-Atom, um den Zustand des Tastenkombinationsbereichs zu verwalten und in der gesamten Anwendung verfügbar zu machen.
|
||||
Wir haben auch einen Jotai-State erstellt, um den Tastenkombinationsbereich zu verwalten und in der gesamten Anwendung verfügbar zu machen.
|
||||
|
||||
@@ -61,9 +61,6 @@ yarn twenty function:logs
|
||||
# Eine Funktion anhand ihres Namens ausführen
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Die Pre-Installationsfunktion ausführen
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Die Post-Installationsfunktion ausführen
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
@@ -71,7 +68,7 @@ yarn twenty function:execute --postInstall
|
||||
yarn twenty app:uninstall
|
||||
|
||||
# Hilfe zu Befehlen anzeigen
|
||||
yarn twenty help},{
|
||||
yarn twenty help
|
||||
```
|
||||
|
||||
Siehe auch: die CLI-Referenzseiten für [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) und [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -83,7 +80,7 @@ Wenn Sie `npx create-twenty-app@latest my-twenty-app` ausführen, erledigt der S
|
||||
* Kopiert eine minimale Basisanwendung nach `my-twenty-app/`
|
||||
* Fügt eine lokale `twenty-sdk`-Abhängigkeit und die Yarn-4-Konfiguration hinzu
|
||||
* Erstellt Konfigurationsdateien und Skripte, die an die `twenty`-CLI angebunden sind
|
||||
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Pre-Installations- und Post-Installationsfunktionen) sowie Beispieldateien entsprechend dem Scaffolding-Modus
|
||||
* Erzeugt Kerndateien (Anwendungskonfiguration, Standardrolle für Logikfunktionen, Post-Installationsfunktion) sowie Beispieldateien entsprechend dem Scaffolding-Modus
|
||||
|
||||
Eine frisch erstellte App mit dem Standardmodus `--exhaustive` sieht so aus:
|
||||
|
||||
@@ -110,7 +107,6 @@ my-twenty-app/
|
||||
│ └── example-field.ts # Beispiel für eine eigenständige Felddefinition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Beispiel für eine Logikfunktion
|
||||
│ ├── pre-install.ts # Pre-Installations-Logikfunktion
|
||||
│ └── post-install.ts # Post-Installations-Logikfunktion
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Beispiel für eine Frontend-Komponente
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # Beispiel für eine Skill-Definition eines KI-Agenten
|
||||
```
|
||||
|
||||
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` und `logic-functions/post-install.ts`). Mit `--interactive` wählst du aus, welche Beispieldateien enthalten sein sollen.
|
||||
Mit `--minimal` werden nur die Kerndateien erstellt (`application-config.ts`, `roles/default-role.ts` und `logic-functions/post-install.ts`). Mit `--interactive` wählst du aus, welche Beispieldateien enthalten sein sollen.
|
||||
|
||||
Auf hoher Ebene:
|
||||
|
||||
@@ -139,18 +135,16 @@ Auf hoher Ebene:
|
||||
|
||||
Das SDK erkennt Entitäten, indem es Ihre TypeScript-Dateien nach Aufrufen von **`export default define<Entity>({...})`** parst. Für jeden Entitätstyp gibt es eine entsprechende Hilfsfunktion, die aus `twenty-sdk` exportiert wird:
|
||||
|
||||
| Hilfsfunktion | Entitätstyp |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
|
||||
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
|
||||
| `definePreInstallLogicFunction()` | Pre-Installations-Logikfunktion (wird vor der Installation ausgeführt) |
|
||||
| `definePostInstallLogicFunction()` | Post-Installations-Logikfunktion (wird nach der Installation ausgeführt) |
|
||||
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
|
||||
| `defineRole()` | Rollendefinitionen |
|
||||
| `defineField()` | Felderweiterungen für bestehende Objekte |
|
||||
| `defineView()` | Gespeicherte View-Definitionen |
|
||||
| `defineNavigationMenuItem()` | Definitionen von Navigationsmenüeinträgen |
|
||||
| `defineSkill()` | Skill-Definitionen für KI-Agenten |
|
||||
| Hilfsfunktion | Entitätstyp |
|
||||
| ---------------------------- | ----------------------------------------- |
|
||||
| `defineObject()` | Benutzerdefinierte Objektdefinitionen |
|
||||
| `defineLogicFunction()` | Definitionen von Logikfunktionen |
|
||||
| `defineFrontComponent()` | Definitionen von Frontend-Komponenten |
|
||||
| `defineRole()` | Rollendefinitionen |
|
||||
| `defineField()` | Felderweiterungen für bestehende Objekte |
|
||||
| `defineView()` | Gespeicherte View-Definitionen |
|
||||
| `defineNavigationMenuItem()` | Definitionen von Navigationsmenüeinträgen |
|
||||
| `defineSkill()` | Skill-Definitionen für KI-Agenten |
|
||||
|
||||
<Note>
|
||||
**Dateibenennung ist flexibel.** Die Entitätserkennung ist AST-basiert — das SDK durchsucht Ihre Quelldateien nach dem Muster `export default define<Entity>({...})`. Sie können Ihre Dateien und Ordner nach Belieben organisieren. Die Gruppierung nach Entitätstyp (z. B. `logic-functions/`, `roles/`) ist lediglich eine Konvention zur Codeorganisation, keine Voraussetzung.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
Spätere Befehle fügen weitere Dateien und Ordner hinzu:
|
||||
|
||||
* `yarn twenty app:dev` generiert automatisch zwei typisierte API-Clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (für Arbeitsbereichsdaten über `/graphql`) und `MetadataApiClient` (für Arbeitsbereichskonfiguration und Datei-Uploads über `/metadata`).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` fügt unter `src/` Entitätsdefinitionsdateien für Ihre benutzerdefinierten Objekte, Funktionen, Frontend-Komponenten, Rollen, Skills und mehr hinzu.
|
||||
|
||||
## Authentifizierung
|
||||
@@ -215,19 +209,17 @@ Das twenty-sdk stellt typisierte Bausteine und Hilfsfunktionen bereit, die Sie i
|
||||
|
||||
Das SDK stellt Hilfsfunktionen bereit, um die Entitäten Ihrer App zu definieren. Wie in [Entitätserkennung](#entity-detection) beschrieben, müssen Sie `export default define<Entity>({...})` verwenden, damit Ihre Entitäten erkannt werden:
|
||||
|
||||
| Funktion | Zweck |
|
||||
| ---------------------------------- | --------------------------------------------------------------- |
|
||||
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
|
||||
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
|
||||
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
|
||||
| `definePreInstallLogicFunction()` | Eine Pre-Installations-Logikfunktion definieren (eine pro App) |
|
||||
| `definePostInstallLogicFunction()` | Eine Post-Installations-Logikfunktion definieren (eine pro App) |
|
||||
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
|
||||
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
|
||||
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
|
||||
| `defineView()` | Gespeicherte Views für Objekte definieren |
|
||||
| `defineNavigationMenuItem()` | Seitenleisten-Navigationslinks definieren |
|
||||
| `defineSkill()` | Definieren Sie Skills für KI-Agenten |
|
||||
| Funktion | Zweck |
|
||||
| ---------------------------- | -------------------------------------------------------------- |
|
||||
| `defineApplication()` | Anwendungsmetadaten konfigurieren (erforderlich, eine pro App) |
|
||||
| `defineObject()` | Benutzerdefinierte Objekte mit Feldern definieren |
|
||||
| `defineLogicFunction()` | Logikfunktionen mit Handlern definieren |
|
||||
| `defineFrontComponent()` | Frontend-Komponenten für benutzerdefinierte UI definieren |
|
||||
| `defineRole()` | Rollenberechtigungen und Objektzugriff konfigurieren |
|
||||
| `defineField()` | Bestehende Objekte mit zusätzlichen Feldern erweitern |
|
||||
| `defineView()` | Gespeicherte Views für Objekte definieren |
|
||||
| `defineNavigationMenuItem()` | Seitenleisten-Navigationslinks definieren |
|
||||
| `defineSkill()` | Definieren Sie Skills für KI-Agenten |
|
||||
|
||||
Diese Funktionen validieren Ihre Konfiguration zur Build-Zeit und bieten IDE-Autovervollständigung sowie Typsicherheit.
|
||||
|
||||
@@ -327,7 +319,6 @@ Jede App hat eine einzelne Datei `application-config.ts`, die Folgendes beschrei
|
||||
* **Was die App ist**: Bezeichner, Anzeigename und Beschreibung.
|
||||
* **Wie ihre Funktionen ausgeführt werden**: welche Rolle sie für Berechtigungen verwenden.
|
||||
* **(Optional) Variablen**: Schlüssel–Wert-Paare, die Ihren Funktionen als Umgebungsvariablen zur Verfügung gestellt werden.
|
||||
* **(Optional) Pre-Installationsfunktion**: eine Logikfunktion, die vor der Installation der App ausgeführt wird.
|
||||
* **(Optional) Post-Installationsfunktion**: eine Logikfunktion, die nach der Installation der App ausgeführt wird.
|
||||
|
||||
Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definieren:
|
||||
@@ -336,6 +327,7 @@ Verwenden Sie `defineApplication()`, um Ihre Anwendungskonfiguration zu definier
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Notizen:
|
||||
* `universalIdentifier`-Felder sind deterministische IDs, die Sie besitzen; generieren Sie sie einmal und halten Sie sie über Synchronisierungen hinweg stabil.
|
||||
* `applicationVariables` werden zu Umgebungsvariablen für Ihre Funktionen (zum Beispiel ist `DEFAULT_RECIPIENT_NAME` als `process.env.DEFAULT_RECIPIENT_NAME` verfügbar).
|
||||
* `defaultRoleUniversalIdentifier` muss mit der Rollendatei übereinstimmen (siehe unten).
|
||||
* Pre-Installations- und Post-Installationsfunktionen werden während des Manifest-Builds automatisch erkannt. Siehe [Pre-Installationsfunktionen](#pre-install-functions) und [Post-Installationsfunktionen](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (optional) verweist auf eine Logikfunktion, die nach der Installation der App automatisch ausgeführt wird. Siehe [Post-Installationsfunktionen](#post-install-functions).
|
||||
|
||||
#### Rollen und Berechtigungen
|
||||
|
||||
@@ -498,44 +491,6 @@ Notizen:
|
||||
* Das Array `triggers` ist optional. Funktionen ohne Trigger können als von anderen Funktionen aufgerufene Utility-Funktionen verwendet werden.
|
||||
* Sie können mehrere Trigger-Typen in einer Funktion kombinieren.
|
||||
|
||||
### Pre-Installationsfunktionen
|
||||
|
||||
Eine Pre-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, bevor deine App in einem Arbeitsbereich installiert wird. Dies ist nützlich für Validierungsaufgaben, Überprüfungen von Voraussetzungen oder die Vorbereitung des Status des Arbeitsbereichs, bevor die Hauptinstallation fortgesetzt wird.
|
||||
|
||||
Wenn du mit `create-twenty-app` eine neue App erstellst, wird für dich eine Pre-Installationsfunktion unter `src/logic-functions/pre-install.ts` erzeugt:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Du kannst die Pre-Installationsfunktion auch jederzeit manuell über die CLI ausführen:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Hauptpunkte:
|
||||
|
||||
* Pre-Installationsfunktionen verwenden `definePreInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt.
|
||||
* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen).
|
||||
* Pro Anwendung ist nur eine Pre-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird.
|
||||
* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `preInstallLogicFunctionUniversalIdentifier` gesetzt — du musst ihn nicht in `defineApplication()` referenzieren.
|
||||
* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Vorbereitungsvorgänge zu ermöglichen.
|
||||
* Pre-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform vor der Installation oder manuell über `function:execute --preInstall` aufgerufen.
|
||||
|
||||
### Post-Installationsfunktionen
|
||||
|
||||
Eine Post-Installationsfunktion ist eine Logikfunktion, die automatisch ausgeführt wird, nachdem Ihre App in einem Arbeitsbereich installiert wurde. Dies ist nützlich für einmalige Einrichtungsvorgänge wie das Befüllen mit Standarddaten, das Erstellen erster Datensätze oder das Konfigurieren von Arbeitsbereichseinstellungen.
|
||||
@@ -544,14 +499,16 @@ Wenn du mit `create-twenty-app` eine neue App erstellst, wird für dich eine Pos
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Die Funktion wird in deine App eingebunden, indem ihr universeller Bezeichner in `application-config.ts` referenziert wird:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Du kannst die Post-Installationsfunktion auch jederzeit manuell über die CLI ausführen:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Hauptpunkte:
|
||||
|
||||
* Post-Installationsfunktionen verwenden `definePostInstallLogicFunction()` — eine spezialisierte Variante, die Trigger-Einstellungen (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) weglässt.
|
||||
* Der Handler erhält ein `InstallLogicFunctionPayload` mit `{ previousVersion: string }` — die Version der App, die zuvor installiert war (oder eine leere Zeichenkette bei Neuinstallationen).
|
||||
* Pro Anwendung ist nur eine Post-Installationsfunktion zulässig. Der Manifest-Build schlägt fehl, wenn mehr als eine erkannt wird.
|
||||
* Der `universalIdentifier` der Funktion wird während des Builds im Anwendungsmanifest automatisch als `postInstallLogicFunctionUniversalIdentifier` gesetzt — du musst ihn nicht in `defineApplication()` referenzieren.
|
||||
* Post-Installationsfunktionen sind Standard-Logikfunktionen — sie verwenden `defineLogicFunction()` wie jede andere Funktion.
|
||||
* Das Feld `postInstallLogicFunctionUniversalIdentifier` in `defineApplication()` ist optional. Wenn es weggelassen wird, wird nach der Installation keine Funktion ausgeführt.
|
||||
* Das standardmäßige Timeout ist auf 300 Sekunden (5 Minuten) festgelegt, um längere Einrichtungsvorgänge wie Daten-Seeding zu ermöglichen.
|
||||
* Post-Installationsfunktionen benötigen keine Trigger — sie werden von der Plattform während der Installation oder manuell über `function:execute --postInstall` aufgerufen.
|
||||
|
||||
@@ -768,7 +734,7 @@ Hauptpunkte:
|
||||
Sie können neue Frontend-Komponenten auf zwei Arten erstellen:
|
||||
|
||||
* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente.
|
||||
* **Manuell**: Erstellen Sie eine neue `.tsx`-Datei und verwenden Sie `defineFrontComponent()` nach demselben Muster.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Fähigkeiten
|
||||
|
||||
@@ -805,12 +771,12 @@ Sie können neue Skills auf zwei Arten erstellen:
|
||||
* **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen eines neuen Skills.
|
||||
* **Manuell**: Erstellen Sie eine neue Datei und verwenden Sie `defineSkill()` nach demselben Muster.
|
||||
|
||||
### Generierte typisierte Clients
|
||||
### Generated typed clients
|
||||
|
||||
Zwei typisierte Clients werden von `yarn twenty app:dev` automatisch generiert und basierend auf Ihrem Arbeitsbereichs-Schema in `node_modules/twenty-sdk/generated` gespeichert:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — fragt den `/graphql`-Endpunkt nach Arbeitsbereichsdaten ab
|
||||
* **`MetadataApiClient`** — ruft über den Endpunkt `/metadata` die Arbeitsbereichskonfiguration und Datei-Uploads ab.
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Beide Clients werden von `yarn twenty app:dev` automatisch neu generiert, sobald sich Ihre Objekte oder Felder ändern.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Laufzeit-Anmeldedaten in Logikfunktionen
|
||||
|
||||
@@ -839,7 +805,7 @@ Notizen:
|
||||
|
||||
#### Dateien hochladen
|
||||
|
||||
Der generierte `MetadataApiClient` enthält eine Methode `uploadFile`, um Dateien an Felder des Typs Datei in Ihren Arbeitsbereichsobjekten anzuhängen. Da Standard-GraphQL-Clients Multipart-Datei-Uploads nicht nativ unterstützen, stellt der Client diese dedizierte Methode bereit, die unter der Haube die [GraphQL-Multipart-Anfragespezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec) implementiert.
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Da Standard-GraphQL-Clients Multipart-Datei-Uploads nicht nativ unterstützen, stellt der Client diese dedizierte Methode bereit, die unter der Haube die [GraphQL-Multipart-Anfragespezifikation](https://github.com/jaydenseric/graphql-multipart-request-spec) implementiert.
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Hauptpunkte:
|
||||
|
||||
* Die Methode `uploadFile` ist auf dem `MetadataApiClient` verfügbar, weil die Upload-Mutation vom Endpunkt `/metadata` aufgelöst wird.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Sie verwendet den `universalIdentifier` des Feldes (nicht dessen arbeitsbereichsspezifische ID), sodass Ihr Upload-Code in jedem Arbeitsbereich funktioniert, in dem Ihre App installiert ist — im Einklang damit, wie Apps Felder überall sonst referenzieren.
|
||||
* Die zurückgegebene `url` ist eine signierte URL, mit der Sie auf die hochgeladene Datei zugreifen können.
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Option auswählen"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Select an option"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,27 +20,27 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Eingabe geändert:", text);
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Taste gedrückt:", event.key);
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Benutzername"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Ungültiger Benutzername"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},{
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate-Funktion ausgelöst")}
|
||||
minRows={1}
|
||||
placeholder="Kommentar schreiben"
|
||||
onFocus={() => console.log("onFocus-Funktion ausgelöst")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Aufgabe: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+5
-5
@@ -6,9 +6,9 @@ Questo documento descrive le migliori pratiche da seguire quando si lavora sul f
|
||||
|
||||
## Gestione dello Stato
|
||||
|
||||
React e Jotai gestiscono lo stato nella base di codice.
|
||||
React e Jotai gestiscono la gestione dello stato nella base di codice.
|
||||
|
||||
### Usa gli atomi di Jotai per memorizzare lo stato
|
||||
### Usa `useAtomState` per memorizzare lo stato
|
||||
|
||||
È buona pratica creare tanti atomi quanti servono per memorizzare il tuo stato.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Evita di usare `useRef` per memorizzare lo stato.
|
||||
|
||||
Se vuoi memorizzare lo stato, dovresti usare `useState` o gli atomi di Jotai con `useAtomState`.
|
||||
Se vuoi memorizzare lo stato, dovresti usare `useState` o `useAtomState`.
|
||||
|
||||
Consulta [come gestire i re-render](#managing-re-renders) se senti che hai bisogno di `useRef` per evitare alcuni re-render.
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Usa gli stati e i selettori della famiglia di atomi
|
||||
### Usa stati di famiglia jotai e selettori di famiglia jotai
|
||||
|
||||
Gli stati e i selettori della famiglia di atomi sono un ottimo modo per evitare re-render.
|
||||
Gli stati e i selettori di famiglia Jotai sono un ottimo modo per evitare re-render.
|
||||
|
||||
Sono utili quando hai bisogno di memorizzare una lista di elementi.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Vedi [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) per ulteri
|
||||
|
||||
Contiene la logica di gestione degli stati. [Jotai](https://jotai.org) se ne occupa.
|
||||
|
||||
* Selettori: Gli atomi derivati (utilizzando `createAtomSelector`) calcolano valori da altri atomi e vengono memoizzati automaticamente.
|
||||
* Selettori: Gli atomi derivati (tramite `createAtomSelector`) calcolano valori da altri atomi e sono automaticamente memorizzati.
|
||||
|
||||
La gestione degli stati integrata di React si occupa ancora dello stato all'interno di un componente.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Internamente, l'ambito selezionato attualmente viene memorizzato in un atomo Jotai condiviso in tutta l'applicazione:
|
||||
Internamente, l'ambito selezionato attualmente viene memorizzato in uno stato Jotai condiviso in tutta l'applicazione:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Ma questo atomo non dovrebbe mai essere gestito manualmente! Vedremo come usarlo nella sezione successiva.
|
||||
Ma questo stato Jotai non dovrebbe mai essere gestito manualmente! Vedremo come usarlo nella sezione successiva.
|
||||
|
||||
## Come funziona internamente?
|
||||
|
||||
Abbiamo creato un sottile wrapper su [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) che lo rende più performante ed evita rendering non necessari.
|
||||
|
||||
Creiamo anche un atomo Jotai per gestire lo stato dell'ambito del tasto di scelta rapida e renderlo disponibile ovunque nell'applicazione.
|
||||
Abbiamo anche creato uno stato Jotai per gestire lo stato dell'ambito del tasto di scelta rapida e renderlo disponibile ovunque nell'applicazione.
|
||||
|
||||
@@ -61,9 +61,6 @@ yarn twenty function:logs
|
||||
# Esegui una funzione per nome
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Esegui la funzione di pre-installazione
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Esegui la funzione post-installazione
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
@@ -83,7 +80,7 @@ Quando esegui `npx create-twenty-app@latest my-twenty-app`, lo scaffolder:
|
||||
* Copia un'applicazione base minimale in `my-twenty-app/`
|
||||
* Aggiunge una dipendenza locale `twenty-sdk` e la configurazione di Yarn 4
|
||||
* Crea file di configurazione e script collegati alla CLI `twenty`
|
||||
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzioni di pre-installazione e post-installazione) più i file di esempio in base alla modalità di scaffolding
|
||||
* Genera i file principali (configurazione dell'applicazione, ruolo predefinito per le funzioni logiche, funzione di post-installazione) più i file di esempio in base alla modalità di scaffolding
|
||||
|
||||
Un'app appena creata con la modalità predefinita `--exhaustive` si presenta così:
|
||||
|
||||
@@ -99,30 +96,29 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Cartella delle risorse pubbliche (immagini, font, ecc.)
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # Obbligatorio - configurazione principale dell'applicazione
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── roles/
|
||||
│ └── default-role.ts # Ruolo predefinito per le funzioni logiche
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # Definizione di oggetto personalizzato di esempio
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # Definizione di campo autonomo di esempio
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Funzione logica di esempio
|
||||
│ ├── pre-install.ts # Funzione logica di pre-installazione
|
||||
│ └── post-install.ts # Funzione logica di post-installazione
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Componente front-end di esempio
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── views/
|
||||
│ └── example-view.ts # Definizione di vista salvata di esempio
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Link di navigazione della barra laterale di esempio
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # Definizione di skill per agente IA di esempio
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
```
|
||||
|
||||
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` e `logic-functions/post-install.ts`). Con `--interactive`, scegli quali file di esempio includere.
|
||||
Con `--minimal`, vengono creati solo i file principali (`application-config.ts`, `roles/default-role.ts` e `logic-functions/post-install.ts`). Con `--interactive`, scegli quali file di esempio includere.
|
||||
|
||||
A livello generale:
|
||||
|
||||
@@ -139,18 +135,16 @@ A livello generale:
|
||||
|
||||
L'SDK rileva le entità analizzando i tuoi file TypeScript alla ricerca di chiamate **`export default define<Entity>({...})`**. Ogni tipo di entità ha una corrispondente funzione helper esportata da `twenty-sdk`:
|
||||
|
||||
| Funzione helper | Tipo di entità |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| `defineObject()` | Definizioni di oggetti personalizzati |
|
||||
| `defineLogicFunction()` | Definizioni di funzioni logiche |
|
||||
| `definePreInstallLogicFunction()` | Funzione logica di pre-installazione (viene eseguita prima dell'installazione) |
|
||||
| `definePostInstallLogicFunction()` | Funzione logica di post-installazione (viene eseguita dopo l'installazione) |
|
||||
| `defineFrontComponent()` | Definizioni dei componenti front-end |
|
||||
| `defineRole()` | Definizioni di ruoli |
|
||||
| `defineField()` | Estensioni di campo per oggetti esistenti |
|
||||
| `defineView()` | Definizioni di viste salvate |
|
||||
| `defineNavigationMenuItem()` | Definizioni delle voci del menu di navigazione |
|
||||
| `defineSkill()` | AI agent skill definitions |
|
||||
| Funzione helper | Tipo di entità |
|
||||
| ---------------------------- | ---------------------------------------------- |
|
||||
| `defineObject()` | Definizioni di oggetti personalizzati |
|
||||
| `defineLogicFunction()` | Definizioni di funzioni logiche |
|
||||
| `defineFrontComponent()` | Definizioni dei componenti front-end |
|
||||
| `defineRole()` | Definizioni di ruoli |
|
||||
| `defineField()` | Estensioni di campo per oggetti esistenti |
|
||||
| `defineView()` | Definizioni di viste salvate |
|
||||
| `defineNavigationMenuItem()` | Definizioni delle voci del menu di navigazione |
|
||||
| `defineSkill()` | AI agent skill definitions |
|
||||
|
||||
<Note>
|
||||
**La denominazione dei file è flessibile.** Il rilevamento delle entità è basato sull'AST — l'SDK esegue la scansione dei file sorgente alla ricerca del pattern `export default define<Entity>({...})`. Puoi organizzare file e cartelle come preferisci. Raggruppare per tipo di entità (ad es., `logic-functions/`, `roles/`) è solo una convenzione per l'organizzazione del codice, non un requisito.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
Comandi successivi aggiungeranno altri file e cartelle:
|
||||
|
||||
* `yarn twenty app:dev` genererà automaticamente due client API tipizzati in `node_modules/twenty-sdk/generated`: `CoreApiClient` (per i dati dell'area di lavoro tramite `/graphql`) e `MetadataApiClient` (per la configurazione dell'area di lavoro e il caricamento di file tramite `/metadata`).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
|
||||
|
||||
## Autenticazione
|
||||
@@ -215,19 +209,17 @@ Il pacchetto twenty-sdk fornisce blocchi tipizzati e funzioni helper da usare ne
|
||||
|
||||
L'SDK fornisce funzioni helper per definire le entità della tua app. Come descritto in [Rilevamento delle entità](#entity-detection), devi usare `export default define<Entity>({...})` affinché le tue entità vengano rilevate:
|
||||
|
||||
| Funzione | Scopo |
|
||||
| ---------------------------------- | -------------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) |
|
||||
| `defineObject()` | Definisci oggetti personalizzati con campi |
|
||||
| `defineLogicFunction()` | Definisci funzioni logiche con handler |
|
||||
| `definePreInstallLogicFunction()` | Definisce una funzione logica di pre-installazione (una per applicazione) |
|
||||
| `definePostInstallLogicFunction()` | Definisce una funzione logica di post-installazione (una per applicazione) |
|
||||
| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata |
|
||||
| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti |
|
||||
| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi |
|
||||
| `defineView()` | Definisce viste salvate per gli oggetti |
|
||||
| `defineNavigationMenuItem()` | Definisce i link di navigazione della barra laterale |
|
||||
| `defineSkill()` | Define AI agent skills |
|
||||
| Funzione | Scopo |
|
||||
| ---------------------------- | ----------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Configura i metadati dell'applicazione (obbligatorio, uno per app) |
|
||||
| `defineObject()` | Definisci oggetti personalizzati con campi |
|
||||
| `defineLogicFunction()` | Definisci funzioni logiche con handler |
|
||||
| `defineFrontComponent()` | Definisci componenti front-end per un'interfaccia utente personalizzata |
|
||||
| `defineRole()` | Configura i permessi dei ruoli e l'accesso agli oggetti |
|
||||
| `defineField()` | Estendi gli oggetti esistenti con campi aggiuntivi |
|
||||
| `defineView()` | Definisce viste salvate per gli oggetti |
|
||||
| `defineNavigationMenuItem()` | Definisce i link di navigazione della barra laterale |
|
||||
| `defineSkill()` | Define AI agent skills |
|
||||
|
||||
Queste funzioni convalidano la configurazione in fase di build e offrono il completamento automatico nell'IDE e la sicurezza dei tipi.
|
||||
|
||||
@@ -327,7 +319,6 @@ Ogni app ha un singolo file `application-config.ts` che descrive:
|
||||
* **Identità dell'app**: identificatori, nome visualizzato e descrizione.
|
||||
* **Come vengono eseguite le sue funzioni**: quale ruolo usano per i permessi.
|
||||
* **Variabili (opzionali)**: coppie chiave–valore esposte alle funzioni come variabili d'ambiente.
|
||||
* **(Opzionale) funzione di pre-installazione**: una funzione logica che viene eseguita prima che l'app venga installata.
|
||||
* **(Opzionale) funzione post-installazione**: una funzione logica che viene eseguita dopo l'installazione dell'app.
|
||||
|
||||
Usa `defineApplication()` per definire la configurazione della tua applicazione:
|
||||
@@ -336,6 +327,7 @@ Usa `defineApplication()` per definire la configurazione della tua applicazione:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Note:
|
||||
* I campi `universalIdentifier` sono ID deterministici sotto il tuo controllo; generali una volta e mantienili stabili tra le sincronizzazioni.
|
||||
* `applicationVariables` diventano variabili d'ambiente per le tue funzioni (ad esempio, `DEFAULT_RECIPIENT_NAME` è disponibile come `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` deve corrispondere al file del ruolo (vedi sotto).
|
||||
* Le funzioni di pre-installazione e post-installazione vengono rilevate automaticamente durante la build del manifesto. Vedi [Funzioni di pre-installazione](#pre-install-functions) e [Funzioni di post-installazione](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (opzionale) fa riferimento a una funzione logica che viene eseguita automaticamente dopo l'installazione dell'app. Vedi [Funzioni post-installazione](#post-install-functions).
|
||||
|
||||
#### Ruoli e permessi
|
||||
|
||||
@@ -498,44 +491,6 @@ Note:
|
||||
* L'array `triggers` è facoltativo. Le funzioni senza trigger possono essere utilizzate come funzioni di utilità richiamate da altre funzioni.
|
||||
* Puoi combinare più tipi di trigger in un'unica funzione.
|
||||
|
||||
### Funzioni di pre-installazione
|
||||
|
||||
Una funzione di pre-installazione è una funzione logica che viene eseguita automaticamente prima che la tua app venga installata in uno spazio di lavoro. È utile per attività di convalida, controlli dei prerequisiti o per preparare lo stato dello spazio di lavoro prima che proceda l'installazione principale.
|
||||
|
||||
Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene generata una funzione di pre-installazione in `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Puoi anche eseguire manualmente la funzione di pre-installazione in qualsiasi momento utilizzando la CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Punti chiave:
|
||||
|
||||
* Le funzioni di pre-installazione utilizzano `definePreInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni).
|
||||
* È consentita una sola funzione di pre-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una.
|
||||
* L'`universalIdentifier` della funzione viene impostato automaticamente come `preInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`.
|
||||
* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di preparazione più lunghe.
|
||||
* Le funzioni di pre-installazione non necessitano di trigger — vengono invocate dalla piattaforma prima dell'installazione o manualmente tramite `function:execute --preInstall`.
|
||||
|
||||
### Funzioni post-installazione
|
||||
|
||||
Una funzione post-installazione è una funzione logica che viene eseguita automaticamente dopo che la tua app è stata installata in uno spazio di lavoro. Questo è utile per attività di configurazione una tantum come il popolamento di dati predefiniti, la creazione di record iniziali o la configurazione delle impostazioni dello spazio di lavoro.
|
||||
@@ -544,14 +499,16 @@ Quando esegui lo scaffolding di una nuova app con `create-twenty-app`, viene gen
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
La funzione viene collegata alla tua app facendo riferimento al suo identificatore universale in `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Puoi anche eseguire manualmente la funzione di post-installazione in qualsiasi momento utilizzando la CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Punti chiave:
|
||||
|
||||
* Le funzioni di post-installazione utilizzano `definePostInstallLogicFunction()` — una variante specializzata che omette le impostazioni dei trigger (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* L'handler riceve un `InstallLogicFunctionPayload` con `{ previousVersion: string }` — la versione dell'app precedentemente installata (oppure una stringa vuota per nuove installazioni).
|
||||
* È consentita una sola funzione di post-installazione per applicazione. La build del manifesto genererà un errore se ne viene rilevata più di una.
|
||||
* L'`universalIdentifier` della funzione viene impostato automaticamente come `postInstallLogicFunctionUniversalIdentifier` nel manifesto dell'applicazione durante la build — non è necessario farvi riferimento in `defineApplication()`.
|
||||
* Le funzioni di post-installazione sono funzioni logiche standard — usano `defineLogicFunction()` come qualsiasi altra funzione.
|
||||
* Il campo `postInstallLogicFunctionUniversalIdentifier` in `defineApplication()` è facoltativo. Se omesso, nessuna funzione viene eseguita dopo l'installazione.
|
||||
* Il timeout predefinito è impostato a 300 secondi (5 minuti) per consentire attività di configurazione più lunghe, come il popolamento dei dati.
|
||||
* Le funzioni di post-installazione non necessitano di trigger — vengono invocate dalla piattaforma durante l'installazione o manualmente tramite `function:execute --postInstall`.
|
||||
|
||||
@@ -768,7 +734,7 @@ Punti chiave:
|
||||
Puoi creare nuovi componenti front-end in due modi:
|
||||
|
||||
* **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo componente front-end.
|
||||
* **Manuale**: Crea un nuovo file `.tsx` e usa `defineFrontComponent()`, seguendo lo stesso schema.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Abilità
|
||||
|
||||
@@ -805,12 +771,12 @@ You can create new skills in two ways:
|
||||
* **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill.
|
||||
* **Manual**: Create a new file and use `defineSkill()`, following the same pattern.
|
||||
|
||||
### Client tipizzati generati
|
||||
### Generated typed clients
|
||||
|
||||
Due client tipizzati sono generati automaticamente da `yarn twenty app:dev` e salvati in `node_modules/twenty-sdk/generated` in base allo schema della tua area di lavoro:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — interroga l'endpoint `/graphql` per i dati dell'area di lavoro
|
||||
* **`MetadataApiClient`** — interroga l'endpoint `/metadata` per la configurazione dello spazio di lavoro e il caricamento dei file
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Entrambi i client vengono rigenerati automaticamente da `yarn twenty app:dev` ogni volta che i tuoi oggetti o campi cambiano.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Credenziali di runtime nelle funzioni logiche
|
||||
|
||||
@@ -839,7 +805,7 @@ Note:
|
||||
|
||||
#### Caricamento dei file
|
||||
|
||||
Il `MetadataApiClient` generato include un metodo `uploadFile` per allegare file ai campi di tipo file sugli oggetti del tuo spazio di lavoro. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte.
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Poiché i client GraphQL standard non supportano nativamente il caricamento di file multipart, il client fornisce questo metodo dedicato che implementa la [specifica della richiesta GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec) dietro le quinte.
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Punti chiave:
|
||||
|
||||
* Il metodo `uploadFile` è disponibile su `MetadataApiClient` perché la mutazione di upload viene risolta dall'endpoint `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Usa l'`universalIdentifier` del campo (non il suo ID specifico dello spazio di lavoro), quindi il tuo codice di upload funziona in qualsiasi spazio di lavoro in cui la tua app è installata — coerentemente con il modo in cui le app fanno riferimento ai campi altrove.
|
||||
* L'`url` restituito è un URL firmato che puoi usare per accedere al file caricato.
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Seleziona un'opzione"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Opzione A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Opzione B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Seleziona un'opzione"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Opzione A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Opzione B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,16 +28,16 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+8
-8
@@ -8,7 +8,7 @@ Este documento descreve as melhores práticas que você deve seguir ao trabalhar
|
||||
|
||||
React e Jotai lidam com o gerenciamento de estado na base de código.
|
||||
|
||||
### Use átomos do Jotai para armazenar o estado
|
||||
### Use `useAtomState` para armazenar o estado
|
||||
|
||||
É uma boa prática criar tantos átomos quanto necessário para armazenar seu estado.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Evite usar `useRef` para armazenar estado.
|
||||
|
||||
Se quiser armazenar o estado, você deve usar `useState` ou átomos do Jotai com `useAtomState`.
|
||||
If you want to store state, you should use `useState` or `useAtomState`.
|
||||
|
||||
Veja [como gerenciar re-renderizações](#managing-re-renders) se você sentir que precisa de `useRef` para evitar que algumas re-renderizações aconteçam.
|
||||
|
||||
@@ -82,8 +82,8 @@ If you feel like you need to add a `useEffect` in your root component, you shoul
|
||||
Você pode aplicar o mesmo para lógica de busca de dados, com hooks do Apollo.
|
||||
|
||||
```tsx
|
||||
// ❌ Ruim, causará re-renderizações mesmo se os dados não estiverem mudando,
|
||||
// porque o useEffect precisa ser reavaliado
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [someDependency] = useAtomState(someDependencyState);
|
||||
@@ -103,8 +103,8 @@ export const App = () => (
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Bom, não causará re-renderizações se os dados não estiverem mudando,
|
||||
// porque o useEffect é reavaliado em outro componente irmão
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Use famílias de átomos e seletores de família
|
||||
### Use estados de família Jotai e seletores de família Jotai
|
||||
|
||||
Famílias de átomos e seletores são uma ótima maneira de evitar re-renderizações.
|
||||
Estados de família Jotai e seletores são uma ótima maneira de evitar re-renderizações.
|
||||
|
||||
Eles são úteis quando você precisa armazenar uma lista de itens.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Veja [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) para mais
|
||||
|
||||
Contém a lógica de gerenciamento de estado. [Jotai](https://jotai.org) lida com isso.
|
||||
|
||||
* Seletores: Átomos derivados (usando `createAtomSelector`) calculam valores a partir de outros átomos e são automaticamente memoizados.
|
||||
* Seletores: Átomos derivados (usando `createAtomSelector`) calculam valores a partir de outros átomos e são automaticamente memorizados.
|
||||
|
||||
O gerenciamento de estado embutido do React ainda lida com o estado dentro de um componente.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Internamente, o escopo atualmente selecionado é armazenado em um átomo Jotai que é compartilhado por toda a aplicação :
|
||||
Internamente, o escopo atualmente selecionado é armazenado em um estado Jotai que é compartilhado por toda a aplicação :
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Mas esse átomo nunca deve ser manipulado manualmente! Veremos como usá-lo na próxima seção.
|
||||
Mas esse estado Jotai nunca deve ser manipulado manualmente! Veremos como usá-lo na próxima seção.
|
||||
|
||||
## Como funciona internamente?
|
||||
|
||||
Criamos um wrapper leve em cima de [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) que o torna mais eficiente e evita renderizações desnecessárias.
|
||||
|
||||
Também criamos um átomo Jotai para gerenciar o estado do escopo da tecla de atalho e torná-lo disponível em toda a aplicação.
|
||||
Também criamos um estado Jotai para gerenciar o estado do escopo da tecla de atalho e torná-lo disponível em toda a aplicação.
|
||||
|
||||
@@ -52,25 +52,22 @@ npx create-twenty-app@latest my-app --interactive
|
||||
A partir daqui você pode:
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Add a new entity to your application (guided)
|
||||
# Adicionar uma nova entidade à sua aplicação (assistido)
|
||||
yarn twenty entity:add
|
||||
|
||||
# Watch your application's function logs
|
||||
# Acompanhar os logs das funções da sua aplicação
|
||||
yarn twenty function:logs
|
||||
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
# Executar uma função pelo nome
|
||||
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Execute the post-install function
|
||||
# Executar a função de pós-instalação
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
# Uninstall the application from the current workspace
|
||||
# Desinstalar a aplicação do espaço de trabalho atual
|
||||
yarn twenty app:uninstall
|
||||
|
||||
# Display commands' help
|
||||
# Exibir a ajuda dos comandos
|
||||
yarn twenty help
|
||||
```
|
||||
|
||||
@@ -83,7 +80,7 @@ Ao executar `npx create-twenty-app@latest my-twenty-app`, o gerador:
|
||||
* Copia um aplicativo base mínimo para `my-twenty-app/`
|
||||
* Adiciona uma dependência local `twenty-sdk` e a configuração do Yarn 4
|
||||
* Cria arquivos de configuração e scripts conectados à CLI `twenty`
|
||||
* Generates core files (application config, default function role, pre-install and post-install functions) plus example files based on the scaffolding mode
|
||||
* Gera arquivos principais (configuração da aplicação, papel padrão para funções de lógica, função de pós-instalação) além de arquivos de exemplo com base no modo de geração de estrutura
|
||||
|
||||
Um app recém-criado com o modo padrão `--exhaustive` fica assim:
|
||||
|
||||
@@ -99,30 +96,29 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
public/ # Pasta de recursos públicos (imagens, fontes, etc.)
|
||||
src/
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── application-config.ts # Obrigatório - configuração principal da aplicação
|
||||
├── roles/
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
│ └── default-role.ts # Papel padrão para funções de lógica
|
||||
├── objects/
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
│ └── example-object.ts # Exemplo de definição de objeto personalizado
|
||||
├── fields/
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
│ └── example-field.ts # Exemplo de definição de campo independente
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ ├── pre-install.ts # Pre-install logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
│ ├── hello-world.ts # Exemplo de função de lógica
|
||||
│ └── post-install.ts # Função de lógica de pós-instalação
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Example front component
|
||||
│ └── hello-world.tsx # Exemplo de componente de front-end
|
||||
├── views/
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
│ └── example-view.ts # Exemplo de definição de visualização salva
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
│ └── example-navigation-menu-item.ts # Exemplo de link de navegação da barra lateral
|
||||
└── skills/
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
└── example-skill.ts # Exemplo de definição de habilidade de agente de IA
|
||||
```
|
||||
|
||||
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts`, and `logic-functions/post-install.ts`). Com `--interactive`, você escolhe quais arquivos de exemplo incluir.
|
||||
Com `--minimal`, apenas os arquivos principais são criados (`application-config.ts`, `roles/default-role.ts` e `logic-functions/post-install.ts`). Com `--interactive`, você escolhe quais arquivos de exemplo incluir.
|
||||
|
||||
Em alto nível:
|
||||
|
||||
@@ -139,18 +135,16 @@ Em alto nível:
|
||||
|
||||
O SDK detecta entidades analisando seus arquivos TypeScript em busca de chamadas **`export default define<Entity>({...})`**. Cada tipo de entidade tem uma função utilitária correspondente exportada de `twenty-sdk`:
|
||||
|
||||
| Função utilitária | Tipo de entidade |
|
||||
| ---------------------------------- | ----------------------------------------------------- |
|
||||
| `defineObject()` | Definições de objetos personalizados |
|
||||
| `defineLogicFunction()` | Definições de funções de lógica |
|
||||
| `definePreInstallLogicFunction()` | Pre-install logic function (runs before installation) |
|
||||
| `definePostInstallLogicFunction()` | Post-install logic function (runs after installation) |
|
||||
| `defineFrontComponent()` | Definições de componentes de front-end |
|
||||
| `defineRole()` | Definições de papéis |
|
||||
| `defineField()` | Extensões de campos para objetos existentes |
|
||||
| `defineView()` | Definições de visualizações salvas |
|
||||
| `defineNavigationMenuItem()` | Definições de itens do menu de navegação |
|
||||
| `defineSkill()` | Definições de habilidades de agente de IA |
|
||||
| Função utilitária | Tipo de entidade |
|
||||
| ---------------------------- | ------------------------------------------- |
|
||||
| `defineObject()` | Definições de objetos personalizados |
|
||||
| `defineLogicFunction()` | Definições de funções de lógica |
|
||||
| `defineFrontComponent()` | Definições de componentes de front-end |
|
||||
| `defineRole()` | Definições de papéis |
|
||||
| `defineField()` | Extensões de campos para objetos existentes |
|
||||
| `defineView()` | Definições de visualizações salvas |
|
||||
| `defineNavigationMenuItem()` | Definições de itens do menu de navegação |
|
||||
| `defineSkill()` | Definições de habilidades de agente de IA |
|
||||
|
||||
<Note>
|
||||
**A nomeação de arquivos é flexível.** A detecção de entidades é baseada em AST — o SDK varre seus arquivos fonte em busca do padrão `export default define<Entity>({...})`. Você pode organizar seus arquivos e pastas como quiser. Agrupar por tipo de entidade (por exemplo, `logic-functions/`, `roles/`) é apenas uma convenção para organização do código, não um requisito.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
Comandos posteriores adicionarão mais arquivos e pastas:
|
||||
|
||||
* `yarn twenty app:dev` irá gerar automaticamente dois clientes de API tipados em `node_modules/twenty-sdk/generated`: `CoreApiClient` (para dados do espaço de trabalho via `/graphql`) e `MetadataApiClient` (para configuração do espaço de trabalho e envio de arquivos via `/metadata`).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` adicionará arquivos de definição de entidade em `src/` para seus objetos, funções, componentes de front-end, papéis e habilidades personalizados, entre outros.
|
||||
|
||||
## Autenticação
|
||||
@@ -215,19 +209,17 @@ O twenty-sdk fornece blocos de construção tipados e funções utilitárias que
|
||||
|
||||
O SDK fornece funções utilitárias para definir as entidades do seu app. Conforme descrito em [Detecção de entidades](#entity-detection), você deve usar `export default define<Entity>({...})` para que suas entidades sejam detectadas:
|
||||
|
||||
| Função | Finalidade |
|
||||
| ---------------------------------- | ------------------------------------------------------------ |
|
||||
| `defineApplication()` | Configurar metadados do aplicativo (obrigatório, um por app) |
|
||||
| `defineObject()` | Define objetos personalizados com campos |
|
||||
| `defineLogicFunction()` | Defina funções de lógica com handlers |
|
||||
| `definePreInstallLogicFunction()` | Define a pre-install logic function (one per app) |
|
||||
| `definePostInstallLogicFunction()` | Define a post-install logic function (one per app) |
|
||||
| `defineFrontComponent()` | Definir componentes de front-end para UI personalizada |
|
||||
| `defineRole()` | Configura permissões de papéis e acesso a objetos |
|
||||
| `defineField()` | Estender objetos existentes com campos adicionais |
|
||||
| `defineView()` | Define visualizações salvas para objetos |
|
||||
| `defineNavigationMenuItem()` | Define links de navegação da barra lateral |
|
||||
| `defineSkill()` | Define habilidades de agente de IA |
|
||||
| Função | Finalidade |
|
||||
| ---------------------------- | ------------------------------------------------------------ |
|
||||
| `defineApplication()` | Configurar metadados do aplicativo (obrigatório, um por app) |
|
||||
| `defineObject()` | Define objetos personalizados com campos |
|
||||
| `defineLogicFunction()` | Defina funções de lógica com handlers |
|
||||
| `defineFrontComponent()` | Definir componentes de front-end para UI personalizada |
|
||||
| `defineRole()` | Configura permissões de papéis e acesso a objetos |
|
||||
| `defineField()` | Estender objetos existentes com campos adicionais |
|
||||
| `defineView()` | Define visualizações salvas para objetos |
|
||||
| `defineNavigationMenuItem()` | Define links de navegação da barra lateral |
|
||||
| `defineSkill()` | Define habilidades de agente de IA |
|
||||
|
||||
Essas funções validam sua configuração em tempo de compilação e oferecem autocompletar na IDE e segurança de tipos.
|
||||
|
||||
@@ -327,7 +319,6 @@ Todo aplicativo tem um único arquivo `application-config.ts` que descreve:
|
||||
* **O que é o aplicativo**: identificadores, nome de exibição e descrição.
|
||||
* **Como suas funções são executadas**: qual papel usam para permissões.
|
||||
* **Variáveis (opcional)**: pares chave–valor expostos às suas funções como variáveis de ambiente.
|
||||
* **(Optional) pre-install function**: a logic function that runs before the app is installed.
|
||||
* **(Opcional) função de pós-instalação**: uma função de lógica que é executada após a instalação da aplicação.
|
||||
|
||||
Use `defineApplication()` to define your application configuration:
|
||||
@@ -336,21 +327,23 @@ Use `defineApplication()` to define your application configuration:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
displayName: 'Meu aplicativo Twenty',
|
||||
description: 'Meu primeiro aplicativo Twenty',
|
||||
displayName: 'My Twenty App',
|
||||
description: 'My first Twenty app',
|
||||
icon: 'IconWorld',
|
||||
applicationVariables: {
|
||||
DEFAULT_RECIPIENT_NAME: {
|
||||
universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de',
|
||||
description: 'Nome padrão do destinatário para cartões-postais',
|
||||
description: 'Default recipient name for postcards',
|
||||
value: 'Jane Doe',
|
||||
isSecret: false,
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Notas:
|
||||
* `universalIdentifier` são IDs determinísticos que você controla; gere-os uma vez e mantenha-os estáveis entre sincronizações.
|
||||
* `applicationVariables` tornam-se variáveis de ambiente para suas funções (por exemplo, `DEFAULT_RECIPIENT_NAME` fica disponível como `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` deve corresponder ao arquivo do papel (veja abaixo).
|
||||
* Pre-install and post-install functions are automatically detected during the manifest build. See [Pre-install functions](#pre-install-functions) and [Post-install functions](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (opcional) aponta para uma função de lógica que é executada automaticamente após a instalação da aplicação. Consulte [Funções de pós-instalação](#post-install-functions).
|
||||
|
||||
#### Papéis e permissões
|
||||
|
||||
@@ -498,44 +491,6 @@ Notas:
|
||||
* O array `triggers` é opcional. Funções sem gatilhos podem ser usadas como funções utilitárias chamadas por outras funções.
|
||||
* Você pode misturar vários tipos de gatilho em uma única função.
|
||||
|
||||
### Pre-install functions
|
||||
|
||||
A pre-install function is a logic function that runs automatically before your app is installed on a workspace. This is useful for validation tasks, prerequisite checks, or preparing workspace state before the main installation proceeds.
|
||||
|
||||
When you scaffold a new app with `create-twenty-app`, a pre-install function is generated for you at `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
You can also manually execute the pre-install function at any time using the CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Pontos-chave:
|
||||
|
||||
* Pre-install functions use `definePreInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
|
||||
* Only one pre-install function is allowed per application. The manifest build will error if more than one is detected.
|
||||
* The function's `universalIdentifier` is automatically set as `preInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
|
||||
* The default timeout is set to 300 seconds (5 minutes) to allow for longer preparation tasks.
|
||||
* Pre-install functions do not need triggers — they are invoked by the platform before installation or manually via `function:execute --preInstall`.
|
||||
|
||||
### Funções de pós-instalação
|
||||
|
||||
Uma função de pós-instalação é uma função de lógica que é executada automaticamente após a sua aplicação ser instalada em um espaço de trabalho. Isso é útil para tarefas de configuração únicas, como preencher dados padrão, criar registros iniciais ou configurar as configurações do espaço de trabalho.
|
||||
@@ -544,14 +499,16 @@ Ao criar a estrutura de um novo app com `create-twenty-app`, uma função de pó
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
A função é conectada ao seu app referenciando seu identificador universal em `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Você também pode executar manualmente a função de pós-instalação a qualquer momento usando a CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Pontos-chave:
|
||||
|
||||
* Post-install functions use `definePostInstallLogicFunction()` — a specialized variant that omits trigger settings (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* The handler receives an `InstallLogicFunctionPayload` with `{ previousVersion: string }` — the version of the app that was previously installed (or an empty string for fresh installs).
|
||||
* Only one post-install function is allowed per application. The manifest build will error if more than one is detected.
|
||||
* The function's `universalIdentifier` is automatically set as `postInstallLogicFunctionUniversalIdentifier` on the application manifest during the build — you do not need to reference it in `defineApplication()`.
|
||||
* As funções de pós-instalação são funções de lógica padrão — elas usam `defineLogicFunction()` como qualquer outra função.
|
||||
* O campo `postInstallLogicFunctionUniversalIdentifier` em `defineApplication()` é opcional. Se omitido, nenhuma função é executada após a instalação.
|
||||
* O tempo limite padrão é definido como 300 segundos (5 minutos) para permitir tarefas de configuração mais longas, como o pré-carregamento de dados.
|
||||
* As funções de pós-instalação não precisam de gatilhos — elas são invocadas pela plataforma durante a instalação ou manualmente via `function:execute --postInstall`.
|
||||
|
||||
@@ -757,7 +723,6 @@ export default defineFrontComponent({
|
||||
description: 'A custom widget component',
|
||||
component: MyWidget,
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
Pontos-chave:
|
||||
@@ -769,7 +734,7 @@ Pontos-chave:
|
||||
Você pode criar novos componentes de front-end de duas formas:
|
||||
|
||||
* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo componente de front-end.
|
||||
* **Manual**: Crie um novo ficheiro `.tsx` e use `defineFrontComponent()`, seguindo o mesmo padrão.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Habilidades
|
||||
|
||||
@@ -806,12 +771,12 @@ Você pode criar novas habilidades de duas formas:
|
||||
* **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar uma nova habilidade.
|
||||
* **Manual**: Crie um novo arquivo e use `defineSkill()`, seguindo o mesmo padrão.
|
||||
|
||||
### Clientes tipados gerados
|
||||
### Generated typed clients
|
||||
|
||||
Dois clientes tipados são gerados automaticamente pelo `yarn twenty app:dev` e armazenados em `node_modules/twenty-sdk/generated` com base no esquema do seu espaço de trabalho:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — consulta o endpoint `/graphql` para dados do espaço de trabalho
|
||||
* **`MetadataApiClient`** — consulta o endpoint `/metadata` para obter a configuração do espaço de trabalho e o carregamento de ficheiros
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -821,10 +786,9 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
|
||||
|
||||
const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
|
||||
```
|
||||
|
||||
Ambos os clientes são regenerados automaticamente pelo `yarn twenty app:dev` sempre que seus objetos ou campos forem alterados.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Credenciais em tempo de execução em funções de lógica
|
||||
|
||||
@@ -841,7 +805,7 @@ Notas:
|
||||
|
||||
#### Carregamento de ficheiros
|
||||
|
||||
O `MetadataApiClient` gerado inclui um método `uploadFile` para anexar ficheiros a campos do tipo ficheiro nos objetos do seu espaço de trabalho. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Como os clientes GraphQL padrão não suportam nativamente o carregamento de ficheiros multipart, o cliente fornece este método dedicado que implementa, nos bastidores, a [especificação de pedidos multipart do GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -860,7 +824,6 @@ const uploadedFile = await metadataClient.uploadFile(
|
||||
|
||||
console.log(uploadedFile);
|
||||
// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' }
|
||||
|
||||
```
|
||||
|
||||
A assinatura do método:
|
||||
@@ -883,7 +846,7 @@ uploadFile(
|
||||
|
||||
Pontos-chave:
|
||||
|
||||
* O método `uploadFile` está disponível no `MetadataApiClient` porque a mutação de upload é resolvida pelo endpoint `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Ele usa o `universalIdentifier` do campo (não o ID específico do espaço de trabalho), de modo que seu código de upload funcione em qualquer espaço de trabalho onde seu app esteja instalado — consistente com a forma como os apps referenciam campos em qualquer outro lugar.
|
||||
* A `url` retornada é um URL assinado que você pode usar para acessar o arquivo enviado.
|
||||
|
||||
|
||||
@@ -17,21 +17,21 @@ Um seletor de ícones baseado em lista suspensa que permite aos usuários seleci
|
||||
import React, { useState } from "react";
|
||||
import { IconPicker } from "@/ui/input/components/IconPicker";
|
||||
|
||||
export const MeuComponente = () => {
|
||||
export const MyComponent = () => {
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Ícone Selecionado:", iconKey);
|
||||
console.log("Selected Icon:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,19 +20,18 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Selecione uma opção"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Opção A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Opção B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Selecione uma opção"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Opção A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Opção B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
</Tab>
|
||||
|
||||
@@ -20,24 +20,24 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Entrada alterada:", text);
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Tecla pressionada:", event.key);
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Nome de usuário"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Nome de usuário inválido"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("função onValidate executada")}
|
||||
minRows={1}
|
||||
placeholder="Escreva um comentário"
|
||||
onFocus={() => console.log("função onFocus executada")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Tarefa: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ Acest document prezintă cele mai bune practici pe care ar trebui să le urmați
|
||||
|
||||
React și Jotai se ocupă de managementul stării în cod.
|
||||
|
||||
### Folosiți atomi Jotai pentru a stoca starea
|
||||
### Folosiți `useAtomState` pentru a stoca starea
|
||||
|
||||
Este o bună practică să creezi atâția atomi câți ai nevoie pentru a-ți stoca starea.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Evitați utilizarea `useRef` pentru a stoca starea.
|
||||
|
||||
Dacă doriți să stocați starea, ar trebui să folosiți `useState` sau atomii Jotai cu `useAtomState`.
|
||||
Dacă doriți să stocați starea, ar trebui să folosiți `useState` sau `useAtomState`.
|
||||
|
||||
Vezi [cum să gestionezi re-randările](#managing-re-renders) dacă ți se pare că ai nevoie de `useRef` pentru a preveni apariția unor re-randări.
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Folosiți stări de familie de atomi și selectoare
|
||||
### Folosiți stări de familie și selectoare de familie cu jotai
|
||||
|
||||
Stările de familie de atomi și selectoarele sunt o metodă excelentă de a evita re-randările.
|
||||
Stările și selectoarele de familie cu jotai sunt o metodă excelentă de a evita re-render-urile.
|
||||
|
||||
Sunt utile când trebuie să stocați o listă de elemente.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Vezi [Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) pentru mai
|
||||
|
||||
Conține logica de gestionare a stării. [Jotai](https://jotai.org) gestionează aceasta.
|
||||
|
||||
* Selectori: Atomi derivați (folosind `createAtomSelector`) calculează valori din alți atomi și sunt memoizați automat.
|
||||
* Selectori: Atomii derivați (folosind `createAtomSelector`) calculează valori din alți atomi și sunt memorați automat.
|
||||
|
||||
Managementul de stare încorporat în React gestionează încă starea în cadrul unei componente.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Intern, domeniul selectat în prezent este stocat într-un atom Jotai care este partajat în toată aplicația:
|
||||
Intern, domeniul selectat în prezent este stocat într-o stare Jotai care este partajată în toată aplicația:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Însă acest atom nu ar trebui niciodată să fie gestionat manual! Vom vedea cum să o folosim în secțiunea următoare.
|
||||
Însă această stare Jotai nu ar trebui să fie gestionată manual! Vom vedea cum să o folosim în secțiunea următoare.
|
||||
|
||||
## Cum funcționează intern?
|
||||
|
||||
Am făcut un wrapper subțire peste [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) care îl face mai performant și evită re-rendările inutile.
|
||||
|
||||
De asemenea, creăm un atom Jotai pentru a gestiona starea domeniului comenzilor rapide și pentru a o face disponibilă oriunde în aplicație.
|
||||
De asemenea, creăm o stare Jotai pentru a gestiona starea domeniului comenzilor rapide și să fie disponibilă oriunde în aplicație.
|
||||
|
||||
@@ -61,9 +61,6 @@ yarn twenty function:logs
|
||||
# Execută o funcție după nume
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Execută funcția de pre-instalare
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Execută funcția post-instalare
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
@@ -71,7 +68,7 @@ yarn twenty function:execute --postInstall
|
||||
yarn twenty app:uninstall
|
||||
|
||||
# Afișează ajutorul pentru comenzi
|
||||
yarn twenty help},{
|
||||
yarn twenty help
|
||||
```
|
||||
|
||||
Consultați și: paginile de referință CLI pentru [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) și [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
|
||||
@@ -83,7 +80,7 @@ Când rulați `npx create-twenty-app@latest my-twenty-app`, generatorul:
|
||||
* Copiază o aplicație de bază minimală în `my-twenty-app/`
|
||||
* Adaugă o dependență locală `twenty-sdk` și configurația Yarn 4
|
||||
* Creează fișiere de configurare și scripturi conectate la CLI-ul `twenty`
|
||||
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcțiile de pre-instalare și post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului.
|
||||
* Generează fișierele de bază (configurația aplicației, rolul implicit al funcțiilor, funcția post-instalare) plus fișiere de exemplu în funcție de modul de generare a scheletului
|
||||
|
||||
O aplicație proaspăt generată cu modul implicit `--exhaustive` arată astfel:
|
||||
|
||||
@@ -110,7 +107,6 @@ my-twenty-app/
|
||||
│ └── example-field.ts # Exemplu de definiție de câmp independent
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Exemplu de funcție logică
|
||||
│ ├── pre-install.ts # Funcție logică de pre-instalare
|
||||
│ └── post-install.ts # Funcție logică post-instalare
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Exemplu de componentă de interfață
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # Exemplu de definiție a unei abilități a agentului AI
|
||||
```
|
||||
|
||||
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` și `logic-functions/post-install.ts`). Cu `--interactive`, alegi ce fișiere de exemplu să incluzi.
|
||||
Cu `--minimal`, sunt create doar fișierele de bază (`application-config.ts`, `roles/default-role.ts` și `logic-functions/post-install.ts`). Cu `--interactive`, alegi ce fișiere de exemplu să incluzi.
|
||||
|
||||
Pe scurt:
|
||||
|
||||
@@ -139,18 +135,16 @@ Pe scurt:
|
||||
|
||||
SDK-ul detectează entitățile analizând fișierele TypeScript pentru apeluri **`export default define<Entity>({...})`**. Fiecare tip de entitate are o funcție ajutătoare corespunzătoare, exportată din `twenty-sdk`:
|
||||
|
||||
| Funcție ajutătoare | Tipul entității |
|
||||
| ---------------------------------- | -------------------------------------------------------------- |
|
||||
| `defineObject()` | Definiții de obiecte personalizate |
|
||||
| `defineLogicFunction()` | Definiții de funcții de logică |
|
||||
| `definePreInstallLogicFunction()` | Funcție logică de pre-instalare (rulează înainte de instalare) |
|
||||
| `definePostInstallLogicFunction()` | Funcție logică post-instalare (rulează după instalare) |
|
||||
| `defineFrontComponent()` | Definiții ale componentelor de interfață |
|
||||
| `defineRole()` | Definiții de rol |
|
||||
| `defineField()` | Extensii de câmp pentru obiectele existente |
|
||||
| `defineView()` | Definiții pentru vizualizări salvate |
|
||||
| `defineNavigationMenuItem()` | Definiții pentru elemente de meniu de navigare |
|
||||
| `defineSkill()` | Definiții ale abilităților agentului AI |
|
||||
| Funcție ajutătoare | Tipul entității |
|
||||
| ---------------------------- | ---------------------------------------------- |
|
||||
| `defineObject()` | Definiții de obiecte personalizate |
|
||||
| `defineLogicFunction()` | Definiții de funcții de logică |
|
||||
| `defineFrontComponent()` | Definiții ale componentelor de interfață |
|
||||
| `defineRole()` | Definiții de rol |
|
||||
| `defineField()` | Extensii de câmp pentru obiectele existente |
|
||||
| `defineView()` | Definiții pentru vizualizări salvate |
|
||||
| `defineNavigationMenuItem()` | Definiții pentru elemente de meniu de navigare |
|
||||
| `defineSkill()` | Definiții ale abilităților agentului AI |
|
||||
|
||||
<Note>
|
||||
**Denumirea fișierelor este flexibilă.** Detectarea entităților se bazează pe AST — SDK-ul scanează fișierele sursă pentru tiparul `export default define<Entity>({...})`. Puteți organiza fișierele și folderele cum doriți. Gruparea după tipul de entitate (de exemplu, `logic-functions/`, `roles/`) este doar o convenție pentru organizarea codului, nu o cerință.
|
||||
@@ -215,19 +209,17 @@ Biblioteca twenty-sdk oferă blocuri de bază tipizate și funcții ajutătoare
|
||||
|
||||
SDK-ul oferă funcții ajutătoare pentru definirea entităților aplicației. După cum este descris în [Detectarea entităților](#entity-detection), trebuie să folosiți `export default define<Entity>({...})` pentru ca entitățile să fie detectate:
|
||||
|
||||
| Funcție | Scop |
|
||||
| ---------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) |
|
||||
| `defineObject()` | Definiți obiecte personalizate cu câmpuri |
|
||||
| `defineLogicFunction()` | Definiți funcții de logică cu handleri |
|
||||
| `definePreInstallLogicFunction()` | Definește o funcție logică de pre-instalare (una per aplicație) |
|
||||
| `definePostInstallLogicFunction()` | Definește o funcție logică post-instalare (una per aplicație) |
|
||||
| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată |
|
||||
| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte |
|
||||
| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare |
|
||||
| `defineView()` | Definește vizualizări salvate pentru obiecte |
|
||||
| `defineNavigationMenuItem()` | Definește linkuri de navigare în bara laterală |
|
||||
| `defineSkill()` | Definiți abilități pentru agentul AI |
|
||||
| Funcție | Scop |
|
||||
| ---------------------------- | ---------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Configurați metadatele aplicației (obligatoriu, una per aplicație) |
|
||||
| `defineObject()` | Definiți obiecte personalizate cu câmpuri |
|
||||
| `defineLogicFunction()` | Definiți funcții de logică cu handleri |
|
||||
| `defineFrontComponent()` | Definiți componente Front pentru interfața de utilizator personalizată |
|
||||
| `defineRole()` | Configurați permisiunile rolurilor și accesul la obiecte |
|
||||
| `defineField()` | Extindeți obiectele existente cu câmpuri suplimentare |
|
||||
| `defineView()` | Definește vizualizări salvate pentru obiecte |
|
||||
| `defineNavigationMenuItem()` | Definește linkuri de navigare în bara laterală |
|
||||
| `defineSkill()` | Definiți abilități pentru agentul AI |
|
||||
|
||||
Aceste funcții validează configurația în timpul build-ului și oferă completare automată în IDE și siguranța tipurilor.
|
||||
|
||||
@@ -327,7 +319,6 @@ Fiecare aplicație are un singur fișier `application-config.ts` care descrie:
|
||||
* **Cine este aplicația**: identificatori, nume de afișare și descriere.
|
||||
* **Cum rulează funcțiile**: ce rol folosesc pentru permisiuni.
|
||||
* **(Opțional) variabile**: perechi cheie–valoare expuse funcțiilor ca variabile de mediu.
|
||||
* **(Opțional) funcție de pre-instalare**: o funcție logică care rulează înainte ca aplicația să fie instalată.
|
||||
* **(Opțional) funcție post-instalare**: o funcție logică care rulează după instalarea aplicației.
|
||||
|
||||
Folosiți `defineApplication()` pentru a defini configurația aplicației:
|
||||
@@ -336,6 +327,7 @@ Folosiți `defineApplication()` pentru a defini configurația aplicației:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Notițe:
|
||||
* Câmpurile `universalIdentifier` sunt ID-uri deterministe pe care le dețineți; generați-le o singură dată și păstrați-le stabile între sincronizări.
|
||||
* `applicationVariables` devin variabile de mediu pentru funcțiile dvs. (de exemplu, `DEFAULT_RECIPIENT_NAME` este disponibil ca `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` trebuie să corespundă fișierului de rol (vedeți mai jos).
|
||||
* Funcțiile de pre-instalare și post-instalare sunt detectate automat în timpul construirii manifestului. Vezi [Funcții de pre-instalare](#pre-install-functions) și [Funcții post-instalare](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (opțional) indică o funcție logică care rulează automat după instalarea aplicației. Vezi [Funcții post-instalare](#post-install-functions).
|
||||
|
||||
#### Roluri și permisiuni
|
||||
|
||||
@@ -498,44 +491,6 @@ Notițe:
|
||||
* Matricea `triggers` este opțională. Funcțiile fără declanșatoare pot fi folosite ca funcții utilitare apelate de alte funcții.
|
||||
* Puteți combina mai multe tipuri de declanșatoare într-o singură funcție.
|
||||
|
||||
### Funcții de pre-instalare
|
||||
|
||||
O funcție de pre-instalare este o funcție logică ce rulează automat înainte ca aplicația ta să fie instalată într-un spațiu de lucru. Aceasta este utilă pentru sarcini de validare, verificări ale condițiilor prealabile sau pregătirea stării spațiului de lucru înainte ca instalarea principală să continue.
|
||||
|
||||
Când creezi scheletul unei aplicații noi cu `create-twenty-app`, ți se generează o funcție de pre-instalare la `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Poți, de asemenea, să execuți manual funcția de pre-instalare oricând folosind CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Puncte cheie:
|
||||
|
||||
* Funcțiile de pre-instalare folosesc `definePreInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi).
|
||||
* Este permisă o singură funcție de pre-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una.
|
||||
* Proprietatea `universalIdentifier` a funcției este setată automat ca `preInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`.
|
||||
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de pregătire mai lungi.
|
||||
* Funcțiile de pre-instalare nu au nevoie de declanșatoare — sunt invocate de platformă înainte de instalare sau manual prin `function:execute --preInstall`.
|
||||
|
||||
### Funcții post-instalare
|
||||
|
||||
O funcție post-instalare este o funcție logică care rulează automat după instalarea aplicației într-un spațiu de lucru. Aceasta este utilă pentru sarcini de configurare unice, cum ar fi popularea cu date implicite, crearea înregistrărilor inițiale sau configurarea setărilor spațiului de lucru.
|
||||
@@ -544,14 +499,16 @@ Când creezi scheletul unei aplicații noi cu `create-twenty-app`, este generat
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Funcția este integrată în aplicația ta prin referirea la identificatorul său universal în `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Poți, de asemenea, să execuți manual funcția post-instalare oricând folosind CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Puncte cheie:
|
||||
|
||||
* Funcțiile de post-instalare folosesc `definePostInstallLogicFunction()` — o variantă specializată care omite setările de declanșare (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Handlerul primește un `InstallLogicFunctionPayload` cu `{ previousVersion: string }` — versiunea aplicației care a fost instalată anterior (sau un șir gol pentru instalări noi).
|
||||
* Este permisă o singură funcție de post-instalare per aplicație. Construirea manifestului va genera o eroare dacă este detectată mai mult de una.
|
||||
* Proprietatea `universalIdentifier` a funcției este setată automat ca `postInstallLogicFunctionUniversalIdentifier` în manifestul aplicației în timpul build-ului — nu este nevoie să o referi în `defineApplication()`.
|
||||
* Funcțiile post-instalare sunt funcții logice standard — folosesc `defineLogicFunction()` la fel ca orice altă funcție.
|
||||
* Câmpul `postInstallLogicFunctionUniversalIdentifier` din `defineApplication()` este opțional. Dacă este omis, nu rulează nicio funcție după instalare.
|
||||
* Timpul de expirare implicit este setat la 300 de secunde (5 minute) pentru a permite sarcini de configurare mai lungi, cum ar fi popularea datelor.
|
||||
* Funcțiile post-instalare nu au nevoie de declanșatoare — sunt invocate de platformă în timpul instalării sau manual prin `function:execute --postInstall`.
|
||||
|
||||
@@ -768,7 +734,7 @@ Puncte cheie:
|
||||
Puteți crea componente Front noi în două moduri:
|
||||
|
||||
* **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o componentă frontend nouă.
|
||||
* **Manual**: Creați un fișier nou `.tsx` și folosiți `defineFrontComponent()`, urmând același model.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Abilități
|
||||
|
||||
@@ -810,7 +776,7 @@ Puteți crea abilități noi în două moduri:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — interoghează endpointul `/metadata` pentru configurarea spațiului de lucru și încărcarea fișierelor
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Ambii clienți sunt regenerați automat de `yarn twenty app:dev` ori de câte ori obiectele sau câmpurile tale se schimbă.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Acreditări la runtime în funcțiile de logică
|
||||
|
||||
@@ -839,7 +805,7 @@ Notițe:
|
||||
|
||||
#### Încărcarea fișierelor
|
||||
|
||||
Clientul `MetadataApiClient` generat include o metodă `uploadFile` pentru atașarea fișierelor la câmpuri de tip fișier ale obiectelor din spațiul tău de lucru. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Deoarece clienții GraphQL standard nu acceptă în mod nativ încărcarea fișierelor multipart, clientul oferă această metodă dedicată care implementează, sub capotă, [specificația cererilor GraphQL multipart](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Puncte cheie:
|
||||
|
||||
* Metoda `uploadFile` este disponibilă pe `MetadataApiClient` deoarece mutația de încărcare este rezolvată de endpointul `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Folosește `universalIdentifier` al câmpului (nu ID-ul specific spațiului de lucru), astfel încât codul tău de încărcare funcționează în orice spațiu de lucru în care aplicația ta este instalată — în concordanță cu modul în care aplicațiile fac referire la câmpuri în rest.
|
||||
* `url` returnat este un URL semnat pe care îl poți folosi pentru a accesa fișierul încărcat.
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Select an option"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Select an option"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Option B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,24 +20,24 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Textul introdus s-a schimbat:", text);
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Tastă apăsată:", event.key);
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Nume de utilizator"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Nume de utilizator nevalid"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("funcția onValidate a fost declanșată")}
|
||||
minRows={1}
|
||||
placeholder="Scrie un comentariu"
|
||||
onFocus={() => console.log("funcția onFocus a fost declanșată")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Sarcină: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+8
-10
@@ -8,7 +8,7 @@ title: Лучшие практики
|
||||
|
||||
React и Jotai отвечают за управление состоянием в коде.
|
||||
|
||||
### Используйте атомы Jotai для хранения состояния
|
||||
### Используйте `useAtomState` для хранения состояния
|
||||
|
||||
Полезно создавать столько атомов, сколько вам нужно для хранения состояния.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Избегайте использования `useRef` для хранения состояния.
|
||||
|
||||
Если вы хотите сохранять состояние, используйте `useState` или атомы Jotai с `useAtomState`.
|
||||
Если вы хотите сохранить состояние, вам следует использовать `useState` или `useAtomState`.
|
||||
|
||||
Смотрите [как управлять повторными рендерами](#managing-re-renders), если вы считаете, что вам нужен `useRef`, чтобы предотвратить их.
|
||||
|
||||
@@ -82,8 +82,8 @@ export const MyComponent = () => {
|
||||
Вы можете применять то же самое для логики получения данных, с хуками Apollo.
|
||||
|
||||
```tsx
|
||||
// ❌ Плохо, вызовет повторные рендеры, даже если данные не меняются,
|
||||
// потому что useEffect нужно повторно вычислять
|
||||
// ❌ Bad, will cause re-renders even if data is not changing,
|
||||
// because useEffect needs to be re-evaluated
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
const [someDependency] = useAtomState(someDependencyState);
|
||||
@@ -100,12 +100,11 @@ export const PageComponent = () => {
|
||||
export const App = () => (
|
||||
<PageComponent />
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ✅ Хорошо, не вызовет повторные рендеры, если данные не меняются,
|
||||
// потому что useEffect пересчитывается в другом соседнем компоненте
|
||||
// ✅ Good, will not cause re-renders if data is not changing,
|
||||
// because useEffect is re-evaluated in another sibling component
|
||||
export const PageComponent = () => {
|
||||
const [data, setData] = useAtomState(dataState);
|
||||
|
||||
@@ -131,12 +130,11 @@ export const App = () => (
|
||||
<PageComponent />
|
||||
</>
|
||||
);
|
||||
|
||||
```
|
||||
|
||||
### Используйте состояния семейства атомов и селекторы
|
||||
### Используйте состояния семейства Jotai и селекторы семейства Jotai
|
||||
|
||||
Состояния семейства атомов и селекторы — отличный способ избежать повторных рендеров.
|
||||
Состояния семейства Jotai и селекторы — отличный способ избежать повторных рендеров.
|
||||
|
||||
Они полезны, когда нужно хранить список элементов.
|
||||
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ npx nx run twenty-front:storybook:coverage # (требуется yarn storybook:
|
||||
|
||||
### Управление состоянием
|
||||
|
||||
[Jotai](https://jotai.org/) отвечает за управление состоянием.
|
||||
[Jotai](https://jotai.org/) обрабатывает управление состоянием.
|
||||
|
||||
[лучшие практики](/l/ru/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) для получения дополнительной информации об управлении состоянием.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Внутренне текущая выбранная область действия хранится в атоме Jotai, который используется по всему приложению:
|
||||
Внутренне текущая выбранная область хранится в состоянии Jotai, которое используется по всему приложению:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Но этот атом никогда не следует обрабатывать вручную! Мы увидим, как использовать это в следующем разделе.
|
||||
Но это состояние Jotai никогда не следует обрабатывать вручную! Мы увидим, как использовать это в следующем разделе.
|
||||
|
||||
## Как это работает внутренне?
|
||||
|
||||
Мы сделали тонкую обертку поверх [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro), которая делает его более производительным и избегает ненужных повторных рендеров.
|
||||
|
||||
Мы также создаем атом Jotai, чтобы управлять состоянием области действия горячих клавиш и сделать его доступным везде в приложении.
|
||||
Мы также создаем состояние Jotai, чтобы управлять состоянием горячей клавиши и сделать его доступным везде в приложении.
|
||||
|
||||
@@ -59,10 +59,7 @@ yarn twenty entity:add
|
||||
yarn twenty function:logs
|
||||
|
||||
# Выполнить функцию по имени
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Выполнить предустановочную функцию
|
||||
yarn twenty function:execute --preInstall
|
||||
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
|
||||
|
||||
# Выполнить послеустановочную функцию
|
||||
yarn twenty function:execute --postInstall
|
||||
@@ -83,7 +80,7 @@ yarn twenty help
|
||||
* Копирует минимальное базовое приложение в `my-twenty-app/`
|
||||
* Добавляет локальную зависимость `twenty-sdk` и конфигурацию Yarn 4
|
||||
* Создаёт файлы конфигурации и скрипты, подключённые к CLI `twenty`
|
||||
* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, предустановочную и послеустановочную функции), а также примерные файлы в зависимости от выбранного режима создания каркаса
|
||||
* Генерирует основные файлы (конфигурацию приложения, роль функций по умолчанию, постустановочную функцию), а также примерные файлы в зависимости от выбранного режима создания каркаса
|
||||
|
||||
Сгенерированное с помощью каркаса приложение с режимом по умолчанию `--exhaustive` выглядит так:
|
||||
|
||||
@@ -110,8 +107,7 @@ my-twenty-app/
|
||||
│ └── example-field.ts # Пример определения отдельного поля
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Пример логической функции
|
||||
│ ├── pre-install.ts # Предустановочная логическая функция
|
||||
│ └── post-install.ts # Послеустановочная логическая функция
|
||||
│ └── post-install.ts # Постустановочная логическая функция
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Пример фронтенд-компонента
|
||||
├── views/
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # Пример определения навыка агента ИИ
|
||||
```
|
||||
|
||||
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` и `logic-functions/post-install.ts`). С `--interactive` вы выбираете, какие примерные файлы включить.
|
||||
С `--minimal` создаются только основные файлы (`application-config.ts`, `roles/default-role.ts` и `logic-functions/post-install.ts`). С `--interactive` вы выбираете, какие примерные файлы включить.
|
||||
|
||||
В общих чертах:
|
||||
|
||||
@@ -139,18 +135,16 @@ my-twenty-app/
|
||||
|
||||
SDK обнаруживает сущности, разбирая ваши файлы TypeScript в поисках вызовов **`export default define<Entity>({...})`**. Для каждого типа сущности существует соответствующая вспомогательная функция, экспортируемая из `twenty-sdk`:
|
||||
|
||||
| Вспомогательная функция | Тип сущности |
|
||||
| ---------------------------------- | ------------------------------------------------------------------ |
|
||||
| `defineObject()` | Определения пользовательских объектов |
|
||||
| `defineLogicFunction()` | Определения логических функций |
|
||||
| `definePreInstallLogicFunction()` | Предустановочная логическая функция (запускается до установки) |
|
||||
| `definePostInstallLogicFunction()` | Послеустановочная логическая функция (запускается после установки) |
|
||||
| `defineFrontComponent()` | Определения компонентов фронтенда |
|
||||
| `defineRole()` | Определения ролей |
|
||||
| `defineField()` | Расширения полей для существующих объектов |
|
||||
| `defineView()` | Определения сохранённых представлений |
|
||||
| `defineNavigationMenuItem()` | Определения пунктов меню навигации |
|
||||
| `defineSkill()` | Определения навыков агента ИИ |
|
||||
| Вспомогательная функция | Тип сущности |
|
||||
| ---------------------------- | ------------------------------------------ |
|
||||
| `defineObject()` | Определения пользовательских объектов |
|
||||
| `defineLogicFunction()` | Определения логических функций |
|
||||
| `defineFrontComponent()` | Определения компонентов фронтенда |
|
||||
| `defineRole()` | Определения ролей |
|
||||
| `defineField()` | Расширения полей для существующих объектов |
|
||||
| `defineView()` | Определения сохранённых представлений |
|
||||
| `defineNavigationMenuItem()` | Определения пунктов меню навигации |
|
||||
| `defineSkill()` | Определения навыков агента ИИ |
|
||||
|
||||
<Note>
|
||||
**Имена файлов заданы гибко.** Обнаружение сущностей основано на AST — SDK сканирует ваши исходные файлы в поисках шаблона `export default define<Entity>({...})`. Вы можете организовывать файлы и папки как угодно. Группировка по типу сущности (например, `logic-functions/`, `roles/`) — это лишь соглашение для организации кода, а не требование.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
Позднее команды добавят больше файлов и папок:
|
||||
|
||||
* `yarn twenty app:dev` автоматически сгенерирует два типизированных клиента API в `node_modules/twenty-sdk/generated`: `CoreApiClient` (для данных рабочего пространства через `/graphql`) и `MetadataApiClient` (для конфигурации рабочего пространства и загрузки файлов через `/metadata`).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` добавит файлы определений сущностей в `src/` для ваших пользовательских объектов, функций, фронтенд-компонентов, ролей, навыков и многого другого.
|
||||
|
||||
## Аутентификация
|
||||
@@ -215,19 +209,17 @@ yarn twenty auth:status
|
||||
|
||||
SDK предоставляет вспомогательные функции для определения сущностей вашего приложения. Как описано в [Обнаружение сущностей](#entity-detection), вы должны использовать `export default define<Entity>({...})`, чтобы ваши сущности были обнаружены:
|
||||
|
||||
| Функция | Назначение |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------ |
|
||||
| `defineApplication()` | Настройка метаданных приложения (обязательно, по одному на приложение) |
|
||||
| `defineObject()` | Определяет пользовательские объекты с полями |
|
||||
| `defineLogicFunction()` | Определение логических функций с обработчиками |
|
||||
| `definePreInstallLogicFunction()` | Определяет предустановочную логическую функцию (по одной на приложение) |
|
||||
| `definePostInstallLogicFunction()` | Определяет послеустановочную логическую функцию (по одной на приложение) |
|
||||
| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса |
|
||||
| `defineRole()` | Настраивает права роли и доступ к объектам |
|
||||
| `defineField()` | Расширение существующих объектов дополнительными полями |
|
||||
| `defineView()` | Определяйте сохранённые представления для объектов |
|
||||
| `defineNavigationMenuItem()` | Определяйте ссылки боковой панели навигации |
|
||||
| `defineSkill()` | Определение навыков агента ИИ |
|
||||
| Функция | Назначение |
|
||||
| ---------------------------- | ---------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Настройка метаданных приложения (обязательно, по одному на приложение) |
|
||||
| `defineObject()` | Определяет пользовательские объекты с полями |
|
||||
| `defineLogicFunction()` | Определение логических функций с обработчиками |
|
||||
| `defineFrontComponent()` | Определение фронт-компонентов для настраиваемого интерфейса |
|
||||
| `defineRole()` | Настраивает права роли и доступ к объектам |
|
||||
| `defineField()` | Расширение существующих объектов дополнительными полями |
|
||||
| `defineView()` | Определяйте сохранённые представления для объектов |
|
||||
| `defineNavigationMenuItem()` | Определяйте ссылки боковой панели навигации |
|
||||
| `defineSkill()` | Определение навыков агента ИИ |
|
||||
|
||||
Эти функции проверяют вашу конфигурацию на этапе сборки и обеспечивают автодополнение в IDE и безопасность типов.
|
||||
|
||||
@@ -327,7 +319,6 @@ export default defineObject({
|
||||
* **Что это за приложение**: идентификаторы, отображаемое имя и описание.
|
||||
* **Как запускаются его функции**: какую роль они используют для прав доступа.
|
||||
* **(Необязательно) переменные**: пары ключ-значение, предоставляемые вашим функциям как переменные окружения.
|
||||
* **(Необязательно) предустановочная функция**: логическая функция, которая запускается до установки приложения.
|
||||
* **(Необязательно) послеустановочная функция**: функция логики, которая запускается после установки приложения.
|
||||
|
||||
Используйте `defineApplication()` для определения конфигурации вашего приложения:
|
||||
@@ -336,6 +327,7 @@ export default defineObject({
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ export default defineApplication({
|
||||
* `universalIdentifier` — это детерминированные идентификаторы, которыми вы управляете; сгенерируйте их один раз и сохраняйте стабильными между синхронизациями.
|
||||
* `applicationVariables` становятся переменными окружения для ваших функций (например, `DEFAULT_RECIPIENT_NAME` доступна как `process.env.DEFAULT_RECIPIENT_NAME`).
|
||||
* `defaultRoleUniversalIdentifier` должен соответствовать файлу роли (см. ниже).
|
||||
* Предустановочные и послеустановочные функции автоматически обнаруживаются во время сборки манифеста. См. [Предустановочные функции](#pre-install-functions) и [Послеустановочные функции](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (необязательно) указывает на логическую функцию, которая автоматически выполняется после установки приложения. См. [Послеустановочные функции](#post-install-functions).
|
||||
|
||||
#### Роли и разрешения
|
||||
|
||||
@@ -498,44 +491,6 @@ export default defineLogicFunction({
|
||||
* Массив `triggers` необязателен. Функции без триггеров можно использовать как вспомогательные, вызываемые другими функциями.
|
||||
* Вы можете сочетать несколько типов триггеров в одной функции.
|
||||
|
||||
### Предустановочные функции
|
||||
|
||||
Предустановочная функция — это логическая функция, которая автоматически выполняется до установки вашего приложения в рабочем пространстве. Это полезно для задач валидации, проверки предварительных условий или подготовки состояния рабочего пространства перед основной установкой.
|
||||
|
||||
Когда вы создаёте каркас нового приложения с помощью `create-twenty-app`, для вас генерируется предустановочная функция по пути `src/logic-functions/pre-install.ts`:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Вы также можете вручную выполнить предустановочную функцию в любое время с помощью CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Основные моменты:
|
||||
|
||||
* Предустановочные функции используют `definePreInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок).
|
||||
* Для каждого приложения допускается только одна предустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции.
|
||||
* Параметр `universalIdentifier` функции автоматически устанавливается как `preInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`.
|
||||
* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы обеспечить выполнение более длительных задач подготовки.
|
||||
* Предустановочным функциям не нужны триггеры — платформа вызывает их перед установкой или вручную через `function:execute --preInstall`.
|
||||
|
||||
### Послеустановочные функции
|
||||
|
||||
Послеустановочная функция — это функция логики, которая автоматически выполняется после установки вашего приложения в рабочем пространстве. Это полезно для одноразовых задач настройки, таких как инициализация данных по умолчанию, создание начальных записей или настройка параметров рабочего пространства.
|
||||
@@ -544,14 +499,16 @@ yarn twenty function:execute --preInstall
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
Функция подключается к вашему приложению посредством ссылки на её универсальный идентификатор в `application-config.ts`:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Вы также можете вручную выполнить постустановочную функцию в любое время с помощью CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Основные моменты:
|
||||
|
||||
* Послеустановочные функции используют `definePostInstallLogicFunction()` — специализированный вариант, который опускает настройки триггеров (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`).
|
||||
* Обработчик получает `InstallLogicFunctionPayload` с `{ previousVersion: string }` — версией приложения, которая была установлена ранее (или пустой строкой для новых установок).
|
||||
* Для каждого приложения допускается только одна послеустановочная функция. Сборка манифеста завершится ошибкой, если будет обнаружено более одной такой функции.
|
||||
* Параметр `universalIdentifier` функции автоматически устанавливается как `postInstallLogicFunctionUniversalIdentifier` в манифесте приложения во время сборки — вам не нужно ссылаться на него в `defineApplication()`.
|
||||
* Постустановочные функции — это стандартные логические функции: они используют `defineLogicFunction()` как и любые другие функции.
|
||||
* Поле `postInstallLogicFunctionUniversalIdentifier` в `defineApplication()` является необязательным. Если его опустить, после установки никакая функция выполняться не будет.
|
||||
* Тайм-аут по умолчанию установлен на 300 секунд (5 минут), чтобы позволить выполнять более длительные задачи настройки, такие как инициализация данных.
|
||||
* Постустановочным функциям не нужны триггеры — платформа вызывает их во время установки или вручную через `function:execute --postInstall`.
|
||||
|
||||
@@ -719,7 +685,7 @@ export default defineLogicFunction({
|
||||
},
|
||||
required: ['companyName'],
|
||||
},
|
||||
});},{
|
||||
});
|
||||
```
|
||||
|
||||
Основные моменты:
|
||||
@@ -768,7 +734,7 @@ export default defineFrontComponent({
|
||||
Вы можете создать новые фронт-компоненты двумя способами:
|
||||
|
||||
* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового фронтенд-компонента.
|
||||
* **Вручную**: Создайте новый файл `.tsx` и используйте `defineFrontComponent()`, следуя тому же шаблону.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Навыки
|
||||
|
||||
@@ -805,12 +771,12 @@ export default defineSkill({
|
||||
* **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового навыка.
|
||||
* **Вручную**: Создайте новый файл и используйте `defineSkill()`, следуя тому же шаблону.
|
||||
|
||||
### Сгенерированные типизированные клиенты
|
||||
### Generated typed clients
|
||||
|
||||
Два типизированных клиента автоматически генерируются с помощью `yarn twenty app:dev` и сохраняются в `node_modules/twenty-sdk/generated` на основе схемы вашего рабочего пространства:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — выполняет запросы к конечной точке `/graphql` для получения данных рабочего пространства
|
||||
* **`MetadataApiClient`** — выполняет запросы к эндпоинту `/metadata` для получения конфигурации рабочего пространства и загрузки файлов.
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Оба клиента автоматически перегенерируются с помощью `yarn twenty app:dev` при изменении ваших объектов или полей.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Учётные данные времени выполнения в логических функциях
|
||||
|
||||
@@ -839,7 +805,7 @@ const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id
|
||||
|
||||
#### Загрузка файлов
|
||||
|
||||
Сгенерированный `MetadataApiClient` включает метод `uploadFile` для прикрепления файлов к полям типа «файл» в объектах вашего рабочего пространства. Поскольку стандартные клиенты GraphQL изначально не поддерживают многочастовую загрузку файлов, клиент предоставляет специальный метод, который под капотом реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Поскольку стандартные клиенты GraphQL изначально не поддерживают многочастовую загрузку файлов, клиент предоставляет специальный метод, который под капотом реализует [спецификацию многочастных запросов GraphQL](https://github.com/jaydenseric/graphql-multipart-request-spec).
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Основные моменты:
|
||||
|
||||
* Метод `uploadFile` доступен в `MetadataApiClient`, потому что мутация загрузки обрабатывается эндпоинтом `/metadata`.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Он использует `universalIdentifier` поля (а не его идентификатор, специфичный для рабочего пространства), поэтому ваш код загрузки будет работать в любом рабочем пространстве, где установлено ваше приложение — в соответствии с тем, как приложения ссылаются на поля повсюду.
|
||||
* Возвращаемый `url` — это подписанный URL, который можно использовать для доступа к загруженному файлу.
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@ export const MyComponent = () => {
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Выбранная иконка:", iconKey);
|
||||
console.log("Selected Icon:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Выберите вариант"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Вариант A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Вариант B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Выберите вариант"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Вариант A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Вариант B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -20,24 +20,24 @@ import { TextInput } from "@/ui/input/components/TextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
const handleChange = (text) => {
|
||||
console.log("Ввод изменён:", text);
|
||||
console.log("Input changed:", text);
|
||||
};
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
console.log("Нажата клавиша:", event.key);
|
||||
console.log("Key pressed:", event.key);
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Имя пользователя"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Недопустимое имя пользователя"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Username"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Invalid username"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("Функция onValidate вызвана")}
|
||||
minRows={1}
|
||||
placeholder="Напишите комментарий"
|
||||
onFocus={() => console.log("Функция onFocus вызвана")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Задача: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate function fired")}
|
||||
minRows={1}
|
||||
placeholder="Write a comment"
|
||||
onFocus={() => console.log("onFocus function fired")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Task: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ Bu belge, ön yüz üzerinde çalışırken takip etmeniz gereken en iyi uygulam
|
||||
|
||||
React ve Jotai, kod tabanında durumu yönetir.
|
||||
|
||||
### Durumu depolamak için Jotai atomlarını kullanın
|
||||
### Durumu depolamak için `useAtomState` kullanın.
|
||||
|
||||
Durumunuzu depolamak için ihtiyaç duyduğunuz kadar atom oluşturmak iyi bir uygulamadır.
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
Durum saklamak için `useRef` kullanmaktan kaçının.
|
||||
|
||||
Durum saklamak istiyorsanız, `useState` veya `useAtomState` ile Jotai atomlarını kullanmalısınız.
|
||||
Durum saklamak istiyorsanız, `useState` veya `useAtomState` kullanmalısınız.
|
||||
|
||||
Bazı yeniden render edilmelerin olmasını önlemek için `useRef`'e ihtiyacınız varmış gibi hissediyorsanız, [yeniden render yönetimi](#managing-re-renders) konusuna bakın.
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### Atom aile durumlarını ve seçimcilerini kullanın
|
||||
### Jotai aile durumlarını ve jotai aile seçimcilerini kullanın.
|
||||
|
||||
Atom aile durumları ve seçimcileri, yeniden render'ları önlemenin harika bir yoludur.
|
||||
Jotai aile durumları ve seçimcileri, yeniden render'ları önlemenin harika bir yoludur.
|
||||
|
||||
Bir öğe listesini saklamanız gerektiğinde kullanılabilirdir.
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ Daha fazla bilgi için [Kancalar](https://react.dev/learn/reusing-logic-with-cus
|
||||
|
||||
Durum yönetim mantığını içerir. [Jotai](https://jotai.org) bunu ele almaktadır.
|
||||
|
||||
* Seçiciler: Türetilmiş atomlar (`createAtomSelector` kullanılarak) diğer atomlardan değerleri hesaplar ve otomatik olarak önbelleğe alınır.
|
||||
* Seçiciler: Türetilmiş atomlar (`createAtomSelector` kullanılarak) diğer atomlardan değer hesaplar ve otomatik olarak önbelleğe alınır.
|
||||
|
||||
React'ın yerleşik durum yönetimi, bir bileşen içinde hala durumu ele alır.
|
||||
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ Gereksiz [yeniden renderların](/l/tr/developers/contribute/capabilities/fronten
|
||||
|
||||
### Durum Yönetimi
|
||||
|
||||
[Jotai](https://jotai.org/) durum yönetimini ele alır.
|
||||
Durum yönetimini [Jotai](https://jotai.org/) halleder.
|
||||
|
||||
Durum yönetimi hakkında daha fazla bilgi için [en iyi uygulamalar](/l/tr/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) bölümüne bakın.
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
Dahili olarak, şu anda seçili olan kapsam, uygulama genelinde paylaşılan bir Jotai atomunda saklanır:
|
||||
Dahili olarak, şu anda seçili olan kapsam, uygulama genelinde paylaşılan bir Jotai durumunda saklanır:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
Ancak bu atom asla el ile yönetilmemelidir! Bunu bir sonraki bölümde nasıl kullanacağımızı göreceğiz.
|
||||
Ancak bu Jotai durumu asla el ile yönetilmemelidir! Bunu bir sonraki bölümde nasıl kullanacağımızı göreceğiz.
|
||||
|
||||
## İçsel olarak nasıl çalışıyor?
|
||||
|
||||
Gereksiz yeniden render etme işlemlerinden kaçınarak daha performanslı hale getiren [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) üzerine ince bir sarmalayıcı yaptık.
|
||||
|
||||
Ayrıca, kısayol kapsamı durumunu yönetmek ve uygulamanın her yerinde kullanılabilir hale getirmek için bir Jotai atomu oluşturuyoruz.
|
||||
Ayrıca, kısayol kapsamı durumunu yönetmek ve uygulamanın her yerinde kullanılabilir hale getirmek için bir Jotai durumu oluşturuyoruz.
|
||||
|
||||
@@ -61,9 +61,6 @@ yarn twenty function:logs
|
||||
# Execute a function by name
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# Execute the pre-install function
|
||||
yarn twenty function:execute --preInstall
|
||||
|
||||
# Execute the post-install function
|
||||
yarn twenty function:execute --postInstall
|
||||
|
||||
@@ -83,7 +80,7 @@ Ayrıca bkz.: [create-twenty-app](https://www.npmjs.com/package/create-twenty-ap
|
||||
* Minimal bir temel uygulamayı `my-twenty-app/` içine kopyalar
|
||||
* Yerel bir `twenty-sdk` bağımlılığı ve Yarn 4 yapılandırması ekler
|
||||
* `twenty` CLI ile bağlantılı yapılandırma dosyaları ve betikler oluşturur
|
||||
* İskelet oluşturma moduna bağlı olarak çekirdek dosyaları (uygulama yapılandırması, varsayılan işlev rolü, kurulum öncesi ve kurulum sonrası işlevler) ile örnek dosyaları üretir
|
||||
* İskelet oluşturma moduna bağlı olarak çekirdek dosyaları (uygulama yapılandırması, varsayılan işlev rolü, kurulum sonrası işlev) ile örnek dosyaları üretir
|
||||
|
||||
Varsayılan `--exhaustive` moduyla yeni oluşturulmuş bir uygulama şu şekilde görünür:
|
||||
|
||||
@@ -110,7 +107,6 @@ my-twenty-app/
|
||||
│ └── example-field.ts # Örnek bağımsız alan tanımı
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # Örnek mantık fonksiyonu
|
||||
│ ├── pre-install.ts # Kurulum öncesi mantık fonksiyonu
|
||||
│ └── post-install.ts # Kurulum sonrası mantık fonksiyonu
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # Örnek ön bileşen
|
||||
@@ -122,7 +118,7 @@ my-twenty-app/
|
||||
└── example-skill.ts # Örnek yapay zekâ ajanı yetenek tanımı
|
||||
```
|
||||
|
||||
`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts`, `logic-functions/pre-install.ts` ve `logic-functions/post-install.ts`). `--interactive` ile hangi örnek dosyaların dahil edileceğini siz seçersiniz.
|
||||
`--minimal` ile yalnızca çekirdek dosyalar oluşturulur (`application-config.ts`, `roles/default-role.ts` ve `logic-functions/post-install.ts`). `--interactive` ile hangi örnek dosyaların dahil edileceğini siz seçersiniz.
|
||||
|
||||
Genel hatlarıyla:
|
||||
|
||||
@@ -139,18 +135,16 @@ Genel hatlarıyla:
|
||||
|
||||
SDK, TypeScript dosyalarınızı **`export default define<Entity>({...})`** çağrılarını arayarak ayrıştırıp varlıkları algılar. Her varlık türünün, `twenty-sdk` tarafından dışa aktarılan karşılık gelen bir yardımcı fonksiyonu vardır:
|
||||
|
||||
| Yardımcı fonksiyon | Varlık türü |
|
||||
| ---------------------------------- | -------------------------------------------------------- |
|
||||
| `defineObject()` | Özel nesne tanımları |
|
||||
| `defineLogicFunction()` | Mantık fonksiyon tanımları |
|
||||
| `definePreInstallLogicFunction()` | Kurulum öncesi mantık işlevi (kurulumdan önce çalışır) |
|
||||
| `definePostInstallLogicFunction()` | Kurulum sonrası mantık işlevi (kurulumdan sonra çalışır) |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | Rol tanımları |
|
||||
| `defineField()` | Mevcut nesneler için alan genişletmeleri |
|
||||
| `defineView()` | Kaydedilmiş görünüm tanımları |
|
||||
| `defineNavigationMenuItem()` | Gezinme menüsü öğesi tanımları |
|
||||
| `defineSkill()` | Yapay zekâ ajanı yetenek tanımları |
|
||||
| Yardımcı fonksiyon | Varlık türü |
|
||||
| ---------------------------- | ---------------------------------------- |
|
||||
| `defineObject()` | Özel nesne tanımları |
|
||||
| `defineLogicFunction()` | Mantık fonksiyon tanımları |
|
||||
| `defineFrontComponent()` | Front component definitions |
|
||||
| `defineRole()` | Rol tanımları |
|
||||
| `defineField()` | Mevcut nesneler için alan genişletmeleri |
|
||||
| `defineView()` | Kaydedilmiş görünüm tanımları |
|
||||
| `defineNavigationMenuItem()` | Gezinme menüsü öğesi tanımları |
|
||||
| `defineSkill()` | Yapay zekâ ajanı yetenek tanımları |
|
||||
|
||||
<Note>
|
||||
**Dosya adlandırma esnektir.** Varlık algılama AST tabanlıdır — SDK, kaynak dosyalarınızı `export default define<Entity>({...})` desenini bulmak için tarar. Dosyalarınızı ve klasörlerinizi dilediğiniz gibi düzenleyebilirsiniz. Varlık türüne göre gruplama (örn. `logic-functions/`, `roles/`) bir gereklilik değil, yalnızca kod organizasyonu için bir gelenektir.
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
İlerideki komutlar daha fazla dosya ve klasör ekleyecektir:
|
||||
|
||||
* `yarn twenty app:dev`, `node_modules/twenty-sdk/generated` içinde iki tiplendirilmiş API istemcisini otomatik olarak oluşturur: `CoreApiClient` (`/graphql` üzerinden çalışma alanı verileri için) ve `MetadataApiClient` (çalışma alanı yapılandırması ve `/metadata` üzerinden dosya yüklemeleri için).
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add`, özel nesneleriniz, fonksiyonlarınız, ön bileşenleriniz, rolleriniz, yetenekleriniz ve daha fazlası için `src/` altında varlık tanım dosyaları ekler.
|
||||
|
||||
## Kimlik Doğrulama
|
||||
@@ -215,19 +209,17 @@ twenty-sdk, uygulamanız içinde kullandığınız türlendirilmiş yapı taşla
|
||||
|
||||
SDK, uygulama varlıklarınızı tanımlamak için yardımcı fonksiyonlar sağlar. [Varlık algılama](#entity-detection) bölümünde açıklandığı gibi, varlıklarınızın algılanması için `export default define<Entity>({...})` kullanmalısınız:
|
||||
|
||||
| Fonksiyon | Amaç |
|
||||
| ---------------------------------- | ------------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) |
|
||||
| `defineObject()` | Alanlara sahip özel nesneler tanımlayın |
|
||||
| `defineLogicFunction()` | İşleyicilerle mantık fonksiyonları tanımlayın |
|
||||
| `definePreInstallLogicFunction()` | Bir kurulum öncesi mantık işlevi tanımlayın (uygulama başına bir adet) |
|
||||
| `definePostInstallLogicFunction()` | Bir kurulum sonrası mantık işlevi tanımlayın (uygulama başına bir adet) |
|
||||
| `defineFrontComponent()` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın |
|
||||
| `defineRole()` | Rol izinlerini ve nesne erişimini yapılandırın |
|
||||
| `defineField()` | Mevcut nesneleri ek alanlarla genişletin |
|
||||
| `defineView()` | Nesneler için kaydedilmiş görünümler tanımlayın |
|
||||
| `defineNavigationMenuItem()` | Kenar çubuğu gezinme bağlantılarını tanımlayın |
|
||||
| `defineSkill()` | Yapay zekâ ajanı yeteneklerini tanımlayın |
|
||||
| Fonksiyon | Amaç |
|
||||
| ---------------------------- | ------------------------------------------------------------------------- |
|
||||
| `defineApplication()` | Uygulama meta verilerini yapılandırın (zorunlu, uygulama başına bir adet) |
|
||||
| `defineObject()` | Alanlara sahip özel nesneler tanımlayın |
|
||||
| `defineLogicFunction()` | İşleyicilerle mantık fonksiyonları tanımlayın |
|
||||
| `defineFrontComponent()` | Özel kullanıcı arayüzü için ön uç bileşenlerini tanımlayın |
|
||||
| `defineRole()` | Rol izinlerini ve nesne erişimini yapılandırın |
|
||||
| `defineField()` | Mevcut nesneleri ek alanlarla genişletin |
|
||||
| `defineView()` | Nesneler için kaydedilmiş görünümler tanımlayın |
|
||||
| `defineNavigationMenuItem()` | Kenar çubuğu gezinme bağlantılarını tanımlayın |
|
||||
| `defineSkill()` | Yapay zekâ ajanı yeteneklerini tanımlayın |
|
||||
|
||||
Bu fonksiyonlar, derleme zamanında yapılandırmanızı doğrular ve IDE otomatik tamamlama ile tür güvenliği sağlar.
|
||||
|
||||
@@ -327,7 +319,6 @@ Her uygulamanın aşağıdakileri açıklayan tek bir `application-config.ts` do
|
||||
* **Uygulamanın kim olduğu**: tanımlayıcılar, görünen ad ve açıklama.
|
||||
* **Fonksiyonlarının nasıl çalıştığı**: izinler için hangi rolü kullandıkları.
|
||||
* **(İsteğe bağlı) değişkenler**: fonksiyonlarınıza ortam değişkenleri olarak sunulan anahtar–değer çiftleri.
|
||||
* **(İsteğe bağlı) kurulum öncesi işlev**: uygulama yüklenmeden önce çalışan bir mantık işlevi.
|
||||
* **(İsteğe bağlı) kurulum sonrası işlev**: uygulama yüklendikten sonra çalışan bir mantık işlevi.
|
||||
|
||||
Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın:
|
||||
@@ -336,6 +327,7 @@ Uygulama yapılandırmanızı tanımlamak için `defineApplication()` kullanın:
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ Notlar:
|
||||
* `universalIdentifier` alanları size ait belirleyici kimliklerdir; bunları bir kez oluşturun ve eşitlemeler boyunca kararlı tutun.
|
||||
* `applicationVariables`, fonksiyonlarınız için ortam değişkenlerine dönüşür (örneğin, `DEFAULT_RECIPIENT_NAME` değeri `process.env.DEFAULT_RECIPIENT_NAME` olarak kullanılabilir).
|
||||
* `defaultRoleUniversalIdentifier`, rol dosyasıyla eşleşmelidir (aşağıya bakın).
|
||||
* Kurulum öncesi ve kurulum sonrası işlevler, manifest oluşturma sırasında otomatik olarak algılanır. Bkz. [Kurulum öncesi işlevler](#pre-install-functions) ve [Kurulum sonrası işlevler](#post-install-functions).
|
||||
* `postInstallLogicFunctionUniversalIdentifier` (isteğe bağlı), uygulama yüklendikten sonra otomatik olarak çalışan bir mantık işlevine işaret eder. Bkz. [Kurulum sonrası işlevler](#post-install-functions).
|
||||
|
||||
#### Roller ve izinler
|
||||
|
||||
@@ -498,44 +491,6 @@ Notlar:
|
||||
* `triggers` dizisi isteğe bağlıdır. Tetikleyicisi olmayan fonksiyonlar, diğer fonksiyonlar tarafından çağrılan yardımcı fonksiyonlar olarak kullanılabilir.
|
||||
* Tek bir fonksiyonda birden çok tetikleyici türünü birleştirebilirsiniz.
|
||||
|
||||
### Kurulum öncesi işlevler
|
||||
|
||||
Kurulum öncesi işlev, uygulamanız bir çalışma alanına yüklenmeden önce otomatik olarak çalışan bir mantık işlevidir. Bu, doğrulama görevleri, önkoşul kontrolleri veya ana kurulum başlamadan önce çalışma alanı durumunun hazırlanması için yararlıdır.
|
||||
|
||||
`create-twenty-app` ile yeni bir uygulama iskeleti oluşturduğunuzda, `src/logic-functions/pre-install.ts` konumunda sizin için bir kurulum öncesi işlev oluşturulur:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
Ayrıca kurulum öncesi işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
Önemli noktalar:
|
||||
|
||||
* Kurulum öncesi işlevler `definePreInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır.
|
||||
* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize).
|
||||
* Uygulama başına yalnızca bir kurulum öncesi işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer.
|
||||
* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `preInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur.
|
||||
* Varsayılan zaman aşımı, daha uzun hazırlık görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır.
|
||||
* Kurulum öncesi işlevlerin tetikleyicilere ihtiyacı yoktur — kurulumdan önce platform tarafından veya `function:execute --preInstall` aracılığıyla manuel olarak çağrılırlar.
|
||||
|
||||
### Kurulum sonrası işlevler
|
||||
|
||||
Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonra otomatik olarak çalışan bir mantık işlevidir. Bu, varsayılan verileri tohumlama, ilk kayıtları oluşturma veya çalışma alanı ayarlarını yapılandırma gibi tek seferlik kurulum görevleri için yararlıdır.
|
||||
@@ -544,14 +499,16 @@ Kurulum sonrası işlev, uygulamanız bir çalışma alanına yüklendikten sonr
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
İşlev, `application-config.ts` içinde evrensel tanımlayıcısına başvurularak uygulamanıza bağlanır:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
Ayrıca kurulum sonrası işlevi istediğiniz zaman CLI kullanarak manuel olarak çalıştırabilirsiniz:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
Önemli noktalar:
|
||||
|
||||
* Kurulum sonrası işlevler `definePostInstallLogicFunction()` kullanır — tetikleyici ayarlarını atlayan (`cronTriggerSettings`, `databaseEventTriggerSettings`, `httpRouteTriggerSettings`, `isTool`) özel bir varyanttır.
|
||||
* İşleyici, `{ previousVersion: string }` içeren bir `InstallLogicFunctionPayload` alır — daha önce yüklü olan uygulamanın sürümü (veya yeni kurulumlar için boş bir dize).
|
||||
* Uygulama başına yalnızca bir kurulum sonrası işlevine izin verilir. Birden fazla tespit edilirse manifest oluşturma hataya düşer.
|
||||
* İşlevin `universalIdentifier` değeri, oluşturma sırasında uygulama manifestinde otomatik olarak `postInstallLogicFunctionUniversalIdentifier` olarak ayarlanır — `defineApplication()` içinde buna atıfta bulunmanıza gerek yoktur.
|
||||
* Kurulum sonrası işlevleri standart mantık işlevleridir — diğer herhangi bir işlev gibi `defineLogicFunction()` kullanırlar.
|
||||
* `defineApplication()` içindeki `postInstallLogicFunctionUniversalIdentifier` alanı isteğe bağlıdır. Atlanırsa, kurulumdan sonra hiçbir işlev çalıştırılmaz.
|
||||
* Varsayılan zaman aşımı, veri tohumlama gibi daha uzun kurulum görevlerine izin vermek için 300 saniye (5 dakika) olarak ayarlanmıştır.
|
||||
* Kurulum sonrası işlevlerin tetikleyicilere ihtiyacı yoktur — kurulum sırasında platform tarafından veya `function:execute --postInstall` aracılığıyla manuel olarak çağrılırlar.
|
||||
|
||||
@@ -768,7 +734,7 @@ export default defineFrontComponent({
|
||||
Yeni ön uç bileşenlerini iki şekilde oluşturabilirsiniz:
|
||||
|
||||
* **Şablondan**: `yarn twenty entity:add` çalıştırın ve yeni bir ön uç bileşeni ekleme seçeneğini seçin.
|
||||
* **Manuel**: Aynı deseni izleyerek yeni bir `.tsx` dosyası oluşturun ve `defineFrontComponent()` kullanın.
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### Beceriler
|
||||
|
||||
@@ -805,12 +771,12 @@ Yeni yetenekleri iki şekilde oluşturabilirsiniz:
|
||||
* **Şablondan**: `yarn twenty entity:add` komutunu çalıştırın ve yeni bir yetenek ekleme seçeneğini seçin.
|
||||
* **Manuel**: Yeni bir dosya oluşturun ve aynı deseni izleyerek `defineSkill()` kullanın.
|
||||
|
||||
### Oluşturulan tiplendirilmiş istemciler
|
||||
### Generated typed clients
|
||||
|
||||
Çalışma alanı şemanıza göre `yarn twenty app:dev` tarafından iki tiplendirilmiş istemci otomatik olarak oluşturulur ve `node_modules/twenty-sdk/generated` içine kaydedilir:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — çalışma alanı verileri için `/graphql` uç noktasını sorgular
|
||||
* **`MetadataApiClient`** — çalışma alanı yapılandırması ve dosya yüklemeleri için `/metadata` uç noktasını sorgular.
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
Her iki istemci de, nesneleriniz veya alanlarınız değiştiğinde, `yarn twenty app:dev` tarafından otomatik olarak yeniden oluşturulur.
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### Mantık fonksiyonlarında çalışma zamanı kimlik bilgileri
|
||||
|
||||
@@ -839,7 +805,7 @@ Notlar:
|
||||
|
||||
#### Dosya yükleme
|
||||
|
||||
Oluşturulan `MetadataApiClient`, çalışma alanı nesnelerinizdeki dosya türündeki alanlara dosya eklemek için bir `uploadFile` yöntemi içerir. Standart GraphQL istemcileri çok parçalı dosya yüklemelerini yerel olarak desteklemediğinden, istemci arka planda [GraphQL çok parçalı istek belirtimi](https://github.com/jaydenseric/graphql-multipart-request-spec) uygulayan bu özel yöntemi sağlar.
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Standart GraphQL istemcileri çok parçalı dosya yüklemelerini yerel olarak desteklemediğinden, istemci arka planda [GraphQL çok parçalı istek belirtimi](https://github.com/jaydenseric/graphql-multipart-request-spec) uygulayan bu özel yöntemi sağlar.
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
Önemli noktalar:
|
||||
|
||||
* `uploadFile` yöntemi, yükleme mutasyonu `/metadata` uç noktası tarafından çözümlendiği için `MetadataApiClient` üzerinde mevcuttur.
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* Alan için `universalIdentifier` kullanılır (çalışma alanına özgü kimliği değil), böylece yükleme kodunuz uygulamanızın yüklü olduğu herhangi bir çalışma alanında çalışır — uygulamaların başka her yerde alanlara nasıl atıfta bulunduğuyla tutarlıdır.
|
||||
* Döndürülen `url`, yüklenen dosyaya erişmek için kullanabileceğiniz imzalı bir URL'dir.
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@ export const MyComponent = () => {
|
||||
|
||||
const [selectedIcon, setSelectedIcon] = useState("");
|
||||
const handleIconChange = ({ iconKey, Icon }) => {
|
||||
console.log("Seçilen Simge:", iconKey);
|
||||
console.log("Selected Icon:", iconKey);
|
||||
setSelectedIcon(iconKey);
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Bir seçenek seçin"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Seçenek A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Seçenek B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="Bir seçenek seçin"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Seçenek A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: 'Seçenek B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,16 +28,16 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="Kullanıcı adı"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Geçersiz kullanıcı adı"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="Kullanıcı adı"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="Geçersiz kullanıcı adı"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},{
|
||||
@@ -82,15 +82,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate fonksiyonu tetiklendi")}
|
||||
minRows={1}
|
||||
placeholder="Bir yorum yazın"
|
||||
onFocus={() => console.log("onFocus fonksiyonu tetiklendi")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Görev: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate fonksiyonu tetiklendi")}
|
||||
minRows={1}
|
||||
placeholder="Bir yorum yazın"
|
||||
onFocus={() => console.log("onFocus fonksiyonu tetiklendi")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="Görev: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
+4
-4
@@ -8,7 +8,7 @@ title: 最佳实践
|
||||
|
||||
React 和 Jotai 在代码库中处理状态管理。
|
||||
|
||||
### 使用 Jotai 原子来存储状态
|
||||
### 使用 `useAtomState` 来存储状态
|
||||
|
||||
根据需要创建足够多的原子来存储你的状态,是一种良好实践。
|
||||
|
||||
@@ -45,7 +45,7 @@ export const MyComponent = () => {
|
||||
|
||||
避免使用 `useRef` 来存储状态。
|
||||
|
||||
如果想存储状态,应该使用 `useState` 或配合 `useAtomState` 的 Jotai 原子。
|
||||
如果想存储状态,应该使用 `useState` 或 `useAtomState`。
|
||||
|
||||
如果您觉得需要通过使用 `useRef` 来防止一些再渲染,请了解[如何管理再渲染](#managing-re-renders)。
|
||||
|
||||
@@ -132,9 +132,9 @@ export const App = () => (
|
||||
);
|
||||
```
|
||||
|
||||
### 使用原子族状态和选择器
|
||||
### 使用 Jotai 状态族和 Jotai 选择器族
|
||||
|
||||
原子族状态和选择器是避免重新渲染的好方法。
|
||||
Jotai 状态族和选择器族是避免再渲染的好方法。
|
||||
|
||||
当您需要存储项目列表时,它们很有用。
|
||||
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ module1
|
||||
|
||||
包含状态管理逻辑。 [Jotai](https://jotai.org) 处理这部分。
|
||||
|
||||
* 选择器:派生 Atom(使用 `createAtomSelector`)根据其他 Atom 计算出值,并会自动记忆化。
|
||||
* 选择器:派生原子(使用 `createAtomSelector`)从其他原子计算值,并自动进行记忆化。
|
||||
|
||||
React 内置状态管理仍然在组件内处理状态。
|
||||
|
||||
|
||||
+3
-3
@@ -160,7 +160,7 @@ export enum PageHotkeyScope {
|
||||
}
|
||||
```
|
||||
|
||||
在内部,当前选择的范围存储在整个应用程序共享的 Jotai 原子中:
|
||||
在内部,当前选择的范围存储在整个应用程序共享的 Jotai 状态中:
|
||||
|
||||
```tsx
|
||||
export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
@@ -169,10 +169,10 @@ export const currentHotkeyScopeState = createState<HotkeyScope>({
|
||||
});
|
||||
```
|
||||
|
||||
但这个原子不应该手动处理! 我们将在下一节中学习如何使用它。
|
||||
但这个 Jotai 状态不应该手动处理! 我们将在下一节中学习如何使用它。
|
||||
|
||||
## 内部是如何运作的?
|
||||
|
||||
我们在 [react-hotkeys-hook](https://react-hotkeys-hook.vercel.app/docs/intro) 之上制作了一个薄包装,使其性能更高并避免不必要的重新渲染。
|
||||
|
||||
我们还创建了一个 Jotai 原子来处理快捷键范围状态,并使其在整个应用程序中可用。
|
||||
我们还创建了一个 Jotai 状态来处理快捷键范围状态,并使其在整个应用程序中可用。
|
||||
|
||||
@@ -59,10 +59,7 @@ yarn twenty entity:add
|
||||
yarn twenty function:logs
|
||||
|
||||
# 按名称执行一个函数
|
||||
yarn twenty function:execute -n my-function -p '{"name": "test"}'
|
||||
|
||||
# 执行安装前函数
|
||||
yarn twenty function:execute --preInstall
|
||||
yarn twenty function:execute -n my-function -p '{\"name\": \"test\"}'
|
||||
|
||||
# 执行安装后函数
|
||||
yarn twenty function:execute --postInstall
|
||||
@@ -83,7 +80,7 @@ yarn twenty help
|
||||
* 将一个最小的基础应用复制到 `my-twenty-app/` 中
|
||||
* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置
|
||||
* 创建与 `twenty` CLI 关联的配置文件和脚本
|
||||
* 生成核心文件(应用配置、默认函数角色、安装前/安装后函数),并基于脚手架模式生成示例文件
|
||||
* Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
|
||||
|
||||
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
|
||||
|
||||
@@ -99,30 +96,29 @@ my-twenty-app/
|
||||
eslint.config.mjs
|
||||
tsconfig.json
|
||||
README.md
|
||||
public/ # 公共资源文件夹(图片、字体等)
|
||||
public/ # Public assets folder (images, fonts, etc.)
|
||||
src/
|
||||
├── application-config.ts # 必需 - 主应用配置
|
||||
├── application-config.ts # Required - main application configuration
|
||||
├── roles/
|
||||
│ └── default-role.ts # 逻辑函数的默认角色
|
||||
│ └── default-role.ts # Default role for logic functions
|
||||
├── objects/
|
||||
│ └── example-object.ts # 示例自定义对象定义
|
||||
│ └── example-object.ts # Example custom object definition
|
||||
├── fields/
|
||||
│ └── example-field.ts # 示例独立字段定义
|
||||
│ └── example-field.ts # Example standalone field definition
|
||||
├── logic-functions/
|
||||
│ ├── hello-world.ts # 示例逻辑函数
|
||||
│ ├── pre-install.ts # 安装前逻辑函数
|
||||
│ └── post-install.ts # 安装后逻辑函数
|
||||
│ ├── hello-world.ts # Example logic function
|
||||
│ └── post-install.ts # Post-install logic function
|
||||
├── front-components/
|
||||
│ └── hello-world.tsx # 示例前端组件
|
||||
│ └── hello-world.tsx # Example front component
|
||||
├── views/
|
||||
│ └── example-view.ts # 示例已保存视图定义
|
||||
│ └── example-view.ts # Example saved view definition
|
||||
├── navigation-menu-items/
|
||||
│ └── example-navigation-menu-item.ts # 示例侧边栏导航链接
|
||||
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
|
||||
└── skills/
|
||||
└── example-skill.ts # 示例 AI 代理技能定义
|
||||
└── example-skill.ts # Example AI agent skill definition
|
||||
```
|
||||
|
||||
使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。 With `--interactive`, you choose which example files to include.
|
||||
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
|
||||
|
||||
总体来说:
|
||||
|
||||
@@ -139,18 +135,16 @@ my-twenty-app/
|
||||
|
||||
该 SDK 通过在你的 TypeScript 文件中解析 **`export default define<Entity>({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数:
|
||||
|
||||
| 辅助函数 | 实体类型 |
|
||||
| ---------------------------------- | -------------------------------- |
|
||||
| `defineObject()` | 自定义对象定义 |
|
||||
| `defineLogicFunction()` | 逻辑函数定义 |
|
||||
| `definePreInstallLogicFunction()` | 安装前逻辑函数(在安装之前运行) |
|
||||
| `definePostInstallLogicFunction()` | 安装后逻辑函数(在安装之后运行) |
|
||||
| `defineFrontComponent()` | 前端组件定义 |
|
||||
| `defineRole()` | 角色定义 |
|
||||
| `defineField()` | 现有对象的字段扩展 |
|
||||
| `defineView()` | Saved view definitions |
|
||||
| `defineNavigationMenuItem()` | Navigation menu item definitions |
|
||||
| `defineSkill()` | AI agent skill definitions |
|
||||
| 辅助函数 | 实体类型 |
|
||||
| ---------------------------- | -------------------------------- |
|
||||
| `defineObject()` | 自定义对象定义 |
|
||||
| `defineLogicFunction()` | 逻辑函数定义 |
|
||||
| `defineFrontComponent()` | 前端组件定义 |
|
||||
| `defineRole()` | 角色定义 |
|
||||
| `defineField()` | 现有对象的字段扩展 |
|
||||
| `defineView()` | Saved view definitions |
|
||||
| `defineNavigationMenuItem()` | Navigation menu item definitions |
|
||||
| `defineSkill()` | AI agent skill definitions |
|
||||
|
||||
<Note>
|
||||
**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define<Entity>({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。
|
||||
@@ -171,7 +165,7 @@ export default defineObject({
|
||||
|
||||
后续命令将添加更多文件和文件夹:
|
||||
|
||||
* `yarn twenty app:dev` 会在 `node_modules/twenty-sdk/generated` 中自动生成两个类型化 API 客户端:`CoreApiClient`(通过 `/graphql` 获取工作区数据)和 `MetadataApiClient`(通过 `/metadata` 处理工作区配置和文件上传)。
|
||||
* `yarn twenty app:dev` will auto-generate two typed API clients in `node_modules/twenty-sdk/generated`: `CoreApiClient` (for workspace data via `/graphql`) and `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`).
|
||||
* `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
|
||||
|
||||
## 身份验证
|
||||
@@ -215,19 +209,17 @@ twenty-sdk 提供你在应用中使用的类型化构件和辅助函数。 以
|
||||
|
||||
该 SDK 提供辅助函数用于定义你的应用实体。 如 [实体检测](#entity-detection) 中所述,你必须使用 `export default define<Entity>({...})` 才能让你的实体被检测到:
|
||||
|
||||
| 函数 | 目的 |
|
||||
| ---------------------------------- | ------------------------------- |
|
||||
| `defineApplication()` | 配置应用元数据(必需,每个应用一个) |
|
||||
| `defineObject()` | 定义带字段的自定义对象 |
|
||||
| `defineLogicFunction()` | 定义带处理程序的逻辑函数 |
|
||||
| `definePreInstallLogicFunction()` | 定义一个安装前逻辑函数(每个应用一个) |
|
||||
| `definePostInstallLogicFunction()` | 定义一个安装后逻辑函数(每个应用一个) |
|
||||
| `defineFrontComponent()` | 为自定义 UI 定义前端组件 |
|
||||
| `defineRole()` | 配置角色权限和对象访问 |
|
||||
| `defineField()` | 为现有对象扩展额外字段 |
|
||||
| `defineView()` | Define saved views for objects |
|
||||
| `defineNavigationMenuItem()` | Define sidebar navigation links |
|
||||
| `defineSkill()` | Define AI agent skills |
|
||||
| 函数 | 目的 |
|
||||
| ---------------------------- | ------------------------------- |
|
||||
| `defineApplication()` | 配置应用元数据(必需,每个应用一个) |
|
||||
| `defineObject()` | 定义带字段的自定义对象 |
|
||||
| `defineLogicFunction()` | 定义带处理程序的逻辑函数 |
|
||||
| `defineFrontComponent()` | 为自定义 UI 定义前端组件 |
|
||||
| `defineRole()` | 配置角色权限和对象访问 |
|
||||
| `defineField()` | 为现有对象扩展额外字段 |
|
||||
| `defineView()` | Define saved views for objects |
|
||||
| `defineNavigationMenuItem()` | Define sidebar navigation links |
|
||||
| `defineSkill()` | Define AI agent skills |
|
||||
|
||||
这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。
|
||||
|
||||
@@ -327,7 +319,6 @@ export default defineObject({
|
||||
* **应用的身份**:标识符、显示名称和描述。
|
||||
* **函数如何运行**:它们用于权限的角色。
|
||||
* **(可选)变量**:以环境变量形式提供给函数的键值对。
|
||||
* **(可选)安装前函数**:在应用安装之前运行的逻辑函数。
|
||||
* **(可选)安装后函数**:在应用安装后运行的逻辑函数。
|
||||
|
||||
使用 `defineApplication()` 定义你的应用配置:
|
||||
@@ -336,6 +327,7 @@ export default defineObject({
|
||||
// src/application-config.ts
|
||||
import { defineApplication } from 'twenty-sdk';
|
||||
import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role';
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7',
|
||||
@@ -351,6 +343,7 @@ export default defineApplication({
|
||||
},
|
||||
},
|
||||
defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
@@ -359,7 +352,7 @@ export default defineApplication({
|
||||
* `universalIdentifier` 字段是你拥有的确定性 ID;生成一次并在多次同步中保持稳定。
|
||||
* `applicationVariables` 会变成函数可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。
|
||||
* `defaultRoleUniversalIdentifier` 必须与角色文件一致(见下文)。
|
||||
* 清单构建期间会自动检测安装前和安装后函数。 参见 [安装前函数](#pre-install-functions) 和 [安装后函数](#post-install-functions)。
|
||||
* `postInstallLogicFunctionUniversalIdentifier`(可选)指向一个在应用安装后自动运行的逻辑函数。 参见 [安装后函数](#post-install-functions)。
|
||||
|
||||
#### 角色和权限
|
||||
|
||||
@@ -498,44 +491,6 @@ export default defineLogicFunction({
|
||||
* `triggers` 数组是可选的。 没有触发器的函数可作为实用函数,被其他函数调用。
|
||||
* 你可以在单个函数中混用多种触发器类型。
|
||||
|
||||
### 安装前函数
|
||||
|
||||
安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。
|
||||
|
||||
当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/pre-install.ts` 为你生成一个安装前函数:
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/pre-install.ts
|
||||
import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Pre install logic function executed successfully!', payload.previousVersion);
|
||||
};
|
||||
|
||||
export default definePreInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
name: 'pre-install',
|
||||
description: 'Runs before installation to prepare the application.',
|
||||
timeoutSeconds: 300,
|
||||
handler,
|
||||
});
|
||||
```
|
||||
|
||||
你也可以随时使用 CLI 手动执行安装前函数:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty function:execute --preInstall
|
||||
```
|
||||
|
||||
关键点:
|
||||
|
||||
* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。
|
||||
* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。
|
||||
* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。
|
||||
* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。
|
||||
* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。
|
||||
* 安装前函数不需要触发器——它们会在安装前由平台调用,或通过 `function:execute --preInstall` 手动调用。
|
||||
|
||||
### 安装后函数
|
||||
|
||||
安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。
|
||||
@@ -544,14 +499,16 @@ yarn twenty function:execute --preInstall
|
||||
|
||||
```typescript
|
||||
// src/logic-functions/post-install.ts
|
||||
import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk';
|
||||
import { defineLogicFunction } from 'twenty-sdk';
|
||||
|
||||
const handler = async (payload: InstallLogicFunctionPayload): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!', payload.previousVersion);
|
||||
export const POST_INSTALL_UNIVERSAL_IDENTIFIER = '<generated-uuid>';
|
||||
|
||||
const handler = async (): Promise<void> => {
|
||||
console.log('Post install logic function executed successfully!');
|
||||
};
|
||||
|
||||
export default definePostInstallLogicFunction({
|
||||
universalIdentifier: '<generated-uuid>',
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'post-install',
|
||||
description: 'Runs after installation to set up the application.',
|
||||
timeoutSeconds: 300,
|
||||
@@ -559,6 +516,17 @@ export default definePostInstallLogicFunction({
|
||||
});
|
||||
```
|
||||
|
||||
通过在 `application-config.ts` 中引用其通用标识符,可将该函数接入你的应用:
|
||||
|
||||
```typescript
|
||||
import { POST_INSTALL_UNIVERSAL_IDENTIFIER } from 'src/logic-functions/post-install';
|
||||
|
||||
export default defineApplication({
|
||||
// ...
|
||||
postInstallLogicFunctionUniversalIdentifier: POST_INSTALL_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
You can also manually execute the post-install function at any time using the CLI:
|
||||
|
||||
```bash filename="Terminal"
|
||||
@@ -567,10 +535,8 @@ yarn twenty function:execute --postInstall
|
||||
|
||||
关键点:
|
||||
|
||||
* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。
|
||||
* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。
|
||||
* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。
|
||||
* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。
|
||||
* Post-install functions are standard logic functions — they use `defineLogicFunction()` like any other function.
|
||||
* The `postInstallLogicFunctionUniversalIdentifier` field in `defineApplication()` is optional. If omitted, no function runs after installation.
|
||||
* The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding.
|
||||
* Post-install functions do not need triggers — they are invoked by the platform during installation or manually via `function:execute --postInstall`.
|
||||
|
||||
@@ -768,7 +734,7 @@ export default defineFrontComponent({
|
||||
你可以通过两种方式创建新的前端组件:
|
||||
|
||||
* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新前端组件的选项。
|
||||
* **手动**:创建一个新的 `.tsx` 文件,并使用 `defineFrontComponent()`,遵循相同的模式。
|
||||
* **Manual**: Create a new `.tsx` file and use `defineFrontComponent()`, following the same pattern.
|
||||
|
||||
### 技能
|
||||
|
||||
@@ -805,12 +771,12 @@ You can create new skills in two ways:
|
||||
* **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill.
|
||||
* **Manual**: Create a new file and use `defineSkill()`, following the same pattern.
|
||||
|
||||
### 生成的类型化客户端
|
||||
### Generated typed clients
|
||||
|
||||
两个类型化客户端由 `yarn twenty app:dev` 自动生成(基于你的工作区架构),并存放在 `node_modules/twenty-sdk/generated`:
|
||||
Two typed clients are auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema:
|
||||
|
||||
* **`CoreApiClient`** — 查询 `/graphql` 端点以获取工作区数据
|
||||
* **`MetadataApiClient`** — 查询 `/metadata` 端点以获取工作区配置并处理文件上传
|
||||
* **`CoreApiClient`** — queries the `/graphql` endpoint for workspace data
|
||||
* **`MetadataApiClient`** — queries the `/metadata` endpoint for workspace configuration and file uploads
|
||||
|
||||
```typescript
|
||||
import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -822,7 +788,7 @@ const metadataClient = new MetadataApiClient();
|
||||
const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } });
|
||||
```
|
||||
|
||||
每当你的对象或字段发生变化时,`yarn twenty app:dev` 都会自动重新生成这两个客户端。
|
||||
Both clients are re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
|
||||
|
||||
#### 逻辑函数中的运行时凭据
|
||||
|
||||
@@ -839,7 +805,7 @@ const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id
|
||||
|
||||
#### 上传文件
|
||||
|
||||
生成的 `MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到你的工作区对象的文件类型字段。 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。
|
||||
The generated `MetadataApiClient` includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。
|
||||
|
||||
```typescript
|
||||
import { MetadataApiClient } from 'twenty-sdk/generated';
|
||||
@@ -880,7 +846,7 @@ uploadFile(
|
||||
|
||||
关键点:
|
||||
|
||||
* `uploadFile` 方法可在 `MetadataApiClient` 上使用,因为上传 mutation 由 `/metadata` 端点解析。
|
||||
* The `uploadFile` method is available on `MetadataApiClient` because the upload mutation is resolved by the `/metadata` endpoint.
|
||||
* 它使用该字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可以在安装了你的应用的任何工作区中使用——这与应用在其他地方引用字段的方式保持一致。
|
||||
* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
<IconPicker
|
||||
disabled={false}
|
||||
onChange={handleIconChange}
|
||||
selectedIconKey={selectedIcon}
|
||||
variant="primary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -20,16 +20,16 @@ import { Select } from '@/ui/input/components/Select';
|
||||
export const MyComponent = () => {
|
||||
|
||||
return (
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="选择一个选项"
|
||||
options={[
|
||||
{ value: 'option1', label: '选项 A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: '选项 B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
<Select
|
||||
className
|
||||
disabled={false}
|
||||
label="选择一个选项"
|
||||
options={[
|
||||
{ value: 'option1', label: '选项 A', Icon: IconTwentyStar },
|
||||
{ value: 'option2', label: '选项 B', Icon: IconTwentyStar },
|
||||
]}
|
||||
value="option1"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -28,19 +28,18 @@ export const MyComponent = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
className
|
||||
label="用户名"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="用户名无效"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
<TextInput
|
||||
className
|
||||
label="用户名"
|
||||
onChange={handleChange}
|
||||
fullWidth={false}
|
||||
disableHotkeys={false}
|
||||
error="用户名无效"
|
||||
onKeyDown={handleKeyDown}
|
||||
RightIcon={null}
|
||||
/>
|
||||
);
|
||||
};
|
||||
},{
|
||||
```
|
||||
|
||||
|
||||
@@ -82,15 +81,15 @@ import { AutosizeTextInput } from "@/ui/input/components/AutosizeTextInput";
|
||||
|
||||
export const MyComponent = () => {
|
||||
return (
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate 函数已触发")}
|
||||
minRows={1}
|
||||
placeholder="写一条评论"
|
||||
onFocus={() => console.log("onFocus 函数已触发")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="任务: "
|
||||
/>
|
||||
<AutosizeTextInput
|
||||
onValidate={() => console.log("onValidate 函数已触发")}
|
||||
minRows={1}
|
||||
placeholder="写一条评论"
|
||||
onFocus={() => console.log("onFocus 函数已触发")}
|
||||
variant="icon"
|
||||
buttonTitle
|
||||
value="任务: "
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
src/generated
|
||||
src/generated-metadata
|
||||
src/testing/mock-data/generated
|
||||
src/generated-metadata
|
||||
@@ -1,10 +1,6 @@
|
||||
import { setProjectAnnotations } from '@storybook/react-vite';
|
||||
import * as projectAnnotations from './preview';
|
||||
|
||||
// Pre-warm the dynamic import used by WorkflowStepDecorator so the
|
||||
// module is cached before any test runs (avoids flaky timeouts in CI).
|
||||
import('~/testing/utils/generatedMockObjectMetadataItems');
|
||||
|
||||
// This is an important step to apply the right configuration when testing your stories.
|
||||
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||
setProjectAnnotations([projectAnnotations]);
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
"dependencies": {
|
||||
"@ai-sdk/react": "3.0.99",
|
||||
"@apollo/client": "^3.7.17",
|
||||
"@blocknote/mantine": "0.47.0",
|
||||
"@blocknote/react": "0.47.0",
|
||||
"@blocknote/xl-docx-exporter": "0.47.0",
|
||||
"@blocknote/xl-pdf-exporter": "0.47.0",
|
||||
"@blocknote/mantine": "^0.31.1",
|
||||
"@blocknote/react": "^0.31.1",
|
||||
"@blocknote/xl-docx-exporter": "^0.31.1",
|
||||
"@blocknote/xl-pdf-exporter": "^0.31.1",
|
||||
"@calcom/embed-react": "^1.5.3",
|
||||
"@cyntler/react-doc-viewer": "^1.17.0",
|
||||
"@dagrejs/dagre": "^1.1.2",
|
||||
@@ -46,9 +46,6 @@
|
||||
"@lingui/core": "^5.1.2",
|
||||
"@lingui/detect-locale": "^5.2.0",
|
||||
"@lingui/react": "^5.1.2",
|
||||
"@mantine/core": "^8.3.11",
|
||||
"@mantine/hooks": "^8.3.11",
|
||||
"@mantine/utils": "^6.0.22",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@nivo/core": "^0.99.0",
|
||||
"@nivo/line": "^0.99.0",
|
||||
@@ -81,6 +78,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"cron-parser": "5.1.1",
|
||||
"date-fns": "^2.30.0",
|
||||
"docx": "^9.1.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"graphiql": "^3.1.1",
|
||||
"graphql": "16.8.1",
|
||||
@@ -144,7 +142,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"eslint-plugin-storybook": "^10.2.13",
|
||||
"eslint-plugin-storybook": "^0.9.0",
|
||||
"eslint-plugin-unicorn": "^56.0.1",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"monaco-editor": "^0.51.0",
|
||||
|
||||
@@ -259,7 +259,7 @@
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "{projectRoot}",
|
||||
"command": "dotenv npx vite-node scripts/generate-mock-data.ts"
|
||||
"command": "dotenv npx tsx scripts/generate-mock-data.ts"
|
||||
}
|
||||
},
|
||||
"chromatic": {
|
||||
|
||||
@@ -1,36 +1,281 @@
|
||||
/* eslint-disable no-console */
|
||||
import { generateApiKeys } from './mock-data/generate-api-keys.js';
|
||||
import { generateBillingPlans } from './mock-data/generate-billing-plans.js';
|
||||
import { generateMetadata } from './mock-data/generate-metadata.js';
|
||||
import { generateRecordData } from './mock-data/generate-record-data.js';
|
||||
import { generateRoles } from './mock-data/generate-roles.js';
|
||||
import { generateViews } from './mock-data/generate-views.js';
|
||||
import { authenticate } from './mock-data/utils.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
const SERVER_BASE_URL =
|
||||
process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000';
|
||||
const AUTH_EMAIL = 'tim@apple.dev';
|
||||
const AUTH_PASSWORD = 'tim@apple.dev';
|
||||
const WORKSPACE_SUBDOMAIN = 'apple';
|
||||
|
||||
const serverUrl = new URL(SERVER_BASE_URL);
|
||||
const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
|
||||
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const OUTPUT_DIR = path.resolve(
|
||||
currentDir,
|
||||
'../src/testing/mock-data/generated',
|
||||
);
|
||||
|
||||
const graphqlRequest = async (
|
||||
endpoint: string,
|
||||
query: string,
|
||||
token?: string,
|
||||
): Promise<unknown> => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: WORKSPACE_ORIGIN,
|
||||
};
|
||||
|
||||
if (token !== undefined) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${SERVER_BASE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ query }),
|
||||
});
|
||||
|
||||
const json = (await response.json()) as {
|
||||
data?: unknown;
|
||||
errors?: { message: string }[];
|
||||
};
|
||||
|
||||
if (
|
||||
json.errors !== undefined &&
|
||||
json.errors !== null &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
const errorDetails = json.errors.map((error) => error.message).join(', ');
|
||||
throw new Error(`GraphQL error on ${endpoint}: ${errorDetails}`);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
};
|
||||
|
||||
const authenticate = async (): Promise<string> => {
|
||||
console.log(
|
||||
`Authenticating as ${AUTH_EMAIL} on workspace ${WORKSPACE_SUBDOMAIN}...`,
|
||||
);
|
||||
|
||||
const loginData = (await graphqlRequest(
|
||||
'/metadata',
|
||||
`mutation GetLoginTokenFromCredentials {
|
||||
getLoginTokenFromCredentials(
|
||||
email: "${AUTH_EMAIL}",
|
||||
password: "${AUTH_PASSWORD}",
|
||||
origin: "${WORKSPACE_ORIGIN}"
|
||||
) {
|
||||
loginToken { token }
|
||||
}
|
||||
}`,
|
||||
)) as {
|
||||
getLoginTokenFromCredentials: { loginToken: { token: string } };
|
||||
};
|
||||
|
||||
const loginToken = loginData.getLoginTokenFromCredentials.loginToken.token;
|
||||
|
||||
const authData = (await graphqlRequest(
|
||||
'/metadata',
|
||||
`mutation GetAuthTokensFromLoginToken {
|
||||
getAuthTokensFromLoginToken(
|
||||
loginToken: "${loginToken}",
|
||||
origin: "${WORKSPACE_ORIGIN}"
|
||||
) {
|
||||
tokens {
|
||||
accessOrWorkspaceAgnosticToken { token }
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)) as {
|
||||
getAuthTokensFromLoginToken: {
|
||||
tokens: { accessOrWorkspaceAgnosticToken: { token: string } };
|
||||
};
|
||||
};
|
||||
|
||||
const accessToken =
|
||||
authData.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken
|
||||
.token;
|
||||
|
||||
console.log('Authenticated successfully.');
|
||||
return accessToken;
|
||||
};
|
||||
|
||||
// Apollo Client automatically adds __typename to every object level;
|
||||
// raw fetch does not, so we include it explicitly here.
|
||||
const METADATA_QUERY = `
|
||||
query ObjectMetadataItems {
|
||||
objects(paging: { first: 1000 }) {
|
||||
__typename
|
||||
edges {
|
||||
__typename
|
||||
node {
|
||||
__typename
|
||||
id
|
||||
universalIdentifier
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isRemote
|
||||
isActive
|
||||
isSystem
|
||||
isUIReadOnly
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
applicationId
|
||||
shortcut
|
||||
isLabelSyncedWithName
|
||||
isSearchable
|
||||
duplicateCriteria
|
||||
indexMetadataList {
|
||||
__typename
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
name
|
||||
indexWhereClause
|
||||
indexType
|
||||
isUnique
|
||||
isCustom
|
||||
indexFieldMetadataList {
|
||||
__typename
|
||||
id
|
||||
fieldMetadataId
|
||||
createdAt
|
||||
updatedAt
|
||||
order
|
||||
}
|
||||
}
|
||||
fieldsList {
|
||||
__typename
|
||||
id
|
||||
universalIdentifier
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
isUIReadOnly
|
||||
isNullable
|
||||
isUnique
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
settings
|
||||
isLabelSyncedWithName
|
||||
morphId
|
||||
applicationId
|
||||
relation {
|
||||
__typename
|
||||
type
|
||||
sourceObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
targetFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
morphRelations {
|
||||
__typename
|
||||
type
|
||||
sourceObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
targetFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
__typename
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const main = async () => {
|
||||
console.log(`Server: ${SERVER_BASE_URL}`);
|
||||
console.log(`Output: ${OUTPUT_DIR}`);
|
||||
console.log('');
|
||||
|
||||
const token = await authenticate();
|
||||
|
||||
const metadata = await generateMetadata(token);
|
||||
await generateRecordData(token, metadata);
|
||||
await generateRoles(token);
|
||||
await generateViews(token);
|
||||
console.log('Fetching object metadata from /metadata ...');
|
||||
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
|
||||
|
||||
try {
|
||||
await generateBillingPlans(token);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Skipping billing plans generation (billing not available):',
|
||||
(error as Error).message,
|
||||
);
|
||||
}
|
||||
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||
|
||||
try {
|
||||
await generateApiKeys(token);
|
||||
} catch (error) {
|
||||
console.warn('Skipping API keys generation:', (error as Error).message);
|
||||
}
|
||||
const filePath = path.join(OUTPUT_DIR, 'mock-metadata-query-result.ts');
|
||||
const content = [
|
||||
'/* eslint-disable */',
|
||||
'// @ts-nocheck',
|
||||
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
|
||||
'',
|
||||
'// This file was automatically generated by scripts/generate-mock-data.ts',
|
||||
'// Do not edit this file manually.',
|
||||
'',
|
||||
'// prettier-ignore',
|
||||
'export const mockedStandardObjectMetadataQueryResult: ObjectMetadataItemsQuery =',
|
||||
JSON.stringify(metadata, null, 2) + ';',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
console.log('All mock data generated!');
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(`Written: ${filePath}`);
|
||||
console.log('Done!');
|
||||
};
|
||||
|
||||
main().catch((error) => {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
const API_KEYS_QUERY = `
|
||||
query ApiKeys {
|
||||
apiKeys {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
createdAt
|
||||
updatedAt
|
||||
revokedAt
|
||||
role {
|
||||
__typename
|
||||
id
|
||||
label
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateApiKeys = async (token: string) => {
|
||||
console.log('Fetching API keys from /metadata ...');
|
||||
|
||||
const data = (await graphqlRequest('/metadata', API_KEYS_QUERY, token)) as {
|
||||
apiKeys: Record<string, unknown>[];
|
||||
};
|
||||
|
||||
console.log(` Got ${data.apiKeys.length} API keys.`);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/api-keys/mock-api-keys-data.ts',
|
||||
'mockedApiKeys',
|
||||
'Record<string, unknown>[]',
|
||||
'',
|
||||
data.apiKeys,
|
||||
);
|
||||
};
|
||||
@@ -1,68 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
const LIST_PLANS_QUERY = `
|
||||
query listPlans {
|
||||
listPlans {
|
||||
planKey
|
||||
licensedProducts {
|
||||
name
|
||||
description
|
||||
images
|
||||
metadata {
|
||||
productKey
|
||||
planKey
|
||||
priceUsageBased
|
||||
}
|
||||
... on BillingLicensedProduct {
|
||||
prices {
|
||||
stripePriceId
|
||||
unitAmount
|
||||
recurringInterval
|
||||
priceUsageType
|
||||
}
|
||||
}
|
||||
}
|
||||
meteredProducts {
|
||||
name
|
||||
description
|
||||
images
|
||||
metadata {
|
||||
productKey
|
||||
planKey
|
||||
priceUsageBased
|
||||
}
|
||||
... on BillingMeteredProduct {
|
||||
prices {
|
||||
priceUsageType
|
||||
recurringInterval
|
||||
stripePriceId
|
||||
tiers {
|
||||
flatAmount
|
||||
unitAmount
|
||||
upTo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateBillingPlans = async (token: string) => {
|
||||
console.log('Fetching billing plans from /metadata ...');
|
||||
|
||||
const data = (await graphqlRequest('/metadata', LIST_PLANS_QUERY, token)) as {
|
||||
listPlans: Record<string, unknown>[];
|
||||
};
|
||||
|
||||
console.log(` Got ${data.listPlans.length} billing plans.`);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/billing-plans/mock-billing-plans-data.ts',
|
||||
'mockedBillingPlans',
|
||||
'Record<string, unknown>',
|
||||
'',
|
||||
{ listPlans: data.listPlans },
|
||||
);
|
||||
};
|
||||
@@ -1,160 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
// Apollo Client automatically adds __typename to every object level;
|
||||
// raw fetch does not, so we include it explicitly here.
|
||||
const METADATA_QUERY = `
|
||||
query ObjectMetadataItems {
|
||||
objects(paging: { first: 1000 }) {
|
||||
__typename
|
||||
edges {
|
||||
__typename
|
||||
node {
|
||||
__typename
|
||||
id
|
||||
universalIdentifier
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isRemote
|
||||
isActive
|
||||
isSystem
|
||||
isUIReadOnly
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
applicationId
|
||||
shortcut
|
||||
isLabelSyncedWithName
|
||||
isSearchable
|
||||
duplicateCriteria
|
||||
indexMetadataList {
|
||||
__typename
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
name
|
||||
indexWhereClause
|
||||
indexType
|
||||
isUnique
|
||||
isCustom
|
||||
indexFieldMetadataList {
|
||||
__typename
|
||||
id
|
||||
fieldMetadataId
|
||||
createdAt
|
||||
updatedAt
|
||||
order
|
||||
}
|
||||
}
|
||||
fieldsList {
|
||||
__typename
|
||||
id
|
||||
universalIdentifier
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
isUIReadOnly
|
||||
isNullable
|
||||
isUnique
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
settings
|
||||
isLabelSyncedWithName
|
||||
morphId
|
||||
applicationId
|
||||
relation {
|
||||
__typename
|
||||
type
|
||||
sourceObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
targetFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
morphRelations {
|
||||
__typename
|
||||
type
|
||||
sourceObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetObjectMetadata {
|
||||
__typename
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
targetFieldMetadata {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
__typename
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateMetadata = async (token: string) => {
|
||||
console.log('Fetching object metadata from /metadata ...');
|
||||
|
||||
const metadata = await graphqlRequest('/metadata', METADATA_QUERY, token);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/objects/mock-objects-metadata.ts',
|
||||
'mockedStandardObjectMetadataQueryResult',
|
||||
'ObjectMetadataItemsQuery',
|
||||
"import { ObjectMetadataItemsQuery } from '~/generated-metadata/graphql';",
|
||||
metadata,
|
||||
);
|
||||
|
||||
return metadata as {
|
||||
objects: { edges: { node: Record<string, unknown> }[] };
|
||||
};
|
||||
};
|
||||
@@ -1,113 +0,0 @@
|
||||
/* eslint-disable no-console, lingui/no-unlocalized-strings */
|
||||
import { print } from 'graphql';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
|
||||
import { generateFindManyRecordsQuery } from '@/object-record/utils/generateFindManyRecordsQuery';
|
||||
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
const RECORDS_LIMIT = 10;
|
||||
|
||||
const OBJECTS_TO_GENERATE = [
|
||||
'company',
|
||||
'person',
|
||||
'task',
|
||||
'note',
|
||||
'timelineActivity',
|
||||
'workspaceMember',
|
||||
'favorite',
|
||||
'favoriteFolder',
|
||||
'connectedAccount',
|
||||
'calendarEvent',
|
||||
];
|
||||
|
||||
// Production query builders omit __typename on connection/edge wrappers
|
||||
// since Apollo Client injects them automatically. Raw fetch needs them explicit.
|
||||
const addTypenamesToSelections = (query: string): string =>
|
||||
query.replace(/\{(?!\s*__typename\b)/g, '{\n__typename');
|
||||
|
||||
const toObjectMetadataItems = (rawMetadata: {
|
||||
objects: { edges: { node: Record<string, unknown> }[] };
|
||||
}): ObjectMetadataItem[] =>
|
||||
rawMetadata.objects.edges.map((edge) => {
|
||||
const { fieldsList, indexMetadataList, ...rest } = edge.node;
|
||||
|
||||
return {
|
||||
...rest,
|
||||
fields: fieldsList,
|
||||
readableFields: fieldsList,
|
||||
updatableFields: fieldsList,
|
||||
indexMetadatas: ((indexMetadataList as unknown[]) ?? []).map(
|
||||
(index: any) => ({
|
||||
...index,
|
||||
indexFieldMetadatas: index.indexFieldMetadataList ?? [],
|
||||
}),
|
||||
),
|
||||
} as unknown as ObjectMetadataItem;
|
||||
});
|
||||
|
||||
const generateForObject = async (
|
||||
token: string,
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
objectNameSingular: string,
|
||||
) => {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
throw new Error(`${objectNameSingular} object metadata not found`);
|
||||
}
|
||||
|
||||
const recordGqlFields = generateDepthRecordGqlFieldsFromObject({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
const queryDocument = generateFindManyRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
|
||||
const query = addTypenamesToSelections(print(queryDocument));
|
||||
|
||||
console.log(
|
||||
`Fetching ${objectMetadataItem.namePlural} (limit: ${RECORDS_LIMIT}) from /graphql ...`,
|
||||
);
|
||||
|
||||
const data = (await graphqlRequest('/graphql', query, token, {
|
||||
limit: RECORDS_LIMIT,
|
||||
})) as Record<string, { edges: { node: Record<string, unknown> }[] }>;
|
||||
|
||||
const records = data[objectMetadataItem.namePlural].edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
|
||||
console.log(` Got ${records.length} ${objectMetadataItem.namePlural}.`);
|
||||
|
||||
const pascalName =
|
||||
objectNameSingular.charAt(0).toUpperCase() + objectNameSingular.slice(1);
|
||||
|
||||
writeGeneratedFile(
|
||||
`data/${objectMetadataItem.namePlural}/mock-${objectMetadataItem.namePlural}-data.ts`,
|
||||
`mocked${pascalName}Records`,
|
||||
'ObjectRecord[]',
|
||||
"import { type ObjectRecord } from '@/object-record/types/ObjectRecord';",
|
||||
records,
|
||||
);
|
||||
};
|
||||
|
||||
export const generateRecordData = async (
|
||||
token: string,
|
||||
rawMetadata: { objects: { edges: { node: Record<string, unknown> }[] } },
|
||||
) => {
|
||||
const objectMetadataItems = toObjectMetadataItems(rawMetadata);
|
||||
|
||||
for (const objectNameSingular of OBJECTS_TO_GENERATE) {
|
||||
await generateForObject(token, objectMetadataItems, objectNameSingular);
|
||||
}
|
||||
};
|
||||
@@ -1,150 +0,0 @@
|
||||
/* eslint-disable no-console, lingui/no-unlocalized-strings */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
// Inline the full GetRoles query with all fragments so we don't need
|
||||
// @apollo/client at generation time.
|
||||
const GET_ROLES_QUERY = `
|
||||
query GetRoles {
|
||||
getRoles {
|
||||
__typename
|
||||
id
|
||||
label
|
||||
description
|
||||
icon
|
||||
canUpdateAllSettings
|
||||
canAccessAllTools
|
||||
isEditable
|
||||
canReadAllObjectRecords
|
||||
canUpdateAllObjectRecords
|
||||
canSoftDeleteAllObjectRecords
|
||||
canDestroyAllObjectRecords
|
||||
canBeAssignedToUsers
|
||||
canBeAssignedToAgents
|
||||
canBeAssignedToApiKeys
|
||||
workspaceMembers {
|
||||
__typename
|
||||
id
|
||||
name {
|
||||
__typename
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
avatarUrl
|
||||
userEmail
|
||||
}
|
||||
agents {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
roleId
|
||||
isCustom
|
||||
modelConfiguration
|
||||
evaluationInputs
|
||||
applicationId
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
apiKeys {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
expiresAt
|
||||
revokedAt
|
||||
}
|
||||
permissionFlags {
|
||||
__typename
|
||||
id
|
||||
flag
|
||||
roleId
|
||||
}
|
||||
objectPermissions {
|
||||
__typename
|
||||
objectMetadataId
|
||||
canReadObjectRecords
|
||||
canUpdateObjectRecords
|
||||
canSoftDeleteObjectRecords
|
||||
canDestroyObjectRecords
|
||||
restrictedFields
|
||||
rowLevelPermissionPredicates {
|
||||
__typename
|
||||
id
|
||||
fieldMetadataId
|
||||
objectMetadataId
|
||||
operand
|
||||
subFieldName
|
||||
workspaceMemberFieldMetadataId
|
||||
workspaceMemberSubFieldName
|
||||
rowLevelPermissionPredicateGroupId
|
||||
positionInRowLevelPermissionPredicateGroup
|
||||
roleId
|
||||
value
|
||||
}
|
||||
rowLevelPermissionPredicateGroups {
|
||||
__typename
|
||||
id
|
||||
parentRowLevelPermissionPredicateGroupId
|
||||
logicalOperator
|
||||
positionInRowLevelPermissionPredicateGroup
|
||||
roleId
|
||||
objectMetadataId
|
||||
}
|
||||
}
|
||||
fieldPermissions {
|
||||
__typename
|
||||
objectMetadataId
|
||||
fieldMetadataId
|
||||
canReadFieldValue
|
||||
canUpdateFieldValue
|
||||
id
|
||||
roleId
|
||||
}
|
||||
rowLevelPermissionPredicates {
|
||||
__typename
|
||||
id
|
||||
fieldMetadataId
|
||||
objectMetadataId
|
||||
operand
|
||||
subFieldName
|
||||
workspaceMemberFieldMetadataId
|
||||
workspaceMemberSubFieldName
|
||||
rowLevelPermissionPredicateGroupId
|
||||
positionInRowLevelPermissionPredicateGroup
|
||||
roleId
|
||||
value
|
||||
}
|
||||
rowLevelPermissionPredicateGroups {
|
||||
__typename
|
||||
id
|
||||
parentRowLevelPermissionPredicateGroupId
|
||||
logicalOperator
|
||||
positionInRowLevelPermissionPredicateGroup
|
||||
roleId
|
||||
objectMetadataId
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateRoles = async (token: string) => {
|
||||
console.log('Fetching roles from /metadata ...');
|
||||
|
||||
const data = (await graphqlRequest('/metadata', GET_ROLES_QUERY, token)) as {
|
||||
getRoles: Record<string, unknown>[];
|
||||
};
|
||||
|
||||
console.log(` Got ${data.getRoles.length} roles.`);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/roles/mock-roles-data.ts',
|
||||
'mockedRoles',
|
||||
'Role[]',
|
||||
"import { type Role } from '~/generated-metadata/graphql';",
|
||||
data.getRoles,
|
||||
);
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
/* eslint-disable no-console, lingui/no-unlocalized-strings */
|
||||
import { graphqlRequest, writeGeneratedFile } from './utils.js';
|
||||
|
||||
const FIND_ALL_CORE_VIEWS_QUERY = `
|
||||
query FindAllCoreViews {
|
||||
getCoreViews {
|
||||
id
|
||||
name
|
||||
objectMetadataId
|
||||
type
|
||||
key
|
||||
icon
|
||||
position
|
||||
isCompact
|
||||
openRecordIn
|
||||
kanbanAggregateOperation
|
||||
kanbanAggregateOperationFieldMetadataId
|
||||
mainGroupByFieldMetadataId
|
||||
shouldHideEmptyGroups
|
||||
anyFieldFilterValue
|
||||
calendarFieldMetadataId
|
||||
calendarLayout
|
||||
visibility
|
||||
createdByUserWorkspaceId
|
||||
viewFields {
|
||||
id
|
||||
fieldMetadataId
|
||||
viewId
|
||||
isVisible
|
||||
position
|
||||
size
|
||||
aggregateOperation
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
viewFieldGroups {
|
||||
id
|
||||
name
|
||||
position
|
||||
isVisible
|
||||
viewId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
viewFields {
|
||||
id
|
||||
fieldMetadataId
|
||||
viewId
|
||||
isVisible
|
||||
position
|
||||
size
|
||||
aggregateOperation
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
viewFilters {
|
||||
id
|
||||
fieldMetadataId
|
||||
operand
|
||||
value
|
||||
viewFilterGroupId
|
||||
positionInViewFilterGroup
|
||||
subFieldName
|
||||
viewId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
viewFilterGroups {
|
||||
id
|
||||
parentViewFilterGroupId
|
||||
logicalOperator
|
||||
positionInViewFilterGroup
|
||||
viewId
|
||||
}
|
||||
viewSorts {
|
||||
id
|
||||
fieldMetadataId
|
||||
direction
|
||||
viewId
|
||||
}
|
||||
viewGroups {
|
||||
id
|
||||
isVisible
|
||||
fieldValue
|
||||
position
|
||||
viewId
|
||||
createdAt
|
||||
updatedAt
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const generateViews = async (token: string) => {
|
||||
console.log('Fetching views from /metadata ...');
|
||||
|
||||
const data = (await graphqlRequest(
|
||||
'/metadata',
|
||||
FIND_ALL_CORE_VIEWS_QUERY,
|
||||
token,
|
||||
)) as {
|
||||
getCoreViews: Record<string, unknown>[];
|
||||
};
|
||||
|
||||
console.log(` Got ${data.getCoreViews.length} views.`);
|
||||
|
||||
writeGeneratedFile(
|
||||
'metadata/views/mock-views-data.ts',
|
||||
'mockedCoreViews',
|
||||
'CoreViewWithRelations[]',
|
||||
"import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';",
|
||||
data.getCoreViews,
|
||||
);
|
||||
};
|
||||
@@ -1,134 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
const SERVER_BASE_URL =
|
||||
process.env.REACT_APP_SERVER_BASE_URL ?? 'http://localhost:3000';
|
||||
const AUTH_EMAIL = 'jane.austen@apple.dev';
|
||||
const AUTH_PASSWORD = 'tim@apple.dev';
|
||||
const WORKSPACE_SUBDOMAIN = 'apple';
|
||||
|
||||
const serverUrl = new URL(SERVER_BASE_URL);
|
||||
export const WORKSPACE_ORIGIN = `${serverUrl.protocol}//${WORKSPACE_SUBDOMAIN}.${serverUrl.host}`;
|
||||
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const GENERATED_DIR = path.resolve(
|
||||
currentDir,
|
||||
'../../src/testing/mock-data/generated',
|
||||
);
|
||||
|
||||
export const graphqlRequest = async (
|
||||
endpoint: string,
|
||||
query: string,
|
||||
token?: string,
|
||||
variables?: Record<string, unknown>,
|
||||
): Promise<unknown> => {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Origin: WORKSPACE_ORIGIN,
|
||||
};
|
||||
|
||||
if (token !== undefined) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${SERVER_BASE_URL}${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ query, variables }),
|
||||
});
|
||||
|
||||
const json = (await response.json()) as {
|
||||
data?: unknown;
|
||||
errors?: { message: string }[];
|
||||
};
|
||||
|
||||
if (
|
||||
json.errors !== undefined &&
|
||||
json.errors !== null &&
|
||||
json.errors.length > 0
|
||||
) {
|
||||
const errorDetails = json.errors.map((error) => error.message).join(', ');
|
||||
throw new Error(`GraphQL error on ${endpoint}: ${errorDetails}`);
|
||||
}
|
||||
|
||||
return json.data;
|
||||
};
|
||||
|
||||
export const authenticate = async (): Promise<string> => {
|
||||
console.log(
|
||||
`Authenticating as ${AUTH_EMAIL} on workspace ${WORKSPACE_SUBDOMAIN}...`,
|
||||
);
|
||||
|
||||
const loginData = (await graphqlRequest(
|
||||
'/metadata',
|
||||
`mutation GetLoginTokenFromCredentials {
|
||||
getLoginTokenFromCredentials(
|
||||
email: "${AUTH_EMAIL}",
|
||||
password: "${AUTH_PASSWORD}",
|
||||
origin: "${WORKSPACE_ORIGIN}"
|
||||
) {
|
||||
loginToken { token }
|
||||
}
|
||||
}`,
|
||||
)) as {
|
||||
getLoginTokenFromCredentials: { loginToken: { token: string } };
|
||||
};
|
||||
|
||||
const loginToken = loginData.getLoginTokenFromCredentials.loginToken.token;
|
||||
|
||||
const authData = (await graphqlRequest(
|
||||
'/metadata',
|
||||
`mutation GetAuthTokensFromLoginToken {
|
||||
getAuthTokensFromLoginToken(
|
||||
loginToken: "${loginToken}",
|
||||
origin: "${WORKSPACE_ORIGIN}"
|
||||
) {
|
||||
tokens {
|
||||
accessOrWorkspaceAgnosticToken { token }
|
||||
}
|
||||
}
|
||||
}`,
|
||||
)) as {
|
||||
getAuthTokensFromLoginToken: {
|
||||
tokens: { accessOrWorkspaceAgnosticToken: { token: string } };
|
||||
};
|
||||
};
|
||||
|
||||
const accessToken =
|
||||
authData.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken
|
||||
.token;
|
||||
|
||||
console.log('Authenticated successfully.');
|
||||
return accessToken;
|
||||
};
|
||||
|
||||
export const writeGeneratedFile = (
|
||||
relativePath: string,
|
||||
exportName: string,
|
||||
typeName: string,
|
||||
typeImport: string,
|
||||
data: unknown,
|
||||
) => {
|
||||
const filePath = path.join(GENERATED_DIR, relativePath);
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
|
||||
const content = [
|
||||
'/* eslint-disable */',
|
||||
'// @ts-nocheck',
|
||||
typeImport,
|
||||
'',
|
||||
'// This file was automatically generated — do not edit manually.',
|
||||
'',
|
||||
'// prettier-ignore',
|
||||
`export const ${exportName}: ${typeName} =`,
|
||||
JSON.stringify(data, null, 2) + ';',
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
console.log(`Written: ${filePath}`);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -38,7 +38,7 @@ export const Default: Story = {
|
||||
|
||||
await canvas.findByText('Search');
|
||||
await canvas.findByText('Settings');
|
||||
await canvas.findByText('Opened');
|
||||
await canvas.findByText('Linkedin');
|
||||
await canvas.findByText('Companies');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Voeg Blokkie By"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Oplopend"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Verander Kleur"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Verander na jaarliks?"
|
||||
msgid "Chart"
|
||||
msgstr "Grafiek"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Huidige werksruimtelid nie gevind nie."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "verwyder"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Moenie invoer nie"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentasie"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Versteek versteekte groepe"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Navigeer na vorige werkstroom"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nuwe Goedgekeurde Toegangsdomein"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Nuwe geselsie"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Notas"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "nou"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "\"Van die gebaande pad af\""
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Ander"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Instelling van jou databasis..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Vandag"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Gebruiker is nie aangeteken nie"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14462,7 +14443,6 @@ msgstr "ja"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Gister"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "إضافة بلوك"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "تصاعدي"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "تغيير اللون"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "التغيير إلى سنوي؟"
|
||||
msgid "Chart"
|
||||
msgstr "الرسم البياني"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "لم يتم العثور على عضو مساحة العمل الحالي."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "حذف"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "عدم الاستيراد"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "التوثيق"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "إخفاء المجموعات المخفية"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "الانتقال إلى عملية العمل السابقة"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "نطاق وصول معتمد جديد"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "دردشة جديدة"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "الملاحظات"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "الآن"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "خارج المألوف"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "أخرى"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "إعداد قاعدة البيانات الخاصة بك..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "اليوم"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "المستخدم غير مسجل الدخول"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14460,7 +14441,6 @@ msgstr "نعم"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "أمس"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Afegeix Bloc"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Ascendent"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Canvia Color"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Canviar a anual?"
|
||||
msgid "Chart"
|
||||
msgstr "Gràfic"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Membre de l'espai de treball actual no trobat."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "elimina"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "No importar"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Documentació"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Amaga grups ocults"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Navega al workflow anterior"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nou domini d'accés aprovat"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Nou xat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Notes"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "ara"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Fora del camí conegut"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Altres"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Configurant la teva base de dades..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Avui"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "L'usuari no està logat"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14462,7 +14443,6 @@ msgstr "sí"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Ahir"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Přidat blok"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Vzestupně"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Změnit barvu"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Změnit na roční?"
|
||||
msgid "Chart"
|
||||
msgstr "Graf"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Současný člen pracovního prostoru nebyl nalezen."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "smazat"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Neimportovat"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentace"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Skrýt skryté skupiny"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Přejít na předchozí workflow"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nová schválená přístupová doména"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Nový chat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Poznámky"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "nyní"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Mimo ušlapané cesty"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Ostatní"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Nastavení vaší databáze..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Dnes"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Uživatel není přihlášen"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14462,7 +14443,6 @@ msgstr "ano"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Včera"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Tilføj blok"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Stigende"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Skift farve"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Skift til Årlig?"
|
||||
msgid "Chart"
|
||||
msgstr "Diagram"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Nuværende arbejdsområde medlem ikke fundet."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "slet"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Importér ikke"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentation"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Skjul skjulte grupper"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Naviger til forrige workflow"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nyt godkendt adgangsdomæne"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Ny chat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Noter"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "nu"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Uden for alfarvej"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Andet"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Opsætning af din database..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13038,7 +13016,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "I dag"
|
||||
|
||||
@@ -13702,8 +13679,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13747,6 +13722,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Bruger er ikke logget ind"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14464,7 +14445,6 @@ msgstr "ja"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "I går"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Block hinzufügen"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Aufsteigend"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Farbe ändern"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Zu jährlich wechseln?"
|
||||
msgid "Chart"
|
||||
msgstr "Diagramm"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Aktuelles Arbeitsbereichsmitglied nicht gefunden."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "löschen"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Nicht importieren"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentation"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Verborgene Gruppen ausblenden"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Zum vorherigen Workflow navigieren"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Neue genehmigte Zugriffsdomäne"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Neuer Chat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Notizen"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "jetzt"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Abseits der ausgetretenen Pfade"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Andere"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Einrichten Ihrer Datenbank..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Heute"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Benutzer ist nicht eingeloggt"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14462,7 +14443,6 @@ msgstr "ja"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Gestern"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Προσθήκη Μπλοκ"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Αύξουσα"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Αλλαγή Χρώματος"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Αλλαγή σε ετήσια;"
|
||||
msgid "Chart"
|
||||
msgstr "Διάγραμμα"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Το τρέχον μέλος του χώρου εργασίας δεν βρέθηκε."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "διαγραφή"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Μην εισάγετε"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Τεκμηρίωση"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Απόκρυψη κρυφών ομάδων"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Μετάβαση στον προηγούμενο workflow"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Νέος Εγκεκριμένος Τομέας Πρόσβασης"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Νέα συνομιλία"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Σημειώματα"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "τώρα"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Εκτός της συνηθισμένης πορείας"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Άλλο"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Ρύθμιση της βάσης δεδομένων σας..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13040,7 +13018,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Σήμερα"
|
||||
|
||||
@@ -13704,8 +13681,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13749,6 +13724,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Ο χρήστης δεν έχει συνδεθεί"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14466,7 +14447,6 @@ msgstr "ναι"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Χθες"
|
||||
|
||||
|
||||
@@ -832,7 +832,7 @@ msgid "Add BCC"
|
||||
msgstr "Add BCC"
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Add Block"
|
||||
@@ -1852,6 +1852,7 @@ msgstr "Ascending"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2525,7 +2526,7 @@ msgid "CC"
|
||||
msgstr "CC"
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Change Color"
|
||||
@@ -2577,11 +2578,6 @@ msgstr "Change to Yearly?"
|
||||
msgid "Chart"
|
||||
msgstr "Chart"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr "Chat"
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3654,7 +3650,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Current workspace member not found."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4085,7 +4080,7 @@ msgstr "delete"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4436,8 +4431,8 @@ msgstr "Do not import"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Documentation"
|
||||
|
||||
@@ -6678,7 +6673,6 @@ msgstr "Hide hidden groups"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr "Home"
|
||||
|
||||
@@ -8508,11 +8502,6 @@ msgstr "Navigate to previous workflow"
|
||||
msgid "navigation menu item"
|
||||
msgstr "navigation menu item"
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr "Navigation tabs"
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8563,9 +8552,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "New Approved Access Domain"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "New chat"
|
||||
@@ -9257,7 +9243,6 @@ msgstr "Notes"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "now"
|
||||
|
||||
@@ -9416,11 +9401,6 @@ msgstr "Off the beaten path"
|
||||
msgid "ol"
|
||||
msgstr "ol"
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr "Older"
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9652,7 +9632,6 @@ msgstr "Organize"
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Other"
|
||||
@@ -11051,12 +11030,12 @@ msgid "Score"
|
||||
msgstr "Score"
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11748,13 +11727,12 @@ msgstr "Setting up your database..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13033,7 +13011,6 @@ msgstr "To"
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Today"
|
||||
|
||||
@@ -13697,8 +13674,6 @@ msgstr "Useful for pivot/junction tables"
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13742,6 +13717,12 @@ msgstr "User Information"
|
||||
msgid "User is not logged in"
|
||||
msgstr "User is not logged in"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr "User Workspace"
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14459,7 +14440,6 @@ msgstr "yes"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Yesterday"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Agregar Bloque"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Ascendente"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Cambiar Color"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Cambiar a anual?"
|
||||
msgid "Chart"
|
||||
msgstr "Gráfico"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Miembro actual del espacio de trabajo no encontrado."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "eliminar"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "No importar"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Documentación"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Ocultar grupos ocultos"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Navegar al flujo de trabajo anterior"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nuevo Dominio de Acceso Aprobado"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Nuevo chat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Notas"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "ahora"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Fuera del camino trillado"
|
||||
msgid "ol"
|
||||
msgstr "ol"
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Otros"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Configurando su base de datos..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13038,7 +13016,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Hoy"
|
||||
|
||||
@@ -13702,8 +13679,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13747,6 +13722,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "El usuario no ha iniciado sesión"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14464,7 +14445,6 @@ msgstr "sí"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Ayer"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Lisää lohko"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Nouseva"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Vaihda väri"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Vaihda vuosittaiseksi?"
|
||||
msgid "Chart"
|
||||
msgstr "Kaavio"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Nykyistä työtilan jäsentä ei löytynyt."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "poista"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Älä tuo"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Dokumentaatio"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Piilota piilotetut ryhmät"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Siirry edelliseen työnkulkuun"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Uusi Hyväksytty Pääsytunnusalue"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Uusi keskustelu"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Muistiinpanot"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "nyt"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Kulkemattomia polkuja pitkin"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Muu"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Asetetaan tietokantaasi..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13036,7 +13014,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Tänään"
|
||||
|
||||
@@ -13700,8 +13677,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13745,6 +13720,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "Käyttäjä ei ole kirjautunut sisään"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14462,7 +14443,6 @@ msgstr "kyllä"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Eilen"
|
||||
|
||||
|
||||
@@ -837,7 +837,7 @@ msgid "Add BCC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: We0vOO
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Add Block"
|
||||
msgstr "Ajouter un bloc"
|
||||
@@ -1857,6 +1857,7 @@ msgstr "Ascendant"
|
||||
|
||||
#. js-lingui-id: xWEvuo
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenAskAIPageInCommandMenu.ts
|
||||
#: src/modules/command-menu/components/CommandMenuAskAIInfo.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -2530,7 +2531,7 @@ msgid "CC"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: tHntRt
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/blocknote-editor/components/CustomSideMenu.tsx
|
||||
msgid "Change Color"
|
||||
msgstr "Changer la couleur"
|
||||
@@ -2582,11 +2583,6 @@ msgstr "Passer à l'annuel?"
|
||||
msgid "Chart"
|
||||
msgstr "Graphique"
|
||||
|
||||
#. js-lingui-id: AHZflp
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Chat"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: UxeiNP
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useActionRolePermissionFlagConfig.ts
|
||||
msgid "Chat with AI agents and use AI features"
|
||||
@@ -3659,7 +3655,6 @@ msgid "Current workspace member not found."
|
||||
msgstr "Membre actuel de l'espace de travail introuvable."
|
||||
|
||||
#. js-lingui-id: 8Tg/JR
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/components/SettingsToolsTable.tsx
|
||||
#: src/modules/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelObjectPicker.tsx
|
||||
#: src/modules/settings/data-model/fields/forms/date/utils/getDisplayFormatLabel.ts
|
||||
@@ -4090,7 +4085,7 @@ msgstr "supprimer"
|
||||
#: src/modules/settings/data-model/object-details/components/tabs/ObjectSettings.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminQueueJobRowDropdownMenu.tsx
|
||||
#: src/modules/settings/admin-panel/health-status/components/SettingsAdminDeleteJobsConfirmationModal.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardEditorSideMenu.tsx
|
||||
#: src/modules/page-layout/widgets/standalone-rich-text/components/DashboardBlockDragHandleMenu.tsx
|
||||
#: src/modules/object-record/record-field-list/record-detail-section/relation/components/RecordDetailRelationRecordsListItem.tsx
|
||||
#: src/modules/object-record/record-field/ui/meta-types/input/components/MultiItemFieldMenuItem.tsx
|
||||
#: src/modules/navigation-menu-item/components/NavigationMenuItemFolderNavigationDrawerItemDropdown.tsx
|
||||
@@ -4441,8 +4436,8 @@ msgstr "Ne pas importer"
|
||||
#. js-lingui-id: TvY/XA
|
||||
#: src/pages/settings/workspace/SettingsApiWebhooks.tsx
|
||||
#: src/pages/settings/developers/api-keys/SettingsApiKeys.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
msgid "Documentation"
|
||||
msgstr "Documentation"
|
||||
|
||||
@@ -6683,7 +6678,6 @@ msgstr "Masquer les groupes cachés"
|
||||
|
||||
#. js-lingui-id: i0qMbr
|
||||
#: src/modules/page-layout/constants/StandardPageLayoutTabTitleTranslations.ts
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
@@ -8513,11 +8507,6 @@ msgstr "Aller au workflow précédent"
|
||||
msgid "navigation menu item"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: s2HXiD
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
msgid "Navigation tabs"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: qqeAJM
|
||||
#: src/modules/settings/developers/utils/formatExpiration.ts
|
||||
msgid "Never"
|
||||
@@ -8568,9 +8557,6 @@ msgid "New Approved Access Domain"
|
||||
msgstr "Nouveau domaine d'accès approuvé"
|
||||
|
||||
#. js-lingui-id: hIQkLb
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
|
||||
#: src/modules/ai/components/NavigationDrawerAIChatThreadDateSection.tsx
|
||||
#: src/modules/ai/components/AIChatThreadsList.tsx
|
||||
msgid "New chat"
|
||||
msgstr "Nouveau chat"
|
||||
@@ -9262,7 +9248,6 @@ msgstr "Notes"
|
||||
|
||||
#. js-lingui-id: YwzE9K
|
||||
#: src/utils/date-utils.ts
|
||||
#: src/utils/date-utils.ts
|
||||
msgid "now"
|
||||
msgstr "maintenant"
|
||||
|
||||
@@ -9421,11 +9406,6 @@ msgstr "Hors des sentiers battus"
|
||||
msgid "ol"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: /fwE6J
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Older"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: +rvV4v
|
||||
#: src/modules/command-menu/pages/page-layout/constants/settings/ChartConfigurationSettingLabels.ts
|
||||
msgid "Omit zero values"
|
||||
@@ -9657,7 +9637,6 @@ msgstr ""
|
||||
#: src/pages/settings/admin-panel/SettingsAdmin.tsx
|
||||
#: src/modules/settings/hooks/useSettingsNavigationItems.tsx
|
||||
#: src/modules/page-layout/widgets/fields/hooks/useFieldsWidgetGroups.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/command-menu/pages/navigation-menu-item/components/CommandMenuNewSidebarItemMainMenu.tsx
|
||||
msgid "Other"
|
||||
msgstr "Autres"
|
||||
@@ -11056,12 +11035,12 @@ msgid "Score"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: A1taO8
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownWorkspacesListComponents.tsx
|
||||
#: src/modules/ui/layout/dropdown/components/DropdownMenuSearchInput.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-assignment/components/SettingsRoleAssignmentWorkspaceMemberPickerDropdown.tsx
|
||||
#: src/modules/settings/admin-panel/components/SettingsAdminGeneral.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu.ts
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
#: src/modules/action-menu/actions/record-agnostic-actions/constants/RecordAgnosticActionsConfig.tsx
|
||||
@@ -11753,13 +11732,12 @@ msgstr "Configuration de votre base de données..."
|
||||
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
|
||||
#: src/pages/settings/applications/SettingsApplicationDetails.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentForm.tsx
|
||||
#: src/modules/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/internal/MultiWorkspaceDropdownDefaultComponents.tsx
|
||||
#: src/modules/sign-in-background-mock/components/SignInAppNavigationDrawerMock.tsx
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx
|
||||
#: src/modules/settings/roles/role/components/SettingsRole.tsx
|
||||
#: src/modules/settings/accounts/components/SettingsAccountsSettingsSection.tsx
|
||||
#: src/modules/object-record/record-merge/hooks/useMergeRecordsContainerTabs.ts
|
||||
#: src/modules/navigation/components/NavigationDrawerOtherSection.tsx
|
||||
#: src/modules/navigation/components/MainNavigationDrawerFixedItems.tsx
|
||||
#: src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutTabSettings.tsx
|
||||
#: src/modules/action-menu/actions/record-actions/constants/DefaultRecordActionsConfig.tsx
|
||||
msgid "Settings"
|
||||
@@ -13038,7 +13016,6 @@ msgstr ""
|
||||
#: src/modules/settings/admin-panel/health-status/constants/WorkerQueueMetricsSelectOptions.ts
|
||||
#: src/modules/object-record/record-calendar/components/RecordCalendarTopBar.tsx
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Today"
|
||||
msgstr "Aujourd'hui"
|
||||
|
||||
@@ -13702,8 +13679,6 @@ msgstr ""
|
||||
#. js-lingui-id: 7PzzBU
|
||||
#: src/pages/settings/SettingsTwoFactorAuthenticationMethod.tsx
|
||||
#: src/pages/settings/SettingsProfile.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/profile/appearance/components/SettingsExperience.tsx
|
||||
#: src/pages/settings/ai/SettingsAgentTurnDetail.tsx
|
||||
#: src/pages/settings/ai/SettingsAIPrompts.tsx
|
||||
@@ -13747,6 +13722,12 @@ msgstr ""
|
||||
msgid "User is not logged in"
|
||||
msgstr "L'utilisateur n'est pas connecté"
|
||||
|
||||
#. js-lingui-id: X0w+FW
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
#: src/pages/settings/security/event-logs/components/EventLogResultsTable.tsx
|
||||
msgid "User Workspace"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: Sxm8rQ
|
||||
#: src/modules/settings/roles/role-permissions/permission-flags/hooks/useSettingsRolePermissionFlagConfig.ts
|
||||
msgid "Users"
|
||||
@@ -14464,7 +14445,6 @@ msgstr "oui"
|
||||
|
||||
#. js-lingui-id: y/0uwd
|
||||
#: src/modules/localization/utils/formatDateISOStringToRelativeDate.ts
|
||||
#: src/modules/ai/utils/getDateGroupTitle.ts
|
||||
msgid "Yesterday"
|
||||
msgstr "Hier"
|
||||
|
||||
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user