windmill-components 1.596.0 → 1.596.1

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 (75) hide show
  1. package/package/common.d.ts +2 -0
  2. package/package/components/ArgInput.svelte +4 -2
  3. package/package/components/ArgInput.svelte.d.ts +1 -0
  4. package/package/components/ArrayTypeNarrowing.svelte +8 -7
  5. package/package/components/DBManager.svelte +377 -41
  6. package/package/components/DBManager.svelte.d.ts +16 -1
  7. package/package/components/DBManagerContent.svelte +195 -0
  8. package/package/components/DBManagerContent.svelte.d.ts +24 -0
  9. package/package/components/DBManagerDrawer.svelte +90 -175
  10. package/package/components/DBManagerDrawer.svelte.d.ts +5 -1
  11. package/package/components/InputTransformForm.svelte +13 -1
  12. package/package/components/InputTransformForm.svelte.d.ts +1 -0
  13. package/package/components/InputTransformSchemaForm.svelte +2 -1
  14. package/package/components/InputTransformSchemaForm.svelte.d.ts +1 -0
  15. package/package/components/SchemaForm.svelte +21 -7
  16. package/package/components/SchemaForm.svelte.d.ts +1 -0
  17. package/package/components/ScriptBuilder.svelte +14 -0
  18. package/package/components/common/drawer/DrawerContent.svelte +4 -1
  19. package/package/components/common/drawer/DrawerContent.svelte.d.ts +1 -0
  20. package/package/components/common/modal/Modal.svelte +13 -17
  21. package/package/components/common/modal/Modal.svelte.d.ts +13 -23
  22. package/package/components/copilot/chat/AIChatDisplay.svelte +3 -1
  23. package/package/components/copilot/chat/AIChatManager.svelte.js +2 -0
  24. package/package/components/copilot/chat/DatatableCreationPolicy.svelte +63 -0
  25. package/package/components/copilot/chat/DatatableCreationPolicy.svelte.d.ts +3 -0
  26. package/package/components/copilot/chat/__tests__/app/appEvalHelpers.js +13 -0
  27. package/package/components/copilot/chat/app/core.d.ts +27 -0
  28. package/package/components/copilot/chat/app/core.js +207 -2
  29. package/package/components/copilot/chat/flow/core.js +1 -1
  30. package/package/components/copilot/chat/script/core.js +1 -1
  31. package/package/components/copilot/chat/sharedChatState.svelte.js +3 -1
  32. package/package/components/dbOps.d.ts +3 -0
  33. package/package/components/dbOps.js +10 -1
  34. package/package/components/flows/CreateActionsApp.svelte +114 -9
  35. package/package/components/flows/CreateActionsApp.svelte.d.ts +2 -17
  36. package/package/components/flows/DebounceLimit.svelte +64 -1
  37. package/package/components/flows/DebounceLimit.svelte.d.ts +6 -1
  38. package/package/components/flows/content/FlowModuleComponent.svelte +1 -0
  39. package/package/components/flows/content/FlowSettings.svelte +4 -0
  40. package/package/components/flows/conversations/FlowChatManager.svelte.js +0 -1
  41. package/package/components/flows/flowInfers.js +19 -1
  42. package/package/components/flows/flowStore.svelte.d.ts +2 -0
  43. package/package/components/flows/utils.svelte.js +17 -0
  44. package/package/components/icons/index.d.ts +101 -0
  45. package/package/components/icons/index.js +1 -1
  46. package/package/components/raw_apps/DefaultDatabaseSelector.svelte +61 -0
  47. package/package/components/raw_apps/DefaultDatabaseSelector.svelte.d.ts +13 -0
  48. package/package/components/raw_apps/RawAppBackgroundRunner.svelte +17 -1
  49. package/package/components/raw_apps/RawAppDataTableDrawer.svelte +205 -0
  50. package/package/components/raw_apps/RawAppDataTableDrawer.svelte.d.ts +14 -0
  51. package/package/components/raw_apps/RawAppDataTableList.svelte +160 -0
  52. package/package/components/raw_apps/RawAppDataTableList.svelte.d.ts +22 -0
  53. package/package/components/raw_apps/RawAppEditor.svelte +153 -9
  54. package/package/components/raw_apps/RawAppEditor.svelte.d.ts +3 -0
  55. package/package/components/raw_apps/RawAppEditorHeader.svelte +2 -2
  56. package/package/components/raw_apps/RawAppEditorHeader.svelte.d.ts +3 -0
  57. package/package/components/raw_apps/RawAppHistoryManager.svelte.d.ts +5 -2
  58. package/package/components/raw_apps/RawAppHistoryManager.svelte.js +11 -9
  59. package/package/components/raw_apps/RawAppSidebar.svelte +36 -4
  60. package/package/components/raw_apps/RawAppSidebar.svelte.d.ts +8 -0
  61. package/package/components/raw_apps/dataTableRefUtils.d.ts +30 -0
  62. package/package/components/raw_apps/dataTableRefUtils.js +40 -0
  63. package/package/components/raw_apps/datatableUtils.svelte.d.ts +24 -0
  64. package/package/components/raw_apps/datatableUtils.svelte.js +69 -0
  65. package/package/gen/schemas.gen.d.ts +39 -0
  66. package/package/gen/schemas.gen.js +39 -0
  67. package/package/gen/services.gen.d.ts +10 -1
  68. package/package/gen/services.gen.js +18 -0
  69. package/package/gen/types.gen.d.ts +45 -0
  70. package/package/system_prompts/index.d.ts +3 -0
  71. package/package/system_prompts/index.js +33 -0
  72. package/package/system_prompts/prompts.d.ts +25 -0
  73. package/package/system_prompts/prompts.js +2598 -0
  74. package/package/utils.js +3 -1
  75. package/package.json +19 -3
@@ -43,6 +43,7 @@ export interface SchemaProperty {
43
43
  };
44
44
  required?: string[];
45
45
  showExpr?: string;
46
+ hideWhenChatEnabled?: boolean;
46
47
  password?: boolean;
47
48
  order?: string[];
48
49
  nullable?: boolean;
@@ -53,6 +54,7 @@ export interface SchemaProperty {
53
54
  originalType?: string;
54
55
  disabled?: boolean;
55
56
  'x-no-s3-storage-workspace-warning'?: string;
57
+ 'x-auto-generate'?: boolean;
56
58
  }
57
59
  export interface ModalSchemaProperty {
58
60
  selectedType?: string;
@@ -32,8 +32,9 @@ import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte';
32
32
  import AIProviderPicker from './AIProviderPicker.svelte';
33
33
  import TextInput from './text_input/TextInput.svelte';
34
34
  import FileInput from './common/fileInput/FileInput.svelte';
35
+ import { randomUUID } from './flows/conversations/FlowChatManager.svelte';
35
36
  let { label = '', value = $bindable(), defaultValue = $bindable(undefined), description = $bindable(undefined), format = $bindable(undefined), contentEncoding = undefined, type = undefined, oneOf = $bindable(undefined), required = false, pattern = $bindable(undefined), valid = $bindable(undefined), // Note : this should not exist, valid and error should be one coherent state
36
- enum_ = $bindable(undefined), disabled = false, itemsType = $bindable(undefined), displayHeader = true, properties = $bindable(undefined), nestedRequired = undefined, autofocus = null, compact = false, password = false, pickForField = $bindable(undefined), variableEditor = undefined, itemPicker = undefined, noMargin = false, extra = {}, minW = true, prettifyHeader = false, resourceTypes, disablePortal = false, showSchemaExplorer = false, simpleTooltip = undefined, customErrorMessage = undefined, onlyMaskPassword = false, nullable = false, title = $bindable(undefined), placeholder = $bindable(undefined), order = $bindable(undefined), editor = $bindable(undefined), orderEditable = false, shouldDispatchChanges = false, noDefaultOnSelectFirst = false, helperScript = undefined, otherArgs = {}, lightHeader = false, diffStatus = undefined, hideNested = false, nestedParent = undefined, nestedClasses = '', displayType = true, css = undefined, appPath = undefined, computeS3ForceViewerPolicies = undefined, workspace = undefined, s3StorageConfigured = true, actions, innerBottomSnippet, fieldHeaderActions, lightHeaderFont = false } = $props();
37
+ enum_ = $bindable(undefined), disabled = false, itemsType = $bindable(undefined), displayHeader = true, properties = $bindable(undefined), nestedRequired = undefined, autofocus = null, compact = false, password = false, pickForField = $bindable(undefined), variableEditor = undefined, itemPicker = undefined, noMargin = false, extra = {}, minW = true, prettifyHeader = false, resourceTypes, disablePortal = false, showSchemaExplorer = false, simpleTooltip = undefined, customErrorMessage = undefined, onlyMaskPassword = false, nullable = false, title = $bindable(undefined), placeholder = $bindable(undefined), order = $bindable(undefined), editor = $bindable(undefined), orderEditable = false, shouldDispatchChanges = false, noDefaultOnSelectFirst = false, helperScript = undefined, otherArgs = {}, lightHeader = false, diffStatus = undefined, hideNested = false, nestedParent = undefined, nestedClasses = '', displayType = true, css = undefined, appPath = undefined, computeS3ForceViewerPolicies = undefined, workspace = undefined, s3StorageConfigured = true, chatInputEnabled = false, actions, innerBottomSnippet, fieldHeaderActions, lightHeaderFont = false } = $props();
37
38
  $effect(() => {
38
39
  if (description == undefined) {
39
40
  description = '';
@@ -91,7 +92,7 @@ function computeDefaultValue(inputCat, defaultValue, nnullable) {
91
92
  nvalue = structuredClone($state.snapshot(defaultValue));
92
93
  if (defaultValue === undefined || defaultValue === null) {
93
94
  if (inputCat === 'string') {
94
- nvalue = nullable ? null : '';
95
+ nvalue = nullable ? null : format === 'uuid' && extra?.['x-auto-generate'] ? randomUUID() : '';
95
96
  }
96
97
  else if (inputCat == 'enum' && required) {
97
98
  let firstV = enum_?.[0];
@@ -965,6 +966,7 @@ onDestroy(() => {
965
966
  {disablePortal}
966
967
  {disabled}
967
968
  {prettifyHeader}
969
+ {chatInputEnabled}
968
970
  hiddenArgs={['label', 'kind']}
969
971
  schema={{
970
972
  properties: obj.properties,
@@ -80,6 +80,7 @@ interface Props {
80
80
  } | undefined) | undefined;
81
81
  workspace?: string | undefined;
82
82
  s3StorageConfigured?: boolean;
83
+ chatInputEnabled?: boolean;
83
84
  actions?: import('svelte').Snippet;
84
85
  innerBottomSnippet?: import('svelte').Snippet;
85
86
  fieldHeaderActions?: import('svelte').Snippet;
@@ -151,13 +151,14 @@ let selected = $state(itemsType?.type != 'string'
151
151
  () => {
152
152
  return itemsType?.properties != undefined
153
153
  },
154
- async (v) => {
155
- await tick()
156
- if (v) {
157
- itemsType = { type: 'object', properties: {} }
158
- } else {
159
- itemsType = { type: 'object', properties: undefined }
160
- }
154
+ (v) => {
155
+ tick().then(() => {
156
+ if (v) {
157
+ itemsType = { type: 'object', properties: {} }
158
+ } else {
159
+ itemsType = { type: 'object', properties: undefined }
160
+ }
161
+ })
161
162
  }
162
163
  }
163
164
  options={{ left: 'JSON', right: 'Custom Object' }}
@@ -12,7 +12,68 @@ import DbTableEditor from './DBTableEditor.svelte';
12
12
  import Portal from './Portal.svelte';
13
13
  import Select from './select/Select.svelte';
14
14
  import { safeSelectItems } from './select/utils.svelte';
15
- let { dbType, dbSchema, dbTableOpsFactory, dbSchemaOps, getColDefs, dbSupportsSchemas, refresh, initialSchemaKey, initialTableKey } = $props();
15
+ let { dbType, dbSchema, dbTableOpsFactory, dbSchemaOps, getColDefs, dbSupportsSchemas, refresh, initialSchemaKey, initialTableKey, selectedSchemaKey = $bindable(undefined), selectedTableKey = $bindable(undefined), dbSelector, multiSelectMode = false, selectedTables = $bindable([]), disabledTables = [] } = $props();
16
+ // Helper to check if a table is selected in multi-select mode
17
+ function isTableSelected(schema, table) {
18
+ return selectedTables.some((t) => t.schema === schema && t.table === table);
19
+ }
20
+ // Helper to check if a table is disabled (already added)
21
+ function isTableDisabled(schema, table) {
22
+ return disabledTables.some((t) => t.schema === schema && t.table === table);
23
+ }
24
+ // Toggle table selection in multi-select mode
25
+ function toggleTableSelection(schema, table) {
26
+ if (isTableDisabled(schema, table))
27
+ return;
28
+ const idx = selectedTables.findIndex((t) => t.schema === schema && t.table === table);
29
+ if (idx >= 0) {
30
+ selectedTables = selectedTables.filter((_, i) => i !== idx);
31
+ }
32
+ else {
33
+ selectedTables = [...selectedTables, { schema, table }];
34
+ }
35
+ }
36
+ // Get tables for a schema (filtered by search)
37
+ function getTablesForSchema(schema) {
38
+ const tables = Object.keys(dbSchema.schema[schema] ?? {});
39
+ if (search) {
40
+ return tables.filter((t) => t.toLowerCase().includes(search.toLowerCase())).sort();
41
+ }
42
+ return tables.sort();
43
+ }
44
+ // Check if all selectable tables in a schema are selected
45
+ function isSchemaFullySelected(schema) {
46
+ const tables = getTablesForSchema(schema);
47
+ if (tables.length === 0)
48
+ return false;
49
+ const selectableTables = tables.filter((t) => !isTableDisabled(schema, t));
50
+ if (selectableTables.length === 0)
51
+ return true; // All disabled means "fully selected"
52
+ return selectableTables.every((t) => isTableSelected(schema, t));
53
+ }
54
+ // Check if some (but not all) tables in a schema are selected
55
+ function isSchemaPartiallySelected(schema) {
56
+ const tables = getTablesForSchema(schema);
57
+ const selectableTables = tables.filter((t) => !isTableDisabled(schema, t));
58
+ const selectedCount = selectableTables.filter((t) => isTableSelected(schema, t)).length;
59
+ return selectedCount > 0 && selectedCount < selectableTables.length;
60
+ }
61
+ // Toggle all tables in a schema
62
+ function toggleSchemaSelection(schema) {
63
+ const tables = getTablesForSchema(schema);
64
+ const selectableTables = tables.filter((t) => !isTableDisabled(schema, t));
65
+ if (isSchemaFullySelected(schema)) {
66
+ // Deselect all selectable tables in this schema
67
+ selectedTables = selectedTables.filter((t) => t.schema !== schema);
68
+ }
69
+ else {
70
+ // Select all selectable tables in this schema
71
+ const newSelections = selectableTables
72
+ .filter((t) => !isTableSelected(schema, t))
73
+ .map((t) => ({ schema, table: t }));
74
+ selectedTables = [...selectedTables, ...newSelections];
75
+ }
76
+ }
16
77
  let schemaKeys = $derived(Object.keys(dbSchema.schema ?? {}));
17
78
  let search = $state('');
18
79
  let selected = $state({});
@@ -26,6 +87,15 @@ $effect(() => {
26
87
  selected = { schemaKey, tableKey };
27
88
  }
28
89
  });
90
+ // Sync selected state with bindable props
91
+ $effect(() => {
92
+ if (selected.schemaKey) {
93
+ selectedSchemaKey = selected.schemaKey;
94
+ }
95
+ if (selected.tableKey) {
96
+ selectedTableKey = selected.tableKey;
97
+ }
98
+ });
29
99
  let tableKeys = $derived.by(() => {
30
100
  if (dbSchema.lang === 'graphql') {
31
101
  sendUserToast('graphql not supported by DBExplorerTable', true);
@@ -50,12 +120,23 @@ let tableKey = $derived(dbSupportsSchemas && selected.schemaKey
50
120
  : selected.tableKey);
51
121
  let askingForConfirmation = $state();
52
122
  let dbTableEditorState = $state({ open: false });
123
+ let newSchemaDialogOpen = $state(false);
124
+ let newSchemaName = $state('');
125
+ // Check if the sanitized schema name already exists
126
+ const sanitizedNewSchemaName = $derived(newSchemaName
127
+ .trim()
128
+ .toLowerCase()
129
+ .replace(/[^a-zA-Z0-9_]/g, ''));
130
+ const schemaAlreadyExists = $derived(sanitizedNewSchemaName !== '' && schemaKeys.includes(sanitizedNewSchemaName));
53
131
  </script>
54
132
 
55
133
  <Splitpanes>
56
134
  <Pane size={24} class="relative flex flex-col">
57
135
  <div class="mx-3 mt-3 flex flex-col gap-2">
58
- {#if dbSupportsSchemas}
136
+ {#if dbSelector}
137
+ {@render dbSelector()}
138
+ {/if}
139
+ {#if dbSupportsSchemas && !multiSelectMode}
59
140
  <Select
60
141
  bind:value={selected.schemaKey}
61
142
  items={safeSelectItems(schemaKeys)}
@@ -90,28 +171,139 @@ let dbTableEditorState = $state({ open: false });
90
171
  <ClearableInput bind:value={search} placeholder="Search table..." />
91
172
  </div>
92
173
  <div class="overflow-x-clip overflow-y-auto relative mt-3 border-y flex-1">
93
- {#each filteredTableKeys as tableKey}
94
- <button
95
- class={'w-full text-sm font-normal flex gap-2 items-center h-10 cursor-pointer pl-3 pr-1 ' +
96
- (selected.tableKey === tableKey ? 'bg-gray-500/25' : 'hover:bg-gray-500/10')}
97
- onclick={() => (selected.tableKey = tableKey)}
98
- >
99
- <Table2 class="text-primary shrink-0" size={16} />
100
- <p class="truncate text-ellipsis grow text-left text-emphasis text-xs">{tableKey}</p>
101
- <DropdownV2
102
- items={() => [
103
- {
104
- displayName: 'Delete table',
105
- icon: Trash2Icon,
106
- action: () =>
107
- (askingForConfirmation = {
108
- title: `Are you sure you want to delete ${tableKey} ? This action is irreversible`,
109
- confirmationText: 'Delete permanently',
174
+ {#if multiSelectMode}
175
+ <!-- Multi-select mode: show all schemas with their tables -->
176
+ {#if dbSupportsSchemas}
177
+ <!-- New schema button -->
178
+ <button
179
+ class="w-full text-sm font-medium flex gap-2 items-center h-9 cursor-pointer pl-3 pr-1 hover:bg-gray-500/10 border-b border-surface-secondary text-tertiary"
180
+ onclick={() => (newSchemaDialogOpen = true)}
181
+ >
182
+ <Plus class="shrink-0" size={14} />
183
+ <span class="text-xs">New schema</span>
184
+ </button>
185
+ {/if}
186
+ {#each schemaKeys as schemaKey}
187
+ {@const schemaTables = getTablesForSchema(schemaKey)}
188
+ {@const isFullySelected = isSchemaFullySelected(schemaKey)}
189
+ {@const isPartiallySelected = isSchemaPartiallySelected(schemaKey)}
190
+ {@const hasNoTables = schemaTables.length === 0}
191
+ <!-- Schema header with checkbox (or just label if empty) -->
192
+ <div
193
+ class="group w-full text-sm font-medium flex gap-2 items-center h-9 cursor-pointer pl-3 pr-1 hover:bg-gray-500/10 border-b border-surface-secondary"
194
+ role="button"
195
+ tabindex="0"
196
+ onclick={() => {
197
+ if (!hasNoTables) {
198
+ toggleSchemaSelection(schemaKey)
199
+ }
200
+ }}
201
+ onkeydown={(e) => {
202
+ if (e.key === 'Enter' || e.key === ' ') {
203
+ if (!hasNoTables) {
204
+ toggleSchemaSelection(schemaKey)
205
+ }
206
+ }
207
+ }}
208
+ >
209
+ {#if hasNoTables}
210
+ <!-- Empty schema: no checkbox, just indent space -->
211
+ <span class="shrink-0 w-4"></span>
212
+ {:else}
213
+ <span class="shrink-0">
214
+ <input
215
+ type="checkbox"
216
+ checked={isFullySelected}
217
+ indeterminate={isPartiallySelected}
218
+ class="w-4 h-4 cursor-pointer"
219
+ onclick={(e) => e.stopPropagation()}
220
+ onchange={() => toggleSchemaSelection(schemaKey)}
221
+ />
222
+ </span>
223
+ {/if}
224
+ <span class="truncate text-ellipsis grow text-left text-tertiary text-xs"
225
+ >{schemaKey}</span
226
+ >
227
+ <span class="text-2xs text-tertiary mr-2 group-hover:hidden">{schemaTables.length}</span>
228
+ <!-- Delete schema button (on hover) -->
229
+ <button
230
+ class="hidden group-hover:flex p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors mr-1"
231
+ title="Delete schema"
232
+ onclick={(e) => {
233
+ e.stopPropagation()
234
+ askingForConfirmation = {
235
+ title: `Are you sure you want to delete schema "${schemaKey}"? This will drop all tables in this schema. This action is irreversible.`,
236
+ confirmationText: 'Drop schema',
237
+ open: true,
238
+ onConfirm: async () => {
239
+ askingForConfirmation && (askingForConfirmation.loading = true)
240
+ try {
241
+ await dbSchemaOps.onDeleteSchema({ schema: schemaKey })
242
+ refresh?.()
243
+ sendUserToast(`Schema '${schemaKey}' deleted successfully`)
244
+ } catch (e) {
245
+ let msg: string | undefined = (e as Error).message
246
+ if (typeof msg !== 'string') msg = e ? JSON.stringify(e) : undefined
247
+ sendUserToast(msg ?? 'Action failed!', true)
248
+ }
249
+ askingForConfirmation = undefined
250
+ }
251
+ }
252
+ }}
253
+ >
254
+ <Trash2Icon size={12} class="text-red-500" />
255
+ </button>
256
+ </div>
257
+ <!-- Tables under this schema -->
258
+ {#each schemaTables as tableKey}
259
+ {@const isDisabled = isTableDisabled(schemaKey, tableKey)}
260
+ {@const isChecked = isTableSelected(schemaKey, tableKey) || isDisabled}
261
+ {@const isCurrentPreview = selected.schemaKey === schemaKey && selected.tableKey === tableKey}
262
+ <div
263
+ class={'group w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pl-7 pr-1 ' +
264
+ (isCurrentPreview ? 'bg-gray-500/25' : 'hover:bg-gray-500/10') +
265
+ (isDisabled ? ' opacity-50' : '')}
266
+ role="button"
267
+ tabindex="0"
268
+ onclick={() => {
269
+ selected.schemaKey = schemaKey
270
+ selected.tableKey = tableKey
271
+ toggleTableSelection(schemaKey, tableKey)
272
+ }}
273
+ onkeydown={(e) => {
274
+ if (e.key === 'Enter' || e.key === ' ') {
275
+ selected.schemaKey = schemaKey
276
+ selected.tableKey = tableKey
277
+ toggleTableSelection(schemaKey, tableKey)
278
+ }
279
+ }}
280
+ >
281
+ <span class="shrink-0">
282
+ <input
283
+ type="checkbox"
284
+ checked={isChecked}
285
+ disabled={isDisabled}
286
+ class="w-4 h-4 cursor-pointer"
287
+ onclick={(e) => e.stopPropagation()}
288
+ onchange={() => toggleTableSelection(schemaKey, tableKey)}
289
+ />
290
+ </span>
291
+ <Table2 class="text-primary shrink-0" size={14} />
292
+ <p class="truncate text-ellipsis grow text-left text-emphasis text-xs">{tableKey}</p>
293
+ <!-- Delete table button (on hover) -->
294
+ <button
295
+ class="hidden group-hover:flex p-1 hover:bg-red-100 dark:hover:bg-red-900/30 rounded transition-colors mr-1"
296
+ title="Delete table"
297
+ onclick={(e) => {
298
+ e.stopPropagation()
299
+ askingForConfirmation = {
300
+ title: `Are you sure you want to delete table "${tableKey}"? This action is irreversible.`,
301
+ confirmationText: 'Drop table',
110
302
  open: true,
111
303
  onConfirm: async () => {
112
304
  askingForConfirmation && (askingForConfirmation.loading = true)
113
305
  try {
114
- await dbSchemaOps.onDelete({ tableKey, schema: selected.schemaKey })
306
+ await dbSchemaOps.onDelete({ tableKey, schema: schemaKey })
115
307
  refresh?.()
116
308
  sendUserToast(`Table '${tableKey}' deleted successfully`)
117
309
  } catch (e) {
@@ -121,29 +313,84 @@ let dbTableEditorState = $state({ open: false });
121
313
  }
122
314
  askingForConfirmation = undefined
123
315
  }
124
- })
125
- }
126
- ]}
127
- class="w-fit"
316
+ }
317
+ }}
318
+ >
319
+ <Trash2Icon size={12} class="text-red-500" />
320
+ </button>
321
+ </div>
322
+ {/each}
323
+ <!-- New table button for this schema -->
324
+ <button
325
+ class="w-full text-sm font-normal flex gap-2 items-center h-8 cursor-pointer pl-7 pr-1 hover:bg-gray-500/10 text-tertiary"
326
+ onclick={() => {
327
+ selected.schemaKey = schemaKey
328
+ dbTableEditorState = { open: true }
329
+ }}
128
330
  >
129
- <svelte:fragment slot="buttonReplacement">
130
- <MoreVertical
131
- size={8}
132
- class="w-8 h-8 p-2 hover:bg-surface-hover cursor-pointer rounded-md"
133
- />
134
- </svelte:fragment>
135
- </DropdownV2>
136
- </button>
137
- {/each}
331
+ <Plus class="shrink-0" size={14} />
332
+ <span class="text-xs">New table</span>
333
+ </button>
334
+ {/each}
335
+ {:else}
336
+ <!-- Normal mode: show tables for selected schema -->
337
+ {#each filteredTableKeys as tableKey}
338
+ <button
339
+ class={'w-full text-sm font-normal flex gap-2 items-center h-10 cursor-pointer pl-3 pr-1 ' +
340
+ (selected.tableKey === tableKey ? 'bg-gray-500/25' : 'hover:bg-gray-500/10')}
341
+ onclick={() => (selected.tableKey = tableKey)}
342
+ >
343
+ <Table2 class="text-primary shrink-0" size={16} />
344
+ <p class="truncate text-ellipsis grow text-left text-emphasis text-xs">{tableKey}</p>
345
+ <DropdownV2
346
+ items={() => [
347
+ {
348
+ displayName: 'Delete table',
349
+ icon: Trash2Icon,
350
+ action: () =>
351
+ (askingForConfirmation = {
352
+ title: `Are you sure you want to delete ${tableKey} ? This action is irreversible`,
353
+ confirmationText: 'Delete permanently',
354
+ open: true,
355
+ onConfirm: async () => {
356
+ askingForConfirmation && (askingForConfirmation.loading = true)
357
+ try {
358
+ await dbSchemaOps.onDelete({ tableKey, schema: selected.schemaKey })
359
+ refresh?.()
360
+ sendUserToast(`Table '${tableKey}' deleted successfully`)
361
+ } catch (e) {
362
+ let msg: string | undefined = (e as Error).message
363
+ if (typeof msg !== 'string') msg = e ? JSON.stringify(e) : undefined
364
+ sendUserToast(msg ?? 'Action failed!', true)
365
+ }
366
+ askingForConfirmation = undefined
367
+ }
368
+ })
369
+ }
370
+ ]}
371
+ class="w-fit"
372
+ >
373
+ <svelte:fragment slot="buttonReplacement">
374
+ <MoreVertical
375
+ size={8}
376
+ class="w-8 h-8 p-2 hover:bg-surface-hover cursor-pointer rounded-md"
377
+ />
378
+ </svelte:fragment>
379
+ </DropdownV2>
380
+ </button>
381
+ {/each}
382
+ {/if}
138
383
  </div>
139
- <Button
140
- on:click={() => (dbTableEditorState = { open: true })}
141
- wrapperClasses="mx-2 my-2 text-sm"
142
- startIcon={{ icon: Plus }}
143
- variant={tableKeys.length === 0 ? 'accent' : 'default'}
144
- >
145
- New table
146
- </Button>
384
+ {#if !multiSelectMode}
385
+ <Button
386
+ on:click={() => (dbTableEditorState = { open: true })}
387
+ wrapperClasses="mx-2 my-2 text-sm"
388
+ startIcon={{ icon: Plus }}
389
+ variant={tableKeys.length === 0 ? 'accent' : 'default'}
390
+ >
391
+ New table
392
+ </Button>
393
+ {/if}
147
394
  </Pane>
148
395
  <Pane class="p-3 pt-1">
149
396
  {#if tableKey}
@@ -184,3 +431,92 @@ let dbTableEditorState = $state({ open: false });
184
431
  />
185
432
  </DrawerContent>
186
433
  </Drawer>
434
+
435
+ <Drawer
436
+ size="400px"
437
+ open={newSchemaDialogOpen}
438
+ on:close={() => {
439
+ newSchemaDialogOpen = false
440
+ newSchemaName = ''
441
+ }}
442
+ >
443
+ <DrawerContent
444
+ on:close={() => {
445
+ newSchemaDialogOpen = false
446
+ newSchemaName = ''
447
+ }}
448
+ title="Create a new schema"
449
+ >
450
+ <div class="flex flex-col gap-4">
451
+ <div>
452
+ <label for="schema-name" class="block text-sm font-medium text-primary mb-1"
453
+ >Schema name</label
454
+ >
455
+ <ClearableInput
456
+ bind:value={newSchemaName}
457
+ placeholder="Enter schema name..."
458
+ autofocus
459
+ on:keydown={(e) => {
460
+ if (e.key === 'Enter' && sanitizedNewSchemaName && !schemaAlreadyExists) {
461
+ askingForConfirmation = {
462
+ confirmationText: `Create ${sanitizedNewSchemaName}`,
463
+ type: 'reload',
464
+ title: `This will run 'CREATE SCHEMA ${sanitizedNewSchemaName}' on your database. Are you sure?`,
465
+ open: true,
466
+ onConfirm: async () => {
467
+ askingForConfirmation && (askingForConfirmation.loading = true)
468
+ try {
469
+ await dbSchemaOps.onCreateSchema({ schema: sanitizedNewSchemaName })
470
+ refresh?.()
471
+ selected.schemaKey = sanitizedNewSchemaName
472
+ newSchemaDialogOpen = false
473
+ newSchemaName = ''
474
+ } finally {
475
+ askingForConfirmation = undefined
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }}
481
+ />
482
+ {#if schemaAlreadyExists}
483
+ <p class="text-xs text-red-500 mt-1">
484
+ Schema "{sanitizedNewSchemaName}" already exists
485
+ </p>
486
+ {:else}
487
+ <p class="text-xs text-tertiary mt-1">
488
+ Only letters, numbers, and underscores are allowed.
489
+ </p>
490
+ {/if}
491
+ </div>
492
+ </div>
493
+ {#snippet actions()}
494
+ <Button
495
+ color="blue"
496
+ disabled={!sanitizedNewSchemaName || schemaAlreadyExists}
497
+ on:click={() => {
498
+ askingForConfirmation = {
499
+ confirmationText: `Create ${sanitizedNewSchemaName}`,
500
+ type: 'reload',
501
+ title: `This will run 'CREATE SCHEMA ${sanitizedNewSchemaName}' on your database. Are you sure?`,
502
+ open: true,
503
+ onConfirm: async () => {
504
+ askingForConfirmation && (askingForConfirmation.loading = true)
505
+ try {
506
+ await dbSchemaOps.onCreateSchema({ schema: sanitizedNewSchemaName })
507
+ refresh?.()
508
+ selected.schemaKey = sanitizedNewSchemaName
509
+ newSchemaDialogOpen = false
510
+ newSchemaName = ''
511
+ } finally {
512
+ askingForConfirmation = undefined
513
+ }
514
+ }
515
+ }
516
+ }}
517
+ >
518
+ Create schema
519
+ </Button>
520
+ {/snippet}
521
+ </DrawerContent>
522
+ </Drawer>
@@ -2,6 +2,12 @@ import { type DBSchema } from '../stores';
2
2
  import { type ColumnDef } from './apps/components/display/dbtable/utils';
3
3
  import type { IDbSchemaOps, IDbTableOps } from './dbOps';
4
4
  import type { DbType } from './dbTypes';
5
+ import type { Snippet } from 'svelte';
6
+ /** Represents a selected table with its schema */
7
+ export interface SelectedTable {
8
+ schema: string;
9
+ table: string;
10
+ }
5
11
  type Props = {
6
12
  dbType: DbType;
7
13
  dbSchema: DBSchema;
@@ -15,7 +21,16 @@ type Props = {
15
21
  refresh?: () => void;
16
22
  initialSchemaKey?: string;
17
23
  initialTableKey?: string;
24
+ selectedSchemaKey?: string | undefined;
25
+ selectedTableKey?: string | undefined;
26
+ dbSelector?: Snippet<[]>;
27
+ /** Enable multi-select mode with checkboxes in sidebar */
28
+ multiSelectMode?: boolean;
29
+ /** Selected tables in multi-select mode */
30
+ selectedTables?: SelectedTable[];
31
+ /** Tables that are already added and should show as disabled */
32
+ disabledTables?: SelectedTable[];
18
33
  };
19
- declare const DBManager: import("svelte").Component<Props, {}, "">;
34
+ declare const DBManager: import("svelte").Component<Props, {}, "selectedSchemaKey" | "selectedTableKey" | "selectedTables">;
20
35
  type DBManager = ReturnType<typeof DBManager>;
21
36
  export default DBManager;