inicontent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/.github/pull_request.yml +18 -0
  2. package/CONTRIBUTING.md +67 -0
  3. package/LICENSE +21 -0
  4. package/README.md +142 -0
  5. package/app/app.vue +106 -0
  6. package/app/assets/main.css +974 -0
  7. package/app/components/Api/EndpointCard.vue +169 -0
  8. package/app/components/Api/SchemaDocsTable.vue +208 -0
  9. package/app/components/Asset/Card.vue +1363 -0
  10. package/app/components/Asset/Grid.vue +295 -0
  11. package/app/components/Asset/Icon.vue +38 -0
  12. package/app/components/Asset/Preview.vue +486 -0
  13. package/app/components/Asset/Scanner.vue +700 -0
  14. package/app/components/Asset/Thumb.vue +708 -0
  15. package/app/components/Auth/Reset.vue +99 -0
  16. package/app/components/Auth/index.vue +346 -0
  17. package/app/components/ChatInterface.vue +2326 -0
  18. package/app/components/ChatPagePreview.vue +34 -0
  19. package/app/components/Column/Array.vue +29 -0
  20. package/app/components/Column/Asset.vue +21 -0
  21. package/app/components/Column/Boolean.vue +12 -0
  22. package/app/components/Column/Color.vue +9 -0
  23. package/app/components/Column/Date.vue +57 -0
  24. package/app/components/Column/Edit.vue +215 -0
  25. package/app/components/Column/Email.vue +9 -0
  26. package/app/components/Column/Html.vue +27 -0
  27. package/app/components/Column/Id.vue +20 -0
  28. package/app/components/Column/Locale.vue +32 -0
  29. package/app/components/Column/Object.vue +18 -0
  30. package/app/components/Column/Password.vue +7 -0
  31. package/app/components/Column/Role.vue +15 -0
  32. package/app/components/Column/S.vue +14 -0
  33. package/app/components/Column/Table/Single.vue +49 -0
  34. package/app/components/Column/Table/index.vue +42 -0
  35. package/app/components/Column/Tags.vue +31 -0
  36. package/app/components/Column/Text.vue +9 -0
  37. package/app/components/Column/Url.vue +9 -0
  38. package/app/components/Column/index.vue +41 -0
  39. package/app/components/Dashboard/Editor.vue +221 -0
  40. package/app/components/Dashboard/Grid.vue +270 -0
  41. package/app/components/Dashboard/View.vue +131 -0
  42. package/app/components/Dashboard/Widget/BarChart.vue +73 -0
  43. package/app/components/Dashboard/Widget/Counter.vue +51 -0
  44. package/app/components/Dashboard/Widget/LineChart.vue +76 -0
  45. package/app/components/Dashboard/Widget/PieChart.vue +75 -0
  46. package/app/components/Dashboard/Widget/Table.vue +94 -0
  47. package/app/components/Dashboard/WidgetEditor.vue +274 -0
  48. package/app/components/Data/Array.vue +102 -0
  49. package/app/components/Data/Html.vue +9 -0
  50. package/app/components/Data/Icon.vue +9 -0
  51. package/app/components/Data/Object.vue +10 -0
  52. package/app/components/Data/S.vue +16 -0
  53. package/app/components/Data/Table.vue +53 -0
  54. package/app/components/Data/Value.vue +34 -0
  55. package/app/components/Data/index.vue +14 -0
  56. package/app/components/Field/Array.vue +324 -0
  57. package/app/components/Field/Asset/Actions.vue +97 -0
  58. package/app/components/Field/Asset/index.vue +639 -0
  59. package/app/components/Field/Boolean.vue +26 -0
  60. package/app/components/Field/Checkbox.vue +48 -0
  61. package/app/components/Field/Color.vue +27 -0
  62. package/app/components/Field/Date.vue +30 -0
  63. package/app/components/Field/Email.vue +51 -0
  64. package/app/components/Field/EmailTemplate.vue +420 -0
  65. package/app/components/Field/Html/ColorPicker.vue +127 -0
  66. package/app/components/Field/Html/Editor.vue +544 -0
  67. package/app/components/Field/Html/index.vue +25 -0
  68. package/app/components/Field/Icon.vue +114 -0
  69. package/app/components/Field/JSON.vue +113 -0
  70. package/app/components/Field/Mention.vue +36 -0
  71. package/app/components/Field/Number.vue +27 -0
  72. package/app/components/Field/Object.vue +92 -0
  73. package/app/components/Field/Password.vue +55 -0
  74. package/app/components/Field/Radio.vue +47 -0
  75. package/app/components/Field/S.vue +44 -0
  76. package/app/components/Field/Select.vue +49 -0
  77. package/app/components/Field/Table.vue +348 -0
  78. package/app/components/Field/Tags.vue +113 -0
  79. package/app/components/Field/Text.vue +31 -0
  80. package/app/components/Field/Textarea.vue +33 -0
  81. package/app/components/Field/Url.vue +40 -0
  82. package/app/components/Field/Wrapper.vue +128 -0
  83. package/app/components/Field/index.vue +68 -0
  84. package/app/components/FloatingChatbot.vue +73 -0
  85. package/app/components/Form/Card.vue +151 -0
  86. package/app/components/Form/Drawer.vue +195 -0
  87. package/app/components/Form/index.vue +621 -0
  88. package/app/components/Header.vue +328 -0
  89. package/app/components/Offline/ConflictModal.vue +200 -0
  90. package/app/components/Offline/SyncStatus.vue +249 -0
  91. package/app/components/PlatformFeatureLanding.vue +65 -0
  92. package/app/components/Table/Backups.vue +638 -0
  93. package/app/components/Table/Flows.vue +814 -0
  94. package/app/components/Table/Grid.vue +296 -0
  95. package/app/components/Table/Icon.vue +36 -0
  96. package/app/components/Table/Logs.vue +181 -0
  97. package/app/components/Table/Schedules.vue +722 -0
  98. package/app/components/Table/Search/Button.vue +256 -0
  99. package/app/components/Table/Search/Items.vue +286 -0
  100. package/app/components/Table/Search/index.vue +87 -0
  101. package/app/components/Table/Settings/Schema.vue +801 -0
  102. package/app/components/Table/Settings/index.vue +631 -0
  103. package/app/components/Table/TranslateDrawer.vue +526 -0
  104. package/app/components/Table/Views/Kanban.vue +289 -0
  105. package/app/components/Table/Views/Table.vue +1273 -0
  106. package/app/components/Table/index.vue +890 -0
  107. package/app/composables/Translation/define.ts +41 -0
  108. package/app/composables/Translation/fetch.ts +107 -0
  109. package/app/composables/Translation/languages.ts +1 -0
  110. package/app/composables/Translation/loadCoreTranslations.ts +38 -0
  111. package/app/composables/Translation/translate.ts +250 -0
  112. package/app/composables/databaseCookies.ts +79 -0
  113. package/app/composables/fieldValidator.ts +72 -0
  114. package/app/composables/fieldsList.ts +301 -0
  115. package/app/composables/formatDatabase.ts +101 -0
  116. package/app/composables/index.ts +444 -0
  117. package/app/composables/openDrawer.ts +118 -0
  118. package/app/composables/optimizeFile.ts +287 -0
  119. package/app/composables/renderLabel.ts +88 -0
  120. package/app/composables/schemaClipboard.ts +95 -0
  121. package/app/composables/search.ts +353 -0
  122. package/app/composables/setThemeConfig.ts +48 -0
  123. package/app/composables/translationValue.ts +169 -0
  124. package/app/composables/useAssetPreview.ts +84 -0
  125. package/app/composables/useAssetUploader.ts +56 -0
  126. package/app/composables/useDashboardData.ts +310 -0
  127. package/app/composables/useDocumentThumbnail.ts +351 -0
  128. package/app/composables/useEcharts.ts +33 -0
  129. package/app/composables/useLocalDatabaseConfig.ts +51 -0
  130. package/app/composables/useOcr.ts +300 -0
  131. package/app/composables/useOfflineFetch.ts +194 -0
  132. package/app/composables/useOfflineItem.ts +107 -0
  133. package/app/composables/useOfflineQueue.ts +302 -0
  134. package/app/composables/useOfflineSync.ts +235 -0
  135. package/app/composables/usePasskeyAuth.ts +156 -0
  136. package/app/composables/usePdfCompressor.ts +180 -0
  137. package/app/composables/usePlatformRedirect.ts +72 -0
  138. package/app/composables/useRealtimeSync.ts +233 -0
  139. package/app/composables/useReferencedItems.ts +180 -0
  140. package/app/composables/useScanner.ts +664 -0
  141. package/app/composables/useSubscription.ts +650 -0
  142. package/app/composables/useThumbnailCache.ts +136 -0
  143. package/app/composables/useVideoCompressor.ts +194 -0
  144. package/app/error.vue +112 -0
  145. package/app/layouts/api.vue +188 -0
  146. package/app/layouts/dashboard.vue +91 -0
  147. package/app/layouts/default.vue +228 -0
  148. package/app/layouts/table.vue +471 -0
  149. package/app/locales/ar.ts +996 -0
  150. package/app/locales/en.ts +655 -0
  151. package/app/locales/es.ts +996 -0
  152. package/app/locales/fr.ts +997 -0
  153. package/app/middleware/dashboard.ts +33 -0
  154. package/app/middleware/database.ts +83 -0
  155. package/app/middleware/global.ts +1 -0
  156. package/app/middleware/sid-ingest.global.ts +29 -0
  157. package/app/middleware/table.ts +54 -0
  158. package/app/middleware/user.ts +26 -0
  159. package/app/pages/[[database]]/admin/api/auth/index.vue +306 -0
  160. package/app/pages/[[database]]/admin/api/index.vue +18 -0
  161. package/app/pages/[[database]]/admin/api/tables/[table]/index.vue +497 -0
  162. package/app/pages/[[database]]/admin/api/tables/index.vue +87 -0
  163. package/app/pages/[[database]]/admin/billing/index.vue +16 -0
  164. package/app/pages/[[database]]/admin/dashboards/[id]/edit.vue +66 -0
  165. package/app/pages/[[database]]/admin/dashboards/[id]/index.vue +117 -0
  166. package/app/pages/[[database]]/admin/dashboards/index.vue +20 -0
  167. package/app/pages/[[database]]/admin/index.vue +28 -0
  168. package/app/pages/[[database]]/admin/settings.vue +526 -0
  169. package/app/pages/[[database]]/admin/tables/[table]/[id]/edit.vue +143 -0
  170. package/app/pages/[[database]]/admin/tables/[table]/[id]/index.vue +146 -0
  171. package/app/pages/[[database]]/admin/tables/[table]/flows.vue +10 -0
  172. package/app/pages/[[database]]/admin/tables/[table]/index.vue +10 -0
  173. package/app/pages/[[database]]/admin/tables/[table]/new.vue +12 -0
  174. package/app/pages/[[database]]/admin/tables/[table]/schedules.vue +10 -0
  175. package/app/pages/[[database]]/admin/tables/[table]/settings.vue +30 -0
  176. package/app/pages/[[database]]/admin/tables/assets/[...path].vue +24 -0
  177. package/app/pages/[[database]]/admin/tables/assets/flows.vue +10 -0
  178. package/app/pages/[[database]]/admin/tables/backups/index.vue +10 -0
  179. package/app/pages/[[database]]/admin/tables/blocks/index.vue +16 -0
  180. package/app/pages/[[database]]/admin/tables/index.vue +20 -0
  181. package/app/pages/[[database]]/admin/tables/pages/index.vue +16 -0
  182. package/app/pages/[[database]]/admin/tables/templates/index.vue +16 -0
  183. package/app/pages/[[database]]/api/index.vue +120 -0
  184. package/app/pages/[[database]]/auth/index.vue +12 -0
  185. package/app/pages/[[database]]/auth/reset.vue +10 -0
  186. package/app/pages/[[database]]/index.vue +10 -0
  187. package/app/plugins/offline-warmup.client.ts +53 -0
  188. package/app/plugins/template-globals.ts +9 -0
  189. package/app/spa-loading-template.html +93 -0
  190. package/biome.json +53 -0
  191. package/index.d.ts +380 -0
  192. package/modules/naiveui.ts +41 -0
  193. package/nuxt.config.ts +269 -0
  194. package/package.json +56 -0
  195. package/pnpm-workspace.yaml +5 -0
  196. package/public/apple-touch-icon.png +0 -0
  197. package/public/favicon-16x16.png +0 -0
  198. package/public/favicon-32x32.png +0 -0
  199. package/public/favicon.ico +0 -0
  200. package/public/pwa-192x192.png +0 -0
  201. package/public/pwa-512x512.png +0 -0
  202. package/public/pwa-maskable-192x192.png +0 -0
  203. package/public/pwa-maskable-512x512.png +0 -0
  204. package/public/vendor/opencv.js +48 -0
  205. package/tsconfig.json +9 -0
@@ -0,0 +1,814 @@
1
+ <template>
2
+ <NCard :title="t('flows')" style="background-color: transparent;" :bordered="false" embeded
3
+ :header-style="{ paddingRight: 0, paddingLeft: 0 }" content-style="padding: 0"
4
+ :class="`table_${table.slug}`" id="tableCard">
5
+ <template #header-extra>
6
+ <NTabs size="small" style="min-width: 350px;" type="segment" animated v-model:value="currentFlow">
7
+ <NTab v-for="flowName of flowNames" :name="flowName" />
8
+ <template #suffix>
9
+ <NButtonGroup>
10
+ <NTooltip :delay="1500">
11
+ <template #trigger>
12
+ <NButton :type="reorderEnabled ? 'success' : 'default'" secondary round
13
+ @click="reorderEnabled = !reorderEnabled">
14
+ <template #icon>
15
+ <NIcon>
16
+ <Icon name="tabler:arrows-move" />
17
+ </NIcon>
18
+ </template>
19
+ </NButton>
20
+ </template>
21
+ {{ t('reorderFlows') }}
22
+ </NTooltip>
23
+ <NButton secondary type="primary" :disabled="Loading.updateTable" :loading="Loading.updateTable" @click="saveFlow">
24
+ <template #icon>
25
+ <NIcon :size="18">
26
+ <Icon name="tabler:device-floppy" />
27
+ </NIcon>
28
+ </template>
29
+ {{ t('save') }}
30
+ </NButton>
31
+ </NButtonGroup>
32
+ </template>
33
+ </NTabs>
34
+ </template>
35
+ <VueDraggable v-model="tableCopy[currentFlow]" ghost-class="flowCardGhost"
36
+ class="masonry">
37
+ <NCard v-for="(element, index) in tableCopy[currentFlow]" :key="element.id" class="flowCard" hoverable content-style="padding: 0">
38
+ <NButton class="flowCardIndex" disabled type="primary" size="small">
39
+ {{ index + 1 }}
40
+ </NButton>
41
+ <NButtonGroup class="flowCardButtons" vertical size="small">
42
+ <NButton secondary type="primary" @click="currentFlowCard =
43
+ currentFlowCard ===
44
+ `${currentFlow}-${index}`
45
+ ? undefined
46
+ : `${currentFlow}-${index}`">
47
+ <template #icon>
48
+ <NIcon>
49
+ <Icon name="tabler:eye"
50
+ v-if="currentFlowCard === `${currentFlow}-${index}`" />
51
+ <Icon name="tabler:pencil" v-else />
52
+ </NIcon>
53
+ </template>
54
+ </NButton>
55
+ <NButton secondary type="error"
56
+ @click="() => { if (currentFlowCard === `${currentFlow}-${index}`) currentFlowCard = undefined; tableCopy[currentFlow]?.splice(index, 1) }">
57
+ <template #icon>
58
+ <NIcon>
59
+ <Icon name="tabler:trash" />
60
+ </NIcon>
61
+ </template>
62
+ </NButton>
63
+ </NButtonGroup>
64
+ <NScrollbar x-scrollable>
65
+ <div v-if="currentFlowCard === `${currentFlow}-${index}`" class="flowCardRulesEdit">
66
+ <VueDraggable v-model="element.value" class="flowCardRules" ghost-class="flowCardGhost">
67
+ <template v-for="(rule, ruleIndex) in element.value" :key="rule.id">
68
+ <NInputGroup class="flowCardRulesGroupInput">
69
+ <template v-if="rule.value[0] === 'set'">
70
+ <NDropdown v-bind="ruleDropdownProps(element.value, ruleIndex)">
71
+ <NButton size="small" type="success"
72
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0!important' : '50px 0 0 50px!important' }"
73
+ secondary style="width: 47px;">
74
+ {{ t('set') }}
75
+ </NButton>
76
+ </NDropdown>
77
+ <NCascader size="small" style="height: fit-content;"
78
+ :options="generateFlowCascaderOptions(true, true)"
79
+ check-strategy="child" expand-trigger="click" show-path
80
+ separator="." filterable v-model:value="rule.value[1]" />
81
+ <template v-if="isDateFlowField(rule.value[1])">
82
+ <NSelect size="small"
83
+ :style="{ borderRadius: !isRelativeDateValue(rule.value[2]) ? '0!important' : (Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important') }"
84
+ style="overflow: hidden;"
85
+ :consistent-menu-width="false"
86
+ filterable
87
+ :options="dateFlowValueOptions"
88
+ :value="isRelativeDateValue(rule.value[2]) ? rule.value[2] : '__custom__'"
89
+ @update:value="(value) => rule.value[2] = value === '__custom__' ? null : value" />
90
+ <NDatePicker v-if="!isRelativeDateValue(rule.value[2])"
91
+ size="small"
92
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
93
+ style="overflow: hidden; width: 180px;"
94
+ clearable
95
+ :type="getDateFlowPickerType(rule.value[1])"
96
+ :format="['datetime', 'datetimerange'].includes(getDateFlowPickerType(rule.value[1])) ? 'dd-MM-yyyy HH:mm:ss' : 'dd-MM-yyyy'"
97
+ :value="typeof rule.value[2] === 'number' ? rule.value[2] : null"
98
+ @update:value="(value) => rule.value[2] = value" />
99
+ </template>
100
+ <NSelect v-else
101
+ size="small"
102
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
103
+ style="overflow: hidden;"
104
+ :consistent-menu-width="false"
105
+ filterable tag
106
+ :options="generateFlowSelectOptions(rule.value[1], false, true)"
107
+ :value="String(rule.value[2])"
108
+ @update:value="(value) => rule.value[2] = value === 'null' ? null : value" />
109
+ </template>
110
+ <template v-else-if="rule.value[0] === 'error'">
111
+ <NDropdown v-bind="ruleDropdownProps(element.value, ruleIndex)">
112
+ <NButton size="small" type="error" secondary
113
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0!important' : '50px 0 0 50px!important' }"
114
+ style="width: 96px;">
115
+ {{ t('throwError') }}
116
+ </NButton>
117
+ </NDropdown>
118
+ <NSelect size="small"
119
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
120
+ style="overflow: hidden;" :consistent-menu-width="false"
121
+ filterable tag
122
+ :options="[{ label: t('accessDenied'), value: 'accessDenied' }]"
123
+ v-model:value="rule.value[1]" />
124
+ </template>
125
+ <template v-else-if="rule.value[0] === 'email'">
126
+ <NDropdown v-bind="ruleDropdownProps(element.value, ruleIndex)">
127
+ <NButton size="small" type="info" secondary
128
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0!important' : '50px 0 0 50px!important' }"
129
+ style="width: 70px;">
130
+ {{ t('email') }}
131
+ </NButton>
132
+ </NDropdown>
133
+ <NCascader size="small" style="height: fit-content;"
134
+ :options="generateFlowCascaderOptions(false, false, true)"
135
+ check-strategy="child" expand-trigger="click" show-path
136
+ separator="." filterable v-model:value="rule.value[1]" />
137
+ <NSelect size="small"
138
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
139
+ style="overflow: hidden;" :consistent-menu-width="false"
140
+ filterable tag :options="templateNames"
141
+ v-model:value="rule.value[2]" />
142
+ </template>
143
+ <template v-else-if="rule.value[0] === 'unset'">
144
+ <NDropdown v-bind="ruleDropdownProps(element.value, ruleIndex)">
145
+ <NButton size="small" type="warning"
146
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0!important' : '50px 0 0 50px!important' }"
147
+ secondary style="width: 96px;">
148
+ {{ t('unset') }}
149
+ </NButton>
150
+ </NDropdown>
151
+ <NCascader size="small"
152
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
153
+ style="overflow: hidden;height: fit-content;"
154
+ :options="generateFlowCascaderOptions()" check-strategy="parent"
155
+ expand-trigger="click" show-path separator="." filterable
156
+ v-model:value="rule.value[1]" multiple :max-tag-count="1" />
157
+ </template>
158
+ <template v-else>
159
+ <NDropdown v-bind="ruleDropdownProps(element.value, ruleIndex)">
160
+ <NButton size="small" type="info" secondary
161
+ style="width: 37px;"
162
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0!important' : '50px 0 0 50px!important' }">
163
+ {{ t('if') }}
164
+ </NButton>
165
+ </NDropdown>
166
+ <NCascader size="small"
167
+ style="height: fit-content;max-width: 156px;"
168
+ :options="[...generateFlowCascaderOptions(true, true, true), { label: '@method', value: '@method' }]"
169
+ check-strategy="child" expand-trigger="click" show-path
170
+ separator="." filterable v-model:value="rule.value[0]" />
171
+ <NSelect size="small" style="width: 136px;"
172
+ :consistent-menu-width="false" filterable
173
+ :render-tag="({ option }) => option.value"
174
+ :options="checkFieldType(formatValue(rule.value[0], undefined, 'type', 'string'), ['number', 'date']) ? comparisonOperatorOptions().filter(({ value }) => !['*', '!*'].includes(value)) : comparisonOperatorOptions().filter(({ value }) => !['>', '>=', '<', '<=', ...(rule[0] === '@method' ? ['*', '!*'] : [])].includes(value))"
175
+ v-model:value="rule.value[1]" />
176
+ <template v-if="isDateFlowField(rule.value[0])">
177
+ <NSelect size="small"
178
+ :consistent-menu-width="false"
179
+ filterable
180
+ :options="dateFlowValueOptions"
181
+ :value="isRelativeDateValue(rule.value[2]) ? rule.value[2] : '__custom__'"
182
+ @update:value="(value) => rule.value[2] = value === '__custom__' ? null : value" />
183
+ <NDatePicker size="small"
184
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
185
+ style="overflow: hidden; width: 180px;"
186
+ clearable
187
+ :type="getDateFlowPickerType(rule.value[0])"
188
+ :format="['datetime', 'datetimerange'].includes(getDateFlowPickerType(rule.value[0])) ? 'dd-MM-yyyy HH:mm:ss' : 'dd-MM-yyyy'"
189
+ :value="typeof rule.value[2] === 'number' ? rule.value[2] : null"
190
+ @update:value="(value) => rule.value[2] = value" />
191
+ </template>
192
+ <NSelect v-else size="small" style="overflow: hidden;"
193
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px!important' : '0 50px 50px 0!important' }"
194
+ :consistent-menu-width="false" filterable tag
195
+ :options="generateFlowSelectOptions(rule.value[0], false, true)"
196
+ :multiple="['[]', '![]'].includes(rule.value[1] ?? '')"
197
+ max-tag-count="responsive"
198
+ :value="((['[]', '![]'].includes(rule.value[1] ?? '') ? rule.value[2] : rule.value[2]) as string)"
199
+ @update:value="(value) => rule.value[2] = value === 'null' ? null : value" />
200
+ </template>
201
+ </NInputGroup>
202
+ </template>
203
+ <NDropdown show-arrow :options="addRuleDropdownOptions"
204
+ @select="(value) => pushRuleToFlow(element.value, value)">
205
+ <NButton style="margin: auto" round dashed
206
+ @click="pushRuleToFlow(element.value, 'if')">
207
+ <template #icon>
208
+ <NIcon>
209
+ <Icon name="tabler:plus" />
210
+ </NIcon>
211
+ </template>
212
+ </NButton>
213
+ </NDropdown>
214
+ </VueDraggable>
215
+ </div>
216
+ <NEmpty v-else-if="!element.value.length" />
217
+ <NFlex v-else vertical style="padding: 20px 22px;">
218
+ <template v-for="{ value: [firstValue, secondValue, thirdValue] } of element.value">
219
+ <template v-if="firstValue === 'set'">
220
+ <NFlex align="center" :wrap="false">
221
+ <NFlex :wrap="false" :size="0">
222
+ <NTag type="success" :bordered="false" style="padding: 0 13px"
223
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0' : '50px 0 0 50px' }">
224
+ {{ t('set') }}
225
+ </NTag>
226
+ <NTag type="primary" :bordered="false"
227
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px' : '0 50px 50px 0', padding: '0 10px 0' }">
228
+ {{ formatValue(secondValue) }}
229
+ </NTag>
230
+ </NFlex>
231
+ <NTag v-if="thirdValue === undefined" :bordered="false" round>--
232
+ </NTag>
233
+ <NFlex v-else :wrap="false" :size="[4, 8]">
234
+ <NTag
235
+ v-for="value of ([] as string[]).concat(thirdValue as string | string[])"
236
+ :bordered="false" round>
237
+ {{ formatFlowValue(value, secondValue) }}
238
+ </NTag>
239
+ </NFlex>
240
+ </NFlex>
241
+ </template>
242
+ <template v-else-if="firstValue === 'error'">
243
+ <NFlex :wrap="false" :size="0">
244
+ <NTag type="error" :bordered="false"
245
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0' : '50px 0 0 50px' }"
246
+ style="padding: 0 13px;">
247
+ {{ t('throwError') }}
248
+ </NTag>
249
+ <NTag :bordered="false"
250
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px' : '0 50px 50px 0', padding: '0 10px 0' }">
251
+ {{ secondValue ? t(secondValue) : '--' }}
252
+ </NTag>
253
+ </NFlex>
254
+ </template>
255
+ <template v-else-if="firstValue === 'email'">
256
+ <NFlex :wrap="false" :size="0">
257
+ <NTag type="info" :bordered="false"
258
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0' : '50px 0 0 50px' }"
259
+ style="padding: 0 13px;">
260
+ {{ t('email') }}
261
+ </NTag>
262
+ <NTag :bordered="false"
263
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px' : '0 50px 50px 0', padding: '0 10px 0' }">
264
+ {{ formatValue(secondValue) ?? '--' }}
265
+ </NTag>
266
+ </NFlex>
267
+ <NTag :bordered="false" round>
268
+ {{ thirdValue ?? '--' }}
269
+ </NTag>
270
+ </template>
271
+ <template v-else-if="firstValue === 'unset'">
272
+ <NFlex :wrap="false" :size="0">
273
+ <NTag type="warning" :bordered="false" round
274
+ :style="{ margin: Language === 'ar' ? '0 0 0 10px' : '0 10px 0 0' }">
275
+ {{ t('unset') }}
276
+ </NTag>
277
+ <NFlex
278
+ v-if="(Array.isArray(secondValue) && secondValue.length) || secondValue"
279
+ :size="[4, 8]">
280
+ <NTag
281
+ v-for="value of ([] as string[]).concat(secondValue as string | string[])"
282
+ :bordered="false" round>
283
+ {{ formatValue(value) }}
284
+ </NTag>
285
+ </NFlex>
286
+ <NTag v-else :bordered="false" round>
287
+ --
288
+ </NTag>
289
+ </NFlex>
290
+ </template>
291
+ <template v-else>
292
+ <NFlex align="center" :wrap="false">
293
+ <NFlex :wrap="false" :size="0">
294
+ <NTag type="info" :bordered="false" style="padding: 0 13px;"
295
+ :style="{ borderRadius: Language === 'ar' ? '0 50px 50px 0' : '50px 0 0 50px' }">
296
+ {{ t('if') }}
297
+ </NTag>
298
+ <NTag type="primary" :bordered="false"
299
+ :style="{ borderRadius: Language === 'ar' ? '50px 0 0 50px' : '0 50px 50px 0', padding: '0 10px 0' }">
300
+ {{ formatValue(firstValue) }}
301
+ </NTag>
302
+ </NFlex>
303
+ <NTooltip :delay="1500">
304
+ <template #trigger>
305
+ <NTag round :bordered="false" size="small">
306
+ {{ secondValue }}
307
+ </NTag>
308
+ </template>
309
+ {{comparisonOperatorOptions().find(
310
+ ({ value }) => value === (secondValue ?? '='),
311
+ )?.label}}
312
+ </NTooltip>
313
+ <NTag v-if="thirdValue === undefined" :bordered="false" round>--
314
+ </NTag>
315
+ <NFlex v-else :wrap="false" :size="[4, 8]">
316
+ <NTag
317
+ v-for="value of ([] as string[]).concat(thirdValue as string | string[])"
318
+ :bordered="false" round>
319
+ {{ value === null ? '@null' : formatFlowValue(value, firstValue) }}
320
+ </NTag>
321
+ </NFlex>
322
+ </NFlex>
323
+ </template>
324
+ </template>
325
+ </NFlex>
326
+ </NScrollbar>
327
+ </NCard>
328
+ <NPopover placement="bottom" :delay="1500">
329
+ <template #trigger>
330
+ <NCard class="flowAddCard" style="cursor: pointer;" content-style="padding: 34px 0" hoverable
331
+ @click="() => {
332
+ if (tableCopy[currentFlow] && Array.isArray(tableCopy[currentFlow]))
333
+ currentFlowCard = `${currentFlow}-${tableCopy[currentFlow].push({ id: randomID(), value: [] }) - 1}`;
334
+ else {
335
+ tableCopy[currentFlow] = [{ id: randomID(), value: [] }];
336
+ currentFlowCard = `${currentFlow}-${0}`;
337
+ }
338
+ }">
339
+ <NFlex justify="center" align="center">
340
+ <NIcon :size="36">
341
+ <Icon name="tabler:plus" />
342
+ </NIcon>
343
+ </NFlex>
344
+ </NCard>
345
+ </template>
346
+ {{ t('newCard') }}
347
+ </NPopover>
348
+ </VueDraggable>
349
+ </NCard>
350
+ </template>
351
+
352
+ <script lang="ts" setup>
353
+ import {
354
+ flattenSchema,
355
+ isArrayOfObjects,
356
+ isNumber,
357
+ isObject
358
+ } from "inibase/utils"
359
+ import type { CascaderOption, SelectGroupOption, SelectOption } from "naive-ui"
360
+ import { VueDraggable } from "vue-draggable-plus"
361
+ import { Icon, NIcon } from "#components"
362
+
363
+ onMounted(() => {
364
+ document.onkeydown = (e) => {
365
+ if (
366
+ !(
367
+ (e.ctrlKey || e.metaKey) &&
368
+ (e.key.toLowerCase() === "s" || e.key === "س")
369
+ )
370
+ )
371
+ return
372
+ e.preventDefault()
373
+ saveFlow()
374
+ }
375
+ // Load template names for flow email action
376
+ $fetch<apiResponse>(
377
+ `${config.public.apiBase}${database.value.slug}/templates`,
378
+ {
379
+ params: { columns: "name", perPage: 100, locale: Language.value, [`${database.value.slug}_sid`]: sessionID.value },
380
+ credentials: "include",
381
+ },
382
+ ).then((data) => {
383
+ if (Array.isArray(data?.result))
384
+ templateNames.value = (data.result as { name: string }[]).map(({ name }) => ({
385
+ label: name,
386
+ value: name,
387
+ }))
388
+ }).catch(() => {})
389
+ })
390
+
391
+ const database = useState<Database>("database");
392
+ const Language = useScopedCookie<LanguagesType>("language", database.value?.slug)
393
+
394
+ const config = useRuntimeConfig()
395
+ const Loading = useState<Record<string, boolean>>("Loading", () => ({}))
396
+ const table = useState<Table>("table")
397
+ const tableCopy = ref<any>(toRaw(table.value))
398
+ const currentFlow = ref<string>("onRequest")
399
+ const currentFlowCard = ref<string>()
400
+
401
+ const sessionID = useScopedCookie<string>("sid", database.value?.slug)
402
+
403
+ const templateNames = ref<SelectOption[]>([])
404
+
405
+ const reorderEnabled = ref(false)
406
+
407
+ async function saveFlow() {
408
+ Loading.value.updateTable = true
409
+ const bodyContent = toRaw(tableCopy.value)
410
+
411
+ // Remove id and replace with value property before sending
412
+ for (const flowName of flowNames) {
413
+ bodyContent[flowName] = bodyContent[flowName]?.map((item: any) =>
414
+ item.value?.map((rule: any) => rule.value),
415
+ )
416
+ }
417
+
418
+ const data = await $fetch<apiResponse>(
419
+ `${config.public.apiBase}inicontent/databases/${database.value.slug
420
+ }/${table.value.slug}`,
421
+ {
422
+ method: "PUT",
423
+ body: (({ onResponse, onRequest }) => ({
424
+ onResponse,
425
+ onRequest,
426
+ }))(bodyContent),
427
+ params: {
428
+ locale: Language.value, [`${database.value.slug}_sid`]: sessionID.value,
429
+ },
430
+ credentials: "include",
431
+ },
432
+ )
433
+
434
+ if (data.result) {
435
+ tableCopy.value = formatFlow(data.result)
436
+ window.$message.success(data.message)
437
+ } else window.$message.error(data.message)
438
+ Loading.value.updateTable = false
439
+ }
440
+
441
+ const flowNames = ["onRequest", "onResponse"] as const
442
+ // Ensure each flow item has a unique id and a value property
443
+ function formatFlow(_table: Table) {
444
+ for (const flowName of flowNames) {
445
+ _table[flowName] = _table[flowName]?.map((item: any) => {
446
+ if (!item.id) {
447
+ return {
448
+ id: randomID(),
449
+ value: item.map((rule: any) => ({ id: randomID(), value: rule })),
450
+ }
451
+ }
452
+ return item
453
+ })
454
+ }
455
+ return _table
456
+ }
457
+
458
+ formatFlow(tableCopy.value)
459
+
460
+ function ruleDropdownProps(flow: FlowType, index: number) {
461
+ return {
462
+ options: [
463
+ {
464
+ label: t("delete"),
465
+ key: "delete",
466
+ icon: () => h(NIcon, () => h(Icon, { name: "tabler:trash" })),
467
+ },
468
+ ],
469
+ onSelect(key: string) {
470
+ switch (key) {
471
+ case "delete":
472
+ flow.splice(index, 1)
473
+ break
474
+ }
475
+ },
476
+ }
477
+ }
478
+ function schemaToOptions(schema: Schema, prefix = "@data") {
479
+ const options: CascaderOption[] = []
480
+ for (const field of schema) {
481
+ let option: CascaderOption = {
482
+ label: field.key,
483
+ value: `${prefix}.${field.id}`,
484
+ }
485
+ if (isArrayOfObjects(field.children))
486
+ option.children = schemaToOptions(field.children, prefix)
487
+ options.push(option)
488
+ }
489
+ return options
490
+ }
491
+ function generateFlowCascaderOptions(
492
+ withWhere?: boolean,
493
+ withWhereOr?: boolean,
494
+ withUser?: boolean,
495
+ ) {
496
+ const result: CascaderOption[] = []
497
+ if (withUser) {
498
+ let userSchema = database.value.tables?.find(
499
+ ({ slug }) => slug === "users",
500
+ )?.schema
501
+ if (userSchema) {
502
+ userSchema = flattenSchema(userSchema as any)
503
+ result.push({
504
+ label: "@user",
505
+ value: "@user",
506
+ children: userSchema.map(({ id, key }) => ({
507
+ label: key,
508
+ value: `@user.${id}`,
509
+ })),
510
+ })
511
+ }
512
+ }
513
+ if (table.value.schema) {
514
+ if (withWhere)
515
+ result.push({
516
+ label: "@where",
517
+ value: "@where",
518
+ children: [
519
+ ...(withWhereOr
520
+ ? [
521
+ {
522
+ label: t("or"),
523
+ value: "@where.or",
524
+ children: schemaToOptions(table.value.schema, "@where.or"),
525
+ },
526
+ ]
527
+ : []),
528
+ ...schemaToOptions(table.value.schema, "@where"),
529
+ ],
530
+ })
531
+ result.push({
532
+ label: "@data",
533
+ value: "@data",
534
+ children: schemaToOptions(table.value.schema),
535
+ })
536
+ }
537
+ return result
538
+ }
539
+ function generateFlowSelectOptions(
540
+ value: string | null,
541
+ withWhereOr = true,
542
+ withUser?: boolean,
543
+ ) {
544
+ const result: (SelectOption | SelectGroupOption)[] = []
545
+ if (value === "@method")
546
+ return ["get", "post", "put", "delete"].map((method) => ({
547
+ label: method.toUpperCase(),
548
+ value: method.toUpperCase(),
549
+ }))
550
+
551
+ let schema: Schema = []
552
+ if (table.value.schema) {
553
+ schema = flattenSchema(table.value.schema as any) as Schema
554
+ if (value) {
555
+ let userSchema = database.value.tables?.find(
556
+ ({ slug }) => slug === "users",
557
+ )?.schema
558
+ if (userSchema) userSchema = flattenSchema(userSchema as any)
559
+ const field = (value.startsWith("@user.") ? userSchema : schema)?.find(
560
+ ({ id }) => String(id) === value.slice(6),
561
+ )
562
+ if (field?.options)
563
+ field.options.map((_value) =>
564
+ result.push(
565
+ (isObject(_value)
566
+ ? _value
567
+ : Array.isArray(_value)
568
+ ? { label: _value[0], value: _value[0] }
569
+ : {
570
+ label: _value,
571
+ value: _value,
572
+ }) as any,
573
+ ),
574
+ )
575
+ }
576
+ }
577
+
578
+ result.push({
579
+ label: "@null",
580
+ value: "undefined",
581
+ })
582
+
583
+ if (
584
+ value &&
585
+ (value === "@user.4" ||
586
+ (table.value.slug === "users" && value.endsWith(".4")))
587
+ )
588
+ // @user.role
589
+ result.push({
590
+ key: "@role",
591
+ label: "@role",
592
+ type: "group",
593
+ children: database.value.roles?.map(({ name, id }) => ({
594
+ label: name,
595
+ value: id,
596
+ })),
597
+ })
598
+ if (withUser) {
599
+ let userSchema = database.value.tables?.find(
600
+ ({ slug }) => slug === "users",
601
+ )?.schema
602
+ if (userSchema) {
603
+ userSchema = flattenSchema(userSchema as any)
604
+ result.push({
605
+ key: "@user",
606
+ label: "@user",
607
+ type: "group",
608
+ children: userSchema.map(({ id, key }) => ({
609
+ label: `@user.${key}`,
610
+ value: `@user.${id}`,
611
+ })),
612
+ })
613
+ }
614
+ }
615
+ if (schema) {
616
+ result.push({
617
+ key: "@where",
618
+ label: "@where",
619
+ type: "group",
620
+ children: [
621
+ ...(withWhereOr
622
+ ? [
623
+ {
624
+ key: "@where.or",
625
+ label: "or",
626
+ type: "group",
627
+ children: schema.map(({ id, key }) => ({
628
+ label: `@where.or.${key}`,
629
+ value: `@where.or.${id}`,
630
+ })),
631
+ },
632
+ ]
633
+ : []),
634
+ ...schema.map(({ id, key }) => ({
635
+ label: `@where.${key}`,
636
+ value: `@where.${id}`,
637
+ })),
638
+ ],
639
+ })
640
+ result.push({
641
+ key: "@data",
642
+ label: "@data",
643
+ type: "group",
644
+ children: schema.map(({ id, key }) => ({
645
+ label: `@data.${key}`,
646
+ value: `@data.${id}`,
647
+ })),
648
+ })
649
+ }
650
+ return result
651
+ }
652
+
653
+ function formatValue(
654
+ value?: string | number | boolean | null,
655
+ parentKey?: string,
656
+ property: keyof Field = "key",
657
+ defaultValue?: string,
658
+ ) {
659
+ if (
660
+ parentKey &&
661
+ (parentKey === "@user.4" ||
662
+ (table.value.slug === "users" && parentKey.endsWith(".4"))) &&
663
+ value && !value.toString().startsWith("@")
664
+ )
665
+ return (
666
+ database.value.roles?.find(({ id }) => id === value)
667
+ ?.name ?? "@undefined"
668
+ )
669
+
670
+ if (
671
+ value &&
672
+ typeof value === "string" &&
673
+ (value.startsWith("@user.") ||
674
+ value.startsWith("@data.") ||
675
+ value.startsWith("@where."))
676
+ ) {
677
+ const splitedValue = value.split("."),
678
+ lastItem = splitedValue.pop()
679
+ let schema =
680
+ splitedValue[0] === "@user"
681
+ ? database.value?.tables?.find(({ slug }) => slug === "users")?.schema
682
+ : table.value.schema
683
+ if (schema) {
684
+ schema = flattenSchema(schema as any, true)
685
+ const item = schema.find(({ id }) => String(id) === lastItem)
686
+
687
+ if (!item) return undefined
688
+
689
+ if (property === "key") return `${splitedValue.join(".")}.${item?.key}`
690
+
691
+ return item[property] ?? defaultValue
692
+ }
693
+ }
694
+ return value || defaultValue
695
+ }
696
+
697
+ function getFlowField(value: string | null) {
698
+ if (!value || value === "@method") return undefined
699
+
700
+ const valueRoot = value.split(".")[0]
701
+ const fieldID = value.split(".").pop()
702
+ if (!fieldID) return undefined
703
+
704
+ const schema =
705
+ valueRoot === "@user"
706
+ ? database.value.tables?.find(({ slug }) => slug === "users")?.schema
707
+ : table.value.schema
708
+
709
+ if (!schema) return undefined
710
+
711
+ return flattenSchema(schema as any, true).find(
712
+ ({ id }) => String(id) === fieldID,
713
+ )
714
+ }
715
+
716
+ function isDateFlowField(value: string | null) {
717
+ return getFlowField(value)?.type === "date"
718
+ }
719
+
720
+ function getDateFlowPickerType(value: string | null) {
721
+ return getFlowField(value)?.date ?? "date"
722
+ }
723
+
724
+ function isRelativeDateValue(value: unknown) {
725
+ return (
726
+ typeof value === "string" &&
727
+ ["@date.now", "@date.tomorrow", "@date.yesterday"].includes(value)
728
+ )
729
+ }
730
+
731
+ function formatFlowValue(value: unknown, parentKey?: string | null) {
732
+ const field = getFlowField(parentKey ?? null)
733
+ if (field?.type !== "date") return formatValue(value as any, parentKey ?? undefined)
734
+
735
+ if (typeof value === "string") {
736
+ switch (value) {
737
+ case "@date.now":
738
+ return "Now"
739
+ case "@date.tomorrow":
740
+ return "Tomorrow"
741
+ case "@date.yesterday":
742
+ return "Yesterday"
743
+ default:
744
+ if (/^\d+$/.test(value)) value = Number(value)
745
+ }
746
+ }
747
+
748
+ if (typeof value === "number") {
749
+ const date = new Date(value)
750
+ if (Number.isNaN(date.getTime())) return String(value)
751
+ return ["datetime", "datetimerange"].includes(getDateFlowPickerType(parentKey ?? null))
752
+ ? date.toLocaleString(Language.value)
753
+ : date.toLocaleDateString(Language.value)
754
+ }
755
+
756
+ return formatValue(value as any, parentKey ?? undefined)
757
+ }
758
+
759
+ const dateFlowValueOptions = [
760
+ { label: "Now", value: "@date.now" },
761
+ { label: "Tomorrow", value: "@date.tomorrow" },
762
+ { label: "Yesterday", value: "@date.yesterday" },
763
+ { label: "Custom", value: "__custom__" },
764
+ ]
765
+
766
+ const addRuleDropdownOptions = [
767
+ {
768
+ key: "if",
769
+ label: t("condition"),
770
+ },
771
+ {
772
+ key: "set",
773
+ label: t("set"),
774
+ show: table.value.slug !== "assets",
775
+ },
776
+ {
777
+ key: "unset",
778
+ label: t("unset"),
779
+ show: table.value.slug !== "assets",
780
+ },
781
+ {
782
+ key: "error",
783
+ label: t("throwError"),
784
+ },
785
+ {
786
+ key: "email",
787
+ label: t("email"),
788
+ },
789
+ ]
790
+
791
+ function pushRuleToFlow(flow: any, value: "if" | "set" | "unset" | "error" | "email") {
792
+ switch (value) {
793
+ case "if":
794
+ flow.push({ id: randomID(), value: [null, null, null] })
795
+ break
796
+ case "set":
797
+ flow.push({ id: randomID(), value: ["set", null, null] })
798
+ break
799
+ case "email":
800
+ flow.push({ id: randomID(), value: ["email", null, null] })
801
+ break
802
+ default:
803
+ flow.push({ id: randomID(), value: [value, null] })
804
+ break
805
+ }
806
+ }
807
+
808
+ useHead({
809
+ title: `${t(database.value.slug)} | ${t(table.value.slug)} : ${t("flows")}`,
810
+ link: [
811
+ { rel: "icon", href: database.value?.icon?.publicURL ?? "/favicon.ico" },
812
+ ],
813
+ })
814
+ </script>