@vendure/dashboard 3.4.3-master-202509180227 → 3.4.3-master-202509200226
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.
- package/dist/vite/vite-plugin-config.js +1 -0
- package/package.json +11 -7
- package/src/app/common/duplicate-bulk-action.tsx +37 -23
- package/src/app/common/duplicate-entity-dialog.tsx +117 -0
- package/src/app/routes/_authenticated/_administrators/administrators.tsx +1 -2
- package/src/app/routes/_authenticated/_assets/assets.graphql.ts +39 -0
- package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +18 -7
- package/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx +206 -0
- package/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx +226 -0
- package/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx +217 -0
- package/src/app/routes/_authenticated/_channels/channels.tsx +1 -2
- package/src/app/routes/_authenticated/_collections/collections.tsx +2 -16
- package/src/app/routes/_authenticated/_countries/countries.tsx +1 -2
- package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +1 -2
- package/src/app/routes/_authenticated/_customers/customers.tsx +1 -2
- package/src/app/routes/_authenticated/_facets/facets.tsx +0 -1
- package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +1 -2
- package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +1 -2
- package/src/app/routes/_authenticated/_products/products.tsx +1 -2
- package/src/app/routes/_authenticated/_promotions/promotions.tsx +1 -2
- package/src/app/routes/_authenticated/_roles/roles.tsx +1 -2
- package/src/app/routes/_authenticated/_sellers/sellers.tsx +1 -2
- package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +1 -2
- package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +1 -2
- package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +1 -2
- package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +1 -2
- package/src/app/routes/_authenticated/_zones/zones.tsx +1 -2
- package/src/lib/components/data-input/rich-text-input.tsx +2 -115
- package/src/lib/components/data-table/data-table-bulk-actions.tsx +5 -14
- package/src/lib/components/data-table/use-all-bulk-actions.ts +19 -0
- package/src/lib/components/data-table/use-generated-columns.tsx +12 -3
- package/src/lib/components/layout/nav-main.tsx +50 -25
- package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +1 -1
- package/src/lib/components/shared/asset/asset-gallery.tsx +83 -50
- package/src/lib/components/shared/paginated-list-data-table.tsx +1 -0
- package/src/lib/components/shared/rich-text-editor/image-dialog.tsx +223 -0
- package/src/lib/components/shared/rich-text-editor/link-dialog.tsx +151 -0
- package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +439 -0
- package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +338 -0
- package/src/lib/components/shared/rich-text-editor/table-delete-menu.tsx +104 -0
- package/src/lib/components/shared/rich-text-editor/table-edit-icons.tsx +225 -0
- package/src/lib/components/shared/vendure-image.tsx +9 -1
- package/src/lib/framework/defaults.ts +24 -0
- package/src/lib/framework/extension-api/types/navigation.ts +8 -0
- package/src/lib/framework/nav-menu/nav-menu-extensions.ts +26 -0
- package/src/lib/framework/page/list-page.tsx +7 -0
- package/src/lib/graphql/common-operations.ts +19 -0
- package/src/lib/graphql/fragments.ts +23 -13
- package/src/lib/hooks/use-custom-field-config.ts +19 -2
- package/src/lib/index.ts +0 -1
- package/src/lib/providers/channel-provider.tsx +22 -6
- package/src/lib/providers/server-config.tsx +1 -0
- package/src/app/routes/_authenticated/_collections/components/move-single-collection.tsx +0 -33
- package/src/lib/components/shared/asset/focal-point-control.tsx +0 -57
|
@@ -37,30 +37,35 @@ export function registerDefaults() {
|
|
|
37
37
|
title: 'Products',
|
|
38
38
|
url: '/products',
|
|
39
39
|
order: 100,
|
|
40
|
+
requiresPermission: ['ReadProduct', 'ReadCatalog'],
|
|
40
41
|
},
|
|
41
42
|
{
|
|
42
43
|
id: 'product-variants',
|
|
43
44
|
title: 'Product Variants',
|
|
44
45
|
url: '/product-variants',
|
|
45
46
|
order: 200,
|
|
47
|
+
requiresPermission: ['ReadProduct', 'ReadCatalog'],
|
|
46
48
|
},
|
|
47
49
|
{
|
|
48
50
|
id: 'facets',
|
|
49
51
|
title: 'Facets',
|
|
50
52
|
url: '/facets',
|
|
51
53
|
order: 300,
|
|
54
|
+
requiresPermission: ['ReadProduct', 'ReadCatalog'],
|
|
52
55
|
},
|
|
53
56
|
{
|
|
54
57
|
id: 'collections',
|
|
55
58
|
title: 'Collections',
|
|
56
59
|
url: '/collections',
|
|
57
60
|
order: 400,
|
|
61
|
+
requiresPermission: ['ReadCollection', 'ReadCatalog'],
|
|
58
62
|
},
|
|
59
63
|
{
|
|
60
64
|
id: 'assets',
|
|
61
65
|
title: 'Assets',
|
|
62
66
|
url: '/assets',
|
|
63
67
|
order: 500,
|
|
68
|
+
requiresPermission: ['ReadAsset', 'ReadCatalog'],
|
|
64
69
|
},
|
|
65
70
|
],
|
|
66
71
|
},
|
|
@@ -76,6 +81,7 @@ export function registerDefaults() {
|
|
|
76
81
|
title: 'Orders',
|
|
77
82
|
url: '/orders',
|
|
78
83
|
order: 100,
|
|
84
|
+
requiresPermission: ['ReadOrder'],
|
|
79
85
|
},
|
|
80
86
|
],
|
|
81
87
|
},
|
|
@@ -91,12 +97,14 @@ export function registerDefaults() {
|
|
|
91
97
|
title: 'Customers',
|
|
92
98
|
url: '/customers',
|
|
93
99
|
order: 100,
|
|
100
|
+
requiresPermission: ['ReadCustomer'],
|
|
94
101
|
},
|
|
95
102
|
{
|
|
96
103
|
id: 'customer-groups',
|
|
97
104
|
title: 'Customer Groups',
|
|
98
105
|
url: '/customer-groups',
|
|
99
106
|
order: 200,
|
|
107
|
+
requiresPermission: ['ReadCustomerGroup'],
|
|
100
108
|
},
|
|
101
109
|
],
|
|
102
110
|
},
|
|
@@ -112,6 +120,7 @@ export function registerDefaults() {
|
|
|
112
120
|
title: 'Promotions',
|
|
113
121
|
url: '/promotions',
|
|
114
122
|
order: 100,
|
|
123
|
+
requiresPermission: ['ReadPromotion'],
|
|
115
124
|
},
|
|
116
125
|
],
|
|
117
126
|
},
|
|
@@ -127,18 +136,21 @@ export function registerDefaults() {
|
|
|
127
136
|
title: 'Job Queue',
|
|
128
137
|
url: '/job-queue',
|
|
129
138
|
order: 100,
|
|
139
|
+
requiresPermission: ['ReadSystem'],
|
|
130
140
|
},
|
|
131
141
|
{
|
|
132
142
|
id: 'healthchecks',
|
|
133
143
|
title: 'Healthchecks',
|
|
134
144
|
url: '/healthchecks',
|
|
135
145
|
order: 200,
|
|
146
|
+
requiresPermission: ['ReadSystem'],
|
|
136
147
|
},
|
|
137
148
|
{
|
|
138
149
|
id: 'scheduled-tasks',
|
|
139
150
|
title: 'Scheduled Tasks',
|
|
140
151
|
url: '/scheduled-tasks',
|
|
141
152
|
order: 300,
|
|
153
|
+
requiresPermission: ['ReadSystem'],
|
|
142
154
|
},
|
|
143
155
|
],
|
|
144
156
|
},
|
|
@@ -154,72 +166,84 @@ export function registerDefaults() {
|
|
|
154
166
|
title: 'Sellers',
|
|
155
167
|
url: '/sellers',
|
|
156
168
|
order: 100,
|
|
169
|
+
requiresPermission: ['ReadSeller'],
|
|
157
170
|
},
|
|
158
171
|
{
|
|
159
172
|
id: 'channels',
|
|
160
173
|
title: 'Channels',
|
|
161
174
|
url: '/channels',
|
|
162
175
|
order: 200,
|
|
176
|
+
requiresPermission: ['ReadChannel'],
|
|
163
177
|
},
|
|
164
178
|
{
|
|
165
179
|
id: 'stock-locations',
|
|
166
180
|
title: 'Stock Locations',
|
|
167
181
|
url: '/stock-locations',
|
|
168
182
|
order: 300,
|
|
183
|
+
requiresPermission: ['ReadStockLocation'],
|
|
169
184
|
},
|
|
170
185
|
{
|
|
171
186
|
id: 'administrators',
|
|
172
187
|
title: 'Administrators',
|
|
173
188
|
url: '/administrators',
|
|
174
189
|
order: 400,
|
|
190
|
+
requiresPermission: ['ReadAdministrator'],
|
|
175
191
|
},
|
|
176
192
|
{
|
|
177
193
|
id: 'roles',
|
|
178
194
|
title: 'Roles',
|
|
179
195
|
url: '/roles',
|
|
180
196
|
order: 500,
|
|
197
|
+
requiresPermission: ['ReadAdministrator'],
|
|
181
198
|
},
|
|
182
199
|
{
|
|
183
200
|
id: 'shipping-methods',
|
|
184
201
|
title: 'Shipping Methods',
|
|
185
202
|
url: '/shipping-methods',
|
|
186
203
|
order: 600,
|
|
204
|
+
requiresPermission: ['ReadShippingMethod'],
|
|
187
205
|
},
|
|
188
206
|
{
|
|
189
207
|
id: 'payment-methods',
|
|
190
208
|
title: 'Payment Methods',
|
|
191
209
|
url: '/payment-methods',
|
|
192
210
|
order: 700,
|
|
211
|
+
requiresPermission: ['ReadPaymentMethod'],
|
|
193
212
|
},
|
|
194
213
|
{
|
|
195
214
|
id: 'tax-categories',
|
|
196
215
|
title: 'Tax Categories',
|
|
197
216
|
url: '/tax-categories',
|
|
198
217
|
order: 800,
|
|
218
|
+
requiresPermission: ['ReadTaxCategory'],
|
|
199
219
|
},
|
|
200
220
|
{
|
|
201
221
|
id: 'tax-rates',
|
|
202
222
|
title: 'Tax Rates',
|
|
203
223
|
url: '/tax-rates',
|
|
204
224
|
order: 900,
|
|
225
|
+
requiresPermission: ['ReadTaxRate'],
|
|
205
226
|
},
|
|
206
227
|
{
|
|
207
228
|
id: 'countries',
|
|
208
229
|
title: 'Countries',
|
|
209
230
|
url: '/countries',
|
|
210
231
|
order: 1000,
|
|
232
|
+
requiresPermission: ['ReadCountry'],
|
|
211
233
|
},
|
|
212
234
|
{
|
|
213
235
|
id: 'zones',
|
|
214
236
|
title: 'Zones',
|
|
215
237
|
url: '/zones',
|
|
216
238
|
order: 1100,
|
|
239
|
+
requiresPermission: ['ReadZone'],
|
|
217
240
|
},
|
|
218
241
|
{
|
|
219
242
|
id: 'global-settings',
|
|
220
243
|
title: 'Global Settings',
|
|
221
244
|
url: '/global-settings',
|
|
222
245
|
order: 1200,
|
|
246
|
+
requiresPermission: ['UpdateGlobalSettings'],
|
|
223
247
|
},
|
|
224
248
|
],
|
|
225
249
|
},
|
|
@@ -27,6 +27,10 @@ export interface DashboardRouteDefinition {
|
|
|
27
27
|
* @description
|
|
28
28
|
* Optional navigation menu item configuration to add this route to the nav menu
|
|
29
29
|
* on the left side of the dashboard.
|
|
30
|
+
*
|
|
31
|
+
* The `sectionId` specifies which nav menu section (e.g. "catalog", "customers")
|
|
32
|
+
* this item should appear in. It can also point to custom nav menu sections that
|
|
33
|
+
* have been defined using the `navSections` extension property.
|
|
30
34
|
*/
|
|
31
35
|
navMenuItem?: Partial<NavMenuItem> & { sectionId: string };
|
|
32
36
|
/**
|
|
@@ -42,8 +46,12 @@ export interface DashboardRouteDefinition {
|
|
|
42
46
|
* @description
|
|
43
47
|
* Defines a custom navigation section in the dashboard sidebar.
|
|
44
48
|
*
|
|
49
|
+
* Individual items can then be added to the section by defining routes in the
|
|
50
|
+
* `routes` property of your Dashboard extension.
|
|
51
|
+
*
|
|
45
52
|
* @docsCategory extensions-api
|
|
46
53
|
* @docsPage Navigation
|
|
54
|
+
* @docsWeight 0
|
|
47
55
|
* @since 3.4.0
|
|
48
56
|
*/
|
|
49
57
|
export interface DashboardNavSectionDefinition {
|
|
@@ -5,15 +5,41 @@ import { globalRegistry } from '../registry/global-registry.js';
|
|
|
5
5
|
// Define the placement options for navigation sections
|
|
6
6
|
export type NavMenuSectionPlacement = 'top' | 'bottom';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @description
|
|
10
|
+
* The base configuration for navigation items and sections of the main app nav bar.
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory extensions-api
|
|
13
|
+
* @docsPage Navigation
|
|
14
|
+
* @since 3.4.0
|
|
15
|
+
*/
|
|
8
16
|
interface NavMenuBaseItem {
|
|
9
17
|
id: string;
|
|
10
18
|
title: string;
|
|
11
19
|
icon?: LucideIcon;
|
|
12
20
|
order?: number;
|
|
13
21
|
placement?: NavMenuSectionPlacement;
|
|
22
|
+
/**
|
|
23
|
+
* @description
|
|
24
|
+
* This can be used to restrict the menu item to the given
|
|
25
|
+
* permission or permissions.
|
|
26
|
+
*/
|
|
27
|
+
requiresPermission?: string | string[];
|
|
14
28
|
}
|
|
15
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @description
|
|
32
|
+
* Defines an items in the navigation menu.
|
|
33
|
+
*
|
|
34
|
+
* @docsCategory extensions-api
|
|
35
|
+
* @docsPage Navigation
|
|
36
|
+
* @since 3.4.0
|
|
37
|
+
*/
|
|
16
38
|
export interface NavMenuItem extends NavMenuBaseItem {
|
|
39
|
+
/**
|
|
40
|
+
* @description
|
|
41
|
+
* The url of the route which this nav item links to.
|
|
42
|
+
*/
|
|
17
43
|
url: string;
|
|
18
44
|
}
|
|
19
45
|
|
|
@@ -38,6 +38,13 @@ export interface ListPageProps<
|
|
|
38
38
|
route: AnyRoute | (() => AnyRoute);
|
|
39
39
|
title: string | React.ReactElement;
|
|
40
40
|
listQuery: T;
|
|
41
|
+
/**
|
|
42
|
+
* @description
|
|
43
|
+
* Providing the `deleteMutation` will automatically add a "delete" menu item to the
|
|
44
|
+
* actions column dropdown. Note that if this table already has a "delete" bulk action,
|
|
45
|
+
* you don't need to additionally provide a delete mutation, because the bulk action
|
|
46
|
+
* will be added to the action column dropdown already.
|
|
47
|
+
*/
|
|
41
48
|
deleteMutation?: TypedDocumentNode<any, { id: string }>;
|
|
42
49
|
transformVariables?: (variables: V) => V;
|
|
43
50
|
onSearchTermChange?: (searchTerm: string) => NonNullable<V['options']>['filter'];
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { configArgDefinitionFragment } from '@/vdb/graphql/fragments.js';
|
|
2
|
+
|
|
1
3
|
import { graphql } from './graphql.js';
|
|
2
4
|
|
|
3
5
|
export const duplicateEntityDocument = graphql(`
|
|
@@ -16,3 +18,20 @@ export const duplicateEntityDocument = graphql(`
|
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
`);
|
|
21
|
+
|
|
22
|
+
export const getEntityDuplicatorsDocument = graphql(
|
|
23
|
+
`
|
|
24
|
+
query GetEntityDuplicators {
|
|
25
|
+
entityDuplicators {
|
|
26
|
+
code
|
|
27
|
+
description
|
|
28
|
+
requiresPermission
|
|
29
|
+
forEntities
|
|
30
|
+
args {
|
|
31
|
+
...ConfigArgDefinition
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`,
|
|
36
|
+
[configArgDefinitionFragment],
|
|
37
|
+
);
|
|
@@ -34,23 +34,32 @@ export const configurableOperationFragment = graphql(`
|
|
|
34
34
|
|
|
35
35
|
export type ConfigurableOperationFragment = ResultOf<typeof configurableOperationFragment>;
|
|
36
36
|
|
|
37
|
-
export const
|
|
38
|
-
fragment
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
label
|
|
47
|
-
description
|
|
48
|
-
}
|
|
49
|
-
code
|
|
37
|
+
export const configArgDefinitionFragment = graphql(`
|
|
38
|
+
fragment ConfigArgDefinition on ConfigArgDefinition {
|
|
39
|
+
name
|
|
40
|
+
type
|
|
41
|
+
required
|
|
42
|
+
defaultValue
|
|
43
|
+
list
|
|
44
|
+
ui
|
|
45
|
+
label
|
|
50
46
|
description
|
|
51
47
|
}
|
|
52
48
|
`);
|
|
53
49
|
|
|
50
|
+
export const configurableOperationDefFragment = graphql(
|
|
51
|
+
`
|
|
52
|
+
fragment ConfigurableOperationDef on ConfigurableOperationDefinition {
|
|
53
|
+
args {
|
|
54
|
+
...ConfigArgDefinition
|
|
55
|
+
}
|
|
56
|
+
code
|
|
57
|
+
description
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
[configArgDefinitionFragment],
|
|
61
|
+
);
|
|
62
|
+
|
|
54
63
|
export const errorResultFragment = graphql(`
|
|
55
64
|
fragment ErrorResult on ErrorResult {
|
|
56
65
|
errorCode
|
|
@@ -59,3 +68,4 @@ export const errorResultFragment = graphql(`
|
|
|
59
68
|
`);
|
|
60
69
|
|
|
61
70
|
export type ConfigurableOperationDefFragment = ResultOf<typeof configurableOperationDefFragment>;
|
|
71
|
+
export type ConfigArgDefFragment = ResultOf<typeof configArgDefinitionFragment>;
|
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
import { usePermissions } from '@/vdb/hooks/use-permissions.js';
|
|
2
|
+
import { CustomFieldConfig } from '@/vdb/providers/server-config.js';
|
|
3
|
+
|
|
1
4
|
import { useServerConfig } from './use-server-config.js';
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* Returns the custom field config for the given entity type (e.g. 'Product').
|
|
9
|
+
* Also filters out any custom fields that the current active user does not
|
|
10
|
+
* have permissions to access.
|
|
11
|
+
*
|
|
12
|
+
* @docsCategory hooks
|
|
13
|
+
* @since 3.4.0
|
|
14
|
+
*/
|
|
15
|
+
export function useCustomFieldConfig(entityType: string): CustomFieldConfig[] {
|
|
4
16
|
const serverConfig = useServerConfig();
|
|
17
|
+
const { hasPermissions } = usePermissions();
|
|
5
18
|
if (!serverConfig) {
|
|
6
19
|
return [];
|
|
7
20
|
}
|
|
8
21
|
const customFieldConfig = serverConfig.entityCustomFields.find(field => field.entityName === entityType);
|
|
9
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
customFieldConfig?.customFields?.filter(config => {
|
|
24
|
+
return config.requiresPermission ? hasPermissions(config.requiresPermission) : true;
|
|
25
|
+
}) ?? []
|
|
26
|
+
);
|
|
10
27
|
}
|
package/src/lib/index.ts
CHANGED
|
@@ -70,7 +70,6 @@ export * from './components/shared/asset/asset-preview-dialog.js';
|
|
|
70
70
|
export * from './components/shared/asset/asset-preview-selector.js';
|
|
71
71
|
export * from './components/shared/asset/asset-preview.js';
|
|
72
72
|
export * from './components/shared/asset/asset-properties.js';
|
|
73
|
-
export * from './components/shared/asset/focal-point-control.js';
|
|
74
73
|
export * from './components/shared/assign-to-channel-bulk-action.js';
|
|
75
74
|
export * from './components/shared/assign-to-channel-dialog.js';
|
|
76
75
|
export * from './components/shared/assigned-facet-values.js';
|
|
@@ -19,7 +19,7 @@ const channelFragment = graphql(`
|
|
|
19
19
|
`);
|
|
20
20
|
|
|
21
21
|
// Query to get all available channels and the active channel
|
|
22
|
-
const
|
|
22
|
+
const activeChannelDocument = graphql(
|
|
23
23
|
`
|
|
24
24
|
query ChannelInformation {
|
|
25
25
|
activeChannel {
|
|
@@ -28,6 +28,14 @@ const ChannelsQuery = graphql(
|
|
|
28
28
|
id
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
[channelFragment],
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const channelsDocument = graphql(
|
|
37
|
+
`
|
|
38
|
+
query ChannelInformation {
|
|
31
39
|
channels {
|
|
32
40
|
items {
|
|
33
41
|
...ChannelInfo
|
|
@@ -40,7 +48,7 @@ const ChannelsQuery = graphql(
|
|
|
40
48
|
);
|
|
41
49
|
|
|
42
50
|
// Define the type for a channel
|
|
43
|
-
type ActiveChannel = ResultOf<typeof
|
|
51
|
+
type ActiveChannel = ResultOf<typeof activeChannelDocument>['activeChannel'];
|
|
44
52
|
type Channel = ResultOf<typeof channelFragment>;
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -106,10 +114,18 @@ export function ChannelProvider({ children }: Readonly<{ children: React.ReactNo
|
|
|
106
114
|
return activeChannelId;
|
|
107
115
|
});
|
|
108
116
|
|
|
117
|
+
// Fetch active channel
|
|
118
|
+
const { data: activeChannelData, isLoading: isActiveChannelLoading } = useQuery({
|
|
119
|
+
queryKey: ['activeChannel', isAuthenticated],
|
|
120
|
+
queryFn: () => api.query(activeChannelDocument),
|
|
121
|
+
retry: false,
|
|
122
|
+
enabled: isAuthenticated,
|
|
123
|
+
});
|
|
124
|
+
|
|
109
125
|
// Fetch all available channels
|
|
110
|
-
const { data: channelsData
|
|
126
|
+
const { data: channelsData } = useQuery({
|
|
111
127
|
queryKey: ['channels', isAuthenticated],
|
|
112
|
-
queryFn: () => api.query(
|
|
128
|
+
queryFn: () => api.query(channelsDocument),
|
|
113
129
|
retry: false,
|
|
114
130
|
enabled: isAuthenticated,
|
|
115
131
|
});
|
|
@@ -168,10 +184,10 @@ export function ChannelProvider({ children }: Readonly<{ children: React.ReactNo
|
|
|
168
184
|
}
|
|
169
185
|
}, [selectedChannelId, channels]);
|
|
170
186
|
|
|
171
|
-
const isLoading =
|
|
187
|
+
const isLoading = isActiveChannelLoading;
|
|
172
188
|
|
|
173
189
|
// Find the selected channel from the list of channels
|
|
174
|
-
const selectedChannel =
|
|
190
|
+
const selectedChannel = activeChannelData?.activeChannel;
|
|
175
191
|
|
|
176
192
|
const refreshChannels = () => {
|
|
177
193
|
refreshCurrentUser();
|
|
@@ -250,6 +250,7 @@ export const getServerConfigDocument = graphql(
|
|
|
250
250
|
);
|
|
251
251
|
|
|
252
252
|
type QueryResult = ResultOf<typeof getServerConfigDocument>['globalSettings']['serverConfig'];
|
|
253
|
+
export type CustomFieldConfig = QueryResult['entityCustomFields'][number]['customFields'][number];
|
|
253
254
|
|
|
254
255
|
export interface ServerConfig {
|
|
255
256
|
availableLanguages: string[];
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ResultOf } from 'gql.tada';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
import { collectionListDocument } from '../collections.graphql.js';
|
|
5
|
-
import { MoveCollectionsDialog } from './move-collections-dialog.js';
|
|
6
|
-
|
|
7
|
-
type Collection = ResultOf<typeof collectionListDocument>['collections']['items'][number];
|
|
8
|
-
|
|
9
|
-
export function useMoveSingleCollection() {
|
|
10
|
-
const [moveDialogOpen, setMoveDialogOpen] = useState(false);
|
|
11
|
-
const [collectionsToMove, setCollectionsToMove] = useState<Collection[]>([]);
|
|
12
|
-
|
|
13
|
-
const handleMoveClick = (collection: Collection) => {
|
|
14
|
-
setCollectionsToMove([collection]);
|
|
15
|
-
setMoveDialogOpen(true);
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const MoveDialog = () => (
|
|
19
|
-
<MoveCollectionsDialog
|
|
20
|
-
open={moveDialogOpen}
|
|
21
|
-
onOpenChange={setMoveDialogOpen}
|
|
22
|
-
collectionsToMove={collectionsToMove}
|
|
23
|
-
onSuccess={() => {
|
|
24
|
-
// The dialog will handle invalidating queries internally
|
|
25
|
-
}}
|
|
26
|
-
/>
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
handleMoveClick,
|
|
31
|
-
MoveDialog,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { cn } from '@/vdb/lib/utils.js';
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
|
|
4
|
-
export interface Point {
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
interface FocalPointControlProps {
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
point: Point;
|
|
13
|
-
onChange: (point: Point) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function FocalPointControl({ width, height, point, onChange }: Readonly<FocalPointControlProps>) {
|
|
17
|
-
const [dragging, setDragging] = useState(false);
|
|
18
|
-
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
if (!dragging) return;
|
|
21
|
-
|
|
22
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
23
|
-
const rect = (e.target as HTMLDivElement)?.getBoundingClientRect();
|
|
24
|
-
const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
|
|
25
|
-
const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height));
|
|
26
|
-
onChange({ x, y });
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const handleMouseUp = () => {
|
|
30
|
-
setDragging(false);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
34
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
35
|
-
|
|
36
|
-
return () => {
|
|
37
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
38
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
39
|
-
};
|
|
40
|
-
}, [dragging, onChange]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<div className="absolute inset-0 cursor-crosshair" onMouseDown={() => setDragging(true)}>
|
|
44
|
-
<div
|
|
45
|
-
className={cn(
|
|
46
|
-
'absolute w-6 h-6 border-2 border-white rounded-full -translate-x-1/2 -translate-y-1/2',
|
|
47
|
-
'shadow-[0_0_0_1px_rgba(0,0,0,0.3)]',
|
|
48
|
-
dragging && 'scale-75',
|
|
49
|
-
)}
|
|
50
|
-
style={{
|
|
51
|
-
left: `${point.x * width}px`,
|
|
52
|
-
top: `${point.y * height}px`,
|
|
53
|
-
}}
|
|
54
|
-
/>
|
|
55
|
-
</div>
|
|
56
|
-
);
|
|
57
|
-
}
|