@agent-native/core 0.116.0 → 0.117.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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/package.json +4 -1
- package/corpus/core/src/client/analytics.ts +7 -0
- package/corpus/core/src/client/use-db-sync.ts +354 -15
- package/corpus/core/src/collab/client.ts +26 -2
- package/corpus/core/src/db/ddl-guard.ts +49 -10
- package/corpus/core/src/deploy/build.ts +25 -2
- package/corpus/core/src/deploy/workspace-deploy.ts +14 -4
- package/corpus/core/src/realtime-protocol.ts +92 -0
- package/corpus/core/src/server/core-routes-plugin.ts +7 -0
- package/corpus/core/src/server/poll-events.ts +29 -8
- package/corpus/core/src/server/poll.ts +1123 -865
- package/corpus/core/src/server/realtime-token.ts +106 -0
- package/corpus/core/src/server/sentry-config.ts +40 -0
- package/corpus/core/src/server/short-lived-token.ts +156 -0
- package/corpus/core/src/server/sse.ts +15 -0
- package/corpus/core/src/server/ssr-handler.ts +9 -3
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +12 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +566 -299
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +14 -0
- package/corpus/templates/content/app/i18n-data.ts +90 -0
- package/dist/client/analytics.d.ts +10 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +1 -1
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +315 -15
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +26 -2
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/ddl-guard.d.ts +7 -2
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +21 -9
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +25 -2
- package/dist/deploy/build.js.map +1 -1
- package/dist/deploy/workspace-deploy.js +14 -4
- package/dist/deploy/workspace-deploy.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/realtime-protocol.d.ts +40 -0
- package/dist/realtime-protocol.d.ts.map +1 -0
- package/dist/realtime-protocol.js +69 -0
- package/dist/realtime-protocol.js.map +1 -0
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/poll-events.d.ts +7 -1
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +24 -7
- package/dist/server/poll-events.js.map +1 -1
- package/dist/server/poll.d.ts +223 -42
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +849 -737
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +34 -0
- package/dist/server/realtime-token.d.ts.map +1 -0
- package/dist/server/realtime-token.js +85 -0
- package/dist/server/realtime-token.js.map +1 -0
- package/dist/server/sentry-config.d.ts +12 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +34 -0
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/short-lived-token.d.ts +46 -0
- package/dist/server/short-lived-token.d.ts.map +1 -1
- package/dist/server/short-lived-token.js +104 -0
- package/dist/server/short-lived-token.js.map +1 -1
- package/dist/server/sse.d.ts +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +4 -0
- package/dist/server/sse.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +5 -3
- package/dist/server/ssr-handler.js.map +1 -1
- package/package.json +4 -1
- package/src/client/analytics.ts +7 -0
- package/src/client/use-db-sync.ts +354 -15
- package/src/collab/client.ts +26 -2
- package/src/db/ddl-guard.ts +49 -10
- package/src/deploy/build.ts +25 -2
- package/src/deploy/workspace-deploy.ts +14 -4
- package/src/realtime-protocol.ts +92 -0
- package/src/server/core-routes-plugin.ts +7 -0
- package/src/server/poll-events.ts +29 -8
- package/src/server/poll.ts +1123 -865
- package/src/server/realtime-token.ts +106 -0
- package/src/server/sentry-config.ts +40 -0
- package/src/server/short-lived-token.ts +156 -0
- package/src/server/sse.ts +15 -0
- package/src/server/ssr-handler.ts +9 -3
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
import { emailToName } from "@agent-native/core/client/collab";
|
|
2
2
|
import { useActionMutation, useSession } from "@agent-native/core/client/hooks";
|
|
3
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
+
import {
|
|
5
|
+
closestCenter,
|
|
6
|
+
DndContext,
|
|
7
|
+
KeyboardSensor,
|
|
8
|
+
PointerSensor,
|
|
9
|
+
useSensor,
|
|
10
|
+
useSensors,
|
|
11
|
+
type DragEndEvent,
|
|
12
|
+
} from "@dnd-kit/core";
|
|
13
|
+
import {
|
|
14
|
+
arrayMove,
|
|
15
|
+
SortableContext,
|
|
16
|
+
sortableKeyboardCoordinates,
|
|
17
|
+
useSortable,
|
|
18
|
+
verticalListSortingStrategy,
|
|
19
|
+
} from "@dnd-kit/sortable";
|
|
20
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
4
21
|
import type {
|
|
5
22
|
AddContentDatabaseSourceFieldPropertyRequest,
|
|
6
23
|
BindContentDatabaseSourceFieldRequest,
|
|
@@ -30,6 +47,9 @@ import {
|
|
|
30
47
|
} from "@shared/properties";
|
|
31
48
|
import {
|
|
32
49
|
IconAlignLeft,
|
|
50
|
+
IconArrowLeft,
|
|
51
|
+
IconArrowDown,
|
|
52
|
+
IconArrowUp,
|
|
33
53
|
IconAt,
|
|
34
54
|
IconCalendar,
|
|
35
55
|
IconCheck,
|
|
@@ -42,6 +62,8 @@ import {
|
|
|
42
62
|
IconEye,
|
|
43
63
|
IconEyeOff,
|
|
44
64
|
IconFileText,
|
|
65
|
+
IconFilter,
|
|
66
|
+
IconGripVertical,
|
|
45
67
|
IconHash,
|
|
46
68
|
IconLink,
|
|
47
69
|
IconList,
|
|
@@ -49,7 +71,6 @@ import {
|
|
|
49
71
|
IconNumber,
|
|
50
72
|
IconNumber123,
|
|
51
73
|
IconPaperclip,
|
|
52
|
-
IconPalette,
|
|
53
74
|
IconPhone,
|
|
54
75
|
IconPlus,
|
|
55
76
|
IconSearch,
|
|
@@ -89,6 +110,7 @@ import {
|
|
|
89
110
|
DropdownMenu,
|
|
90
111
|
DropdownMenuContent,
|
|
91
112
|
DropdownMenuItem,
|
|
113
|
+
DropdownMenuLabel,
|
|
92
114
|
DropdownMenuSeparator,
|
|
93
115
|
DropdownMenuSub,
|
|
94
116
|
DropdownMenuSubContent,
|
|
@@ -118,6 +140,14 @@ import {
|
|
|
118
140
|
} from "@/hooks/use-document-properties";
|
|
119
141
|
import { cn } from "@/lib/utils";
|
|
120
142
|
|
|
143
|
+
import {
|
|
144
|
+
clearDatabaseFiltersForColumn,
|
|
145
|
+
clearDatabaseSort,
|
|
146
|
+
databaseQuickFilterOptionsForColumn,
|
|
147
|
+
upsertDatabaseQuickFilter,
|
|
148
|
+
upsertDatabaseSort,
|
|
149
|
+
} from "./database/filter-sort";
|
|
150
|
+
import type { DatabaseFilter, DatabaseSort } from "./database/types";
|
|
121
151
|
import { imageUploadErrorMessage, uploadImageFile } from "./image-upload";
|
|
122
152
|
|
|
123
153
|
type TFunction = ReturnType<typeof useT>;
|
|
@@ -989,6 +1019,12 @@ export function PropertyManagementPopover({
|
|
|
989
1019
|
sourceField,
|
|
990
1020
|
sourceAttached = false,
|
|
991
1021
|
sources,
|
|
1022
|
+
sorts,
|
|
1023
|
+
filters,
|
|
1024
|
+
onSortsChange,
|
|
1025
|
+
onFiltersChange,
|
|
1026
|
+
onHide,
|
|
1027
|
+
hideDisabled,
|
|
992
1028
|
}: {
|
|
993
1029
|
property: DocumentProperty;
|
|
994
1030
|
documentId: string;
|
|
@@ -999,8 +1035,29 @@ export function PropertyManagementPopover({
|
|
|
999
1035
|
sourceField?: ContentDatabaseSource["fields"][number] | null;
|
|
1000
1036
|
sourceAttached?: boolean;
|
|
1001
1037
|
sources?: ContentDatabaseSource[];
|
|
1038
|
+
sorts?: DatabaseSort[];
|
|
1039
|
+
filters?: DatabaseFilter[];
|
|
1040
|
+
onSortsChange?: (sorts: DatabaseSort[]) => void;
|
|
1041
|
+
onFiltersChange?: (filters: DatabaseFilter[]) => void;
|
|
1042
|
+
onHide?: () => void | Promise<void>;
|
|
1043
|
+
hideDisabled?: boolean;
|
|
1002
1044
|
}) {
|
|
1003
1045
|
const t = useT();
|
|
1046
|
+
const hasColumnMenu = !!(
|
|
1047
|
+
sorts &&
|
|
1048
|
+
filters &&
|
|
1049
|
+
onSortsChange &&
|
|
1050
|
+
onFiltersChange
|
|
1051
|
+
);
|
|
1052
|
+
const columnKey = property.definition.id;
|
|
1053
|
+
const columnSort =
|
|
1054
|
+
(sorts ?? []).find((sort) => sort.key === columnKey) ?? null;
|
|
1055
|
+
const columnFilterCount = (filters ?? []).filter(
|
|
1056
|
+
(filter) => filter.key === columnKey,
|
|
1057
|
+
).length;
|
|
1058
|
+
const quickFilters = databaseQuickFilterOptionsForColumn(
|
|
1059
|
+
property.definition.type,
|
|
1060
|
+
);
|
|
1004
1061
|
const configure = useConfigureDocumentProperty(documentId);
|
|
1005
1062
|
const duplicate = useDuplicateDocumentProperty(documentId);
|
|
1006
1063
|
const remove = useDeleteDocumentProperty(documentId);
|
|
@@ -1067,12 +1124,18 @@ export function PropertyManagementPopover({
|
|
|
1067
1124
|
blocksFieldCount,
|
|
1068
1125
|
});
|
|
1069
1126
|
const [open, setOpen] = useState(false);
|
|
1127
|
+
const [view, setView] = useState<"quick" | "edit">(
|
|
1128
|
+
hasColumnMenu ? "quick" : "edit",
|
|
1129
|
+
);
|
|
1070
1130
|
const [confirmDeleteOpen, setConfirmDeleteOpen] = useState(false);
|
|
1071
1131
|
const [name, setName] = useState(property.definition.name);
|
|
1072
1132
|
const [description, setDescription] = useState(
|
|
1073
1133
|
property.definition.description,
|
|
1074
1134
|
);
|
|
1075
1135
|
const [newOption, setNewOption] = useState("");
|
|
1136
|
+
const [optionsDraft, setOptionsDraft] = useState<DocumentPropertyOption[]>(
|
|
1137
|
+
property.definition.options.options ?? [],
|
|
1138
|
+
);
|
|
1076
1139
|
const persistMetadataSnapshotRef = useRef<
|
|
1077
1140
|
(metadata: PropertyMetadataSnapshot) => Promise<unknown>
|
|
1078
1141
|
>(async () => undefined);
|
|
@@ -1089,6 +1152,7 @@ export function PropertyManagementPopover({
|
|
|
1089
1152
|
),
|
|
1090
1153
|
);
|
|
1091
1154
|
const propertyNameInputRef = useRef<HTMLInputElement>(null);
|
|
1155
|
+
const menuContentRef = useRef<HTMLDivElement>(null);
|
|
1092
1156
|
const typeIsLocked = isComputedPropertyType(property.definition.type);
|
|
1093
1157
|
const typeNeedsOptions =
|
|
1094
1158
|
property.definition.type === "select" ||
|
|
@@ -1099,6 +1163,7 @@ export function PropertyManagementPopover({
|
|
|
1099
1163
|
setName(property.definition.name);
|
|
1100
1164
|
setDescription(property.definition.description);
|
|
1101
1165
|
setNewOption("");
|
|
1166
|
+
setOptionsDraft(property.definition.options.options ?? []);
|
|
1102
1167
|
metadataUpdateQueueRef.current.replace({
|
|
1103
1168
|
name: property.definition.name,
|
|
1104
1169
|
type: property.definition.type,
|
|
@@ -1112,12 +1177,18 @@ export function PropertyManagementPopover({
|
|
|
1112
1177
|
if (!open) return;
|
|
1113
1178
|
|
|
1114
1179
|
const frame = requestAnimationFrame(() => {
|
|
1115
|
-
|
|
1116
|
-
|
|
1180
|
+
if (view === "edit") {
|
|
1181
|
+
propertyNameInputRef.current?.focus();
|
|
1182
|
+
propertyNameInputRef.current?.select();
|
|
1183
|
+
} else {
|
|
1184
|
+
menuContentRef.current
|
|
1185
|
+
?.querySelector<HTMLElement>('[role="menuitem"]')
|
|
1186
|
+
?.focus();
|
|
1187
|
+
}
|
|
1117
1188
|
});
|
|
1118
1189
|
|
|
1119
1190
|
return () => cancelAnimationFrame(frame);
|
|
1120
|
-
}, [open]);
|
|
1191
|
+
}, [open, view]);
|
|
1121
1192
|
|
|
1122
1193
|
async function configureProperty(next: {
|
|
1123
1194
|
name?: string;
|
|
@@ -1138,6 +1209,7 @@ export function PropertyManagementPopover({
|
|
|
1138
1209
|
async function updateOptions(
|
|
1139
1210
|
update: (options: DocumentPropertyOption[]) => DocumentPropertyOption[],
|
|
1140
1211
|
) {
|
|
1212
|
+
setOptionsDraft((current) => update(current));
|
|
1141
1213
|
await metadataUpdateQueueRef.current.enqueue((current) => ({
|
|
1142
1214
|
...current,
|
|
1143
1215
|
options: {
|
|
@@ -1153,6 +1225,24 @@ export function PropertyManagementPopover({
|
|
|
1153
1225
|
...metadata,
|
|
1154
1226
|
});
|
|
1155
1227
|
|
|
1228
|
+
const optionDragSensors = useSensors(
|
|
1229
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
1230
|
+
useSensor(KeyboardSensor, {
|
|
1231
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
1232
|
+
}),
|
|
1233
|
+
);
|
|
1234
|
+
|
|
1235
|
+
function handleOptionDragEnd(event: DragEndEvent) {
|
|
1236
|
+
const { active, over } = event;
|
|
1237
|
+
if (!over || active.id === over.id) return;
|
|
1238
|
+
void updateOptions((options) => {
|
|
1239
|
+
const fromIndex = options.findIndex((option) => option.id === active.id);
|
|
1240
|
+
const toIndex = options.findIndex((option) => option.id === over.id);
|
|
1241
|
+
if (fromIndex < 0 || toIndex < 0) return options;
|
|
1242
|
+
return arrayMove(options, fromIndex, toIndex);
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1156
1246
|
async function renameProperty() {
|
|
1157
1247
|
const nextName = name.trim();
|
|
1158
1248
|
if (!nextName || nextName === property.definition.name) return;
|
|
@@ -1239,7 +1329,10 @@ export function PropertyManagementPopover({
|
|
|
1239
1329
|
<DropdownMenu
|
|
1240
1330
|
open={open}
|
|
1241
1331
|
onOpenChange={(nextOpen) => {
|
|
1242
|
-
if (nextOpen)
|
|
1332
|
+
if (nextOpen) {
|
|
1333
|
+
resetDraft();
|
|
1334
|
+
setView(hasColumnMenu ? "quick" : "edit");
|
|
1335
|
+
}
|
|
1243
1336
|
setOpen(nextOpen);
|
|
1244
1337
|
}}
|
|
1245
1338
|
>
|
|
@@ -1260,6 +1353,7 @@ export function PropertyManagementPopover({
|
|
|
1260
1353
|
? (event) => {
|
|
1261
1354
|
event.preventDefault();
|
|
1262
1355
|
resetDraft();
|
|
1356
|
+
setView(hasColumnMenu ? "quick" : "edit");
|
|
1263
1357
|
setOpen(true);
|
|
1264
1358
|
}
|
|
1265
1359
|
: undefined
|
|
@@ -1271,292 +1365,443 @@ export function PropertyManagementPopover({
|
|
|
1271
1365
|
</button>
|
|
1272
1366
|
</DropdownMenuTrigger>
|
|
1273
1367
|
<DropdownMenuContent
|
|
1368
|
+
ref={menuContentRef}
|
|
1274
1369
|
align="start"
|
|
1275
1370
|
collisionPadding={12}
|
|
1276
|
-
className="relative z-[300] w-72"
|
|
1371
|
+
className="relative z-[300] w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto"
|
|
1277
1372
|
>
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
value={name}
|
|
1286
|
-
aria-label={t("editor.properties.propertyName")}
|
|
1287
|
-
onChange={(event) => setName(event.target.value)}
|
|
1288
|
-
onBlur={() => void renameProperty()}
|
|
1289
|
-
onKeyDown={(event) => {
|
|
1290
|
-
if (event.key === "Enter") {
|
|
1373
|
+
{view === "quick" && hasColumnMenu ? (
|
|
1374
|
+
<>
|
|
1375
|
+
<DropdownMenuLabel className="truncate text-xs text-muted-foreground">
|
|
1376
|
+
{property.definition.name}
|
|
1377
|
+
</DropdownMenuLabel>
|
|
1378
|
+
<DropdownMenuItem
|
|
1379
|
+
onSelect={(event) => {
|
|
1291
1380
|
event.preventDefault();
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1381
|
+
onSortsChange?.(
|
|
1382
|
+
upsertDatabaseSort(
|
|
1383
|
+
sorts ?? [],
|
|
1384
|
+
columnKey,
|
|
1385
|
+
property.definition.name,
|
|
1386
|
+
"asc",
|
|
1387
|
+
),
|
|
1388
|
+
);
|
|
1389
|
+
}}
|
|
1390
|
+
>
|
|
1391
|
+
<IconArrowUp className="mr-2 size-4 text-muted-foreground" />
|
|
1392
|
+
<span className="min-w-0 flex-1">
|
|
1393
|
+
{t("database.sortAscending")}
|
|
1394
|
+
</span>
|
|
1395
|
+
{columnSort?.direction === "asc" ? (
|
|
1396
|
+
<IconCheck className="size-4 text-muted-foreground" />
|
|
1397
|
+
) : null}
|
|
1398
|
+
</DropdownMenuItem>
|
|
1399
|
+
<DropdownMenuItem
|
|
1400
|
+
onSelect={(event) => {
|
|
1401
|
+
event.preventDefault();
|
|
1402
|
+
onSortsChange?.(
|
|
1403
|
+
upsertDatabaseSort(
|
|
1404
|
+
sorts ?? [],
|
|
1405
|
+
columnKey,
|
|
1406
|
+
property.definition.name,
|
|
1407
|
+
"desc",
|
|
1408
|
+
),
|
|
1409
|
+
);
|
|
1410
|
+
}}
|
|
1411
|
+
>
|
|
1412
|
+
<IconArrowDown className="mr-2 size-4 text-muted-foreground" />
|
|
1413
|
+
<span className="min-w-0 flex-1">
|
|
1414
|
+
{t("database.sortDescending")}
|
|
1415
|
+
</span>
|
|
1416
|
+
{columnSort?.direction === "desc" ? (
|
|
1417
|
+
<IconCheck className="size-4 text-muted-foreground" />
|
|
1418
|
+
) : null}
|
|
1419
|
+
</DropdownMenuItem>
|
|
1420
|
+
{columnSort ? (
|
|
1421
|
+
<DropdownMenuItem
|
|
1422
|
+
onSelect={(event) => {
|
|
1423
|
+
event.preventDefault();
|
|
1424
|
+
onSortsChange?.(clearDatabaseSort(sorts ?? [], columnKey));
|
|
1425
|
+
}}
|
|
1426
|
+
>
|
|
1427
|
+
<IconX className="mr-2 size-4 text-muted-foreground" />
|
|
1428
|
+
{t("database.clearSort")}
|
|
1429
|
+
</DropdownMenuItem>
|
|
1430
|
+
) : null}
|
|
1431
|
+
<DropdownMenuSeparator />
|
|
1432
|
+
{quickFilters.map((quickFilter) => (
|
|
1433
|
+
<DropdownMenuItem
|
|
1434
|
+
key={quickFilter.operator}
|
|
1435
|
+
onSelect={(event) => {
|
|
1436
|
+
event.preventDefault();
|
|
1437
|
+
onFiltersChange?.(
|
|
1438
|
+
upsertDatabaseQuickFilter(
|
|
1439
|
+
filters ?? [],
|
|
1440
|
+
columnKey,
|
|
1441
|
+
property.definition.name,
|
|
1442
|
+
quickFilter.operator,
|
|
1443
|
+
),
|
|
1444
|
+
);
|
|
1445
|
+
}}
|
|
1446
|
+
>
|
|
1447
|
+
<IconFilter className="mr-2 size-4 text-muted-foreground" />
|
|
1448
|
+
{quickFilter.label}
|
|
1449
|
+
</DropdownMenuItem>
|
|
1450
|
+
))}
|
|
1451
|
+
{columnFilterCount > 0 ? (
|
|
1452
|
+
<DropdownMenuItem
|
|
1453
|
+
onSelect={(event) => {
|
|
1454
|
+
event.preventDefault();
|
|
1455
|
+
onFiltersChange?.(
|
|
1456
|
+
clearDatabaseFiltersForColumn(filters ?? [], columnKey),
|
|
1457
|
+
);
|
|
1458
|
+
}}
|
|
1459
|
+
>
|
|
1460
|
+
<IconX className="mr-2 size-4 text-muted-foreground" />
|
|
1461
|
+
{t("editor.properties.clearFilters", {
|
|
1462
|
+
count: columnFilterCount,
|
|
1463
|
+
})}
|
|
1464
|
+
</DropdownMenuItem>
|
|
1465
|
+
) : null}
|
|
1466
|
+
{onHide ? (
|
|
1467
|
+
<>
|
|
1468
|
+
<DropdownMenuSeparator />
|
|
1328
1469
|
<DropdownMenuItem
|
|
1329
|
-
|
|
1330
|
-
disabled={disabled}
|
|
1470
|
+
disabled={hideDisabled}
|
|
1331
1471
|
onSelect={(event) => {
|
|
1332
1472
|
event.preventDefault();
|
|
1333
|
-
void
|
|
1473
|
+
void onHide();
|
|
1334
1474
|
}}
|
|
1335
1475
|
>
|
|
1336
|
-
<
|
|
1337
|
-
|
|
1338
|
-
{t(`editor.propertyTypes.${propertyType}`)}
|
|
1339
|
-
</span>
|
|
1340
|
-
{selected ? (
|
|
1341
|
-
<IconCheck className="size-4 text-muted-foreground" />
|
|
1342
|
-
) : null}
|
|
1476
|
+
<IconEyeOff className="mr-2 size-4 text-muted-foreground" />
|
|
1477
|
+
{t("database.hideInView")}
|
|
1343
1478
|
</DropdownMenuItem>
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
</DropdownMenuSubTrigger>
|
|
1361
|
-
<DropdownMenuSubContent className="w-56">
|
|
1362
|
-
{DOCUMENT_PROPERTY_VISIBILITIES.map((visibility) => (
|
|
1479
|
+
</>
|
|
1480
|
+
) : null}
|
|
1481
|
+
<DropdownMenuSeparator />
|
|
1482
|
+
<DropdownMenuItem
|
|
1483
|
+
onSelect={(event) => {
|
|
1484
|
+
event.preventDefault();
|
|
1485
|
+
setView("edit");
|
|
1486
|
+
}}
|
|
1487
|
+
>
|
|
1488
|
+
<IconEdit className="mr-2 size-4 text-muted-foreground" />
|
|
1489
|
+
{t("editor.properties.editField")}
|
|
1490
|
+
</DropdownMenuItem>
|
|
1491
|
+
</>
|
|
1492
|
+
) : (
|
|
1493
|
+
<>
|
|
1494
|
+
{hasColumnMenu ? (
|
|
1363
1495
|
<DropdownMenuItem
|
|
1364
|
-
key={visibility}
|
|
1365
1496
|
onSelect={(event) => {
|
|
1366
1497
|
event.preventDefault();
|
|
1367
|
-
|
|
1498
|
+
setView("quick");
|
|
1368
1499
|
}}
|
|
1500
|
+
className="gap-1.5 py-1 text-xs text-muted-foreground focus:text-foreground"
|
|
1369
1501
|
>
|
|
1370
|
-
<
|
|
1371
|
-
|
|
1372
|
-
</span>
|
|
1373
|
-
{property.definition.visibility === visibility ? (
|
|
1374
|
-
<IconCheck className="size-4 text-muted-foreground" />
|
|
1375
|
-
) : null}
|
|
1502
|
+
<IconArrowLeft className="size-3.5" />
|
|
1503
|
+
{t("editor.properties.backToColumnMenu")}
|
|
1376
1504
|
</DropdownMenuItem>
|
|
1377
|
-
)
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
{typeNeedsOptions ? (
|
|
1382
|
-
<div className="grid gap-2 px-1 py-2">
|
|
1383
|
-
<div className="px-1 text-xs font-medium text-muted-foreground">
|
|
1384
|
-
{t("editor.properties.options")}
|
|
1385
|
-
</div>
|
|
1386
|
-
<div className="grid gap-1">
|
|
1387
|
-
{(property.definition.options.options ?? []).map((option) => (
|
|
1388
|
-
<PropertyOptionSettingsRow
|
|
1389
|
-
key={option.id}
|
|
1390
|
-
option={option}
|
|
1391
|
-
disabled={configure.isPending}
|
|
1392
|
-
onRename={(name) => void renameOption(option.id, name)}
|
|
1393
|
-
onDescriptionChange={(description) =>
|
|
1394
|
-
void describeOption(option.id, description)
|
|
1395
|
-
}
|
|
1396
|
-
onColorChange={(color) =>
|
|
1397
|
-
void recolorOption(option.id, color)
|
|
1398
|
-
}
|
|
1399
|
-
onRemove={() => void removeOption(option.id)}
|
|
1400
|
-
/>
|
|
1401
|
-
))}
|
|
1402
|
-
</div>
|
|
1403
|
-
<form
|
|
1404
|
-
className="flex gap-2"
|
|
1405
|
-
onSubmit={(event) => {
|
|
1406
|
-
event.preventDefault();
|
|
1407
|
-
void addOption();
|
|
1408
|
-
}}
|
|
1505
|
+
) : null}
|
|
1506
|
+
<div
|
|
1507
|
+
className="flex items-center gap-2 p-1"
|
|
1508
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1409
1509
|
>
|
|
1510
|
+
<IconEdit className="size-4 shrink-0 text-muted-foreground" />
|
|
1410
1511
|
<Input
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1512
|
+
ref={propertyNameInputRef}
|
|
1513
|
+
value={name}
|
|
1514
|
+
aria-label={t("editor.properties.propertyName")}
|
|
1515
|
+
onChange={(event) => setName(event.target.value)}
|
|
1516
|
+
onBlur={() => void renameProperty()}
|
|
1517
|
+
onKeyDown={(event) => {
|
|
1518
|
+
if (event.key === "Enter") {
|
|
1519
|
+
event.preventDefault();
|
|
1520
|
+
event.currentTarget.blur();
|
|
1521
|
+
}
|
|
1522
|
+
}}
|
|
1415
1523
|
className="h-8"
|
|
1416
1524
|
/>
|
|
1417
|
-
|
|
1418
|
-
type="submit"
|
|
1419
|
-
size="sm"
|
|
1420
|
-
variant="secondary"
|
|
1421
|
-
disabled={!newOption.trim() || configure.isPending}
|
|
1422
|
-
>
|
|
1423
|
-
{t("editor.properties.add")}
|
|
1424
|
-
</Button>
|
|
1425
|
-
</form>
|
|
1426
|
-
</div>
|
|
1427
|
-
) : null}
|
|
1525
|
+
</div>
|
|
1428
1526
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
<
|
|
1434
|
-
{
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1527
|
+
<div
|
|
1528
|
+
className="px-2 pb-2 pt-1"
|
|
1529
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1530
|
+
>
|
|
1531
|
+
<Textarea
|
|
1532
|
+
rows={1}
|
|
1533
|
+
value={description}
|
|
1534
|
+
aria-label={t("editor.properties.description")}
|
|
1535
|
+
placeholder={t("editor.properties.addPropertyDescription")}
|
|
1536
|
+
onChange={(event) => setDescription(event.target.value)}
|
|
1537
|
+
onBlur={() => void updateDescription()}
|
|
1538
|
+
className="block min-h-0 w-full resize-none rounded border border-transparent bg-muted/30 px-2 py-1.5 text-xs leading-5 text-muted-foreground outline-none placeholder:text-muted-foreground/60 focus:resize-y focus:border-input focus:bg-background focus:ring-1 focus:ring-ring"
|
|
1539
|
+
/>
|
|
1540
|
+
</div>
|
|
1541
|
+
|
|
1542
|
+
<DropdownMenuSub>
|
|
1543
|
+
<DropdownMenuSubTrigger>
|
|
1544
|
+
<Icon className="mr-2 size-4 text-muted-foreground" />
|
|
1545
|
+
<span className="flex-1">{t("editor.properties.type")}</span>
|
|
1546
|
+
<span className="mr-2 text-muted-foreground">
|
|
1547
|
+
{t(`editor.propertyTypes.${property.definition.type}`)}
|
|
1548
|
+
</span>
|
|
1549
|
+
</DropdownMenuSubTrigger>
|
|
1550
|
+
<DropdownMenuSubContent className="z-[310] max-h-80 w-56 overflow-auto">
|
|
1551
|
+
{CREATABLE_DOCUMENT_PROPERTY_TYPES.map((propertyType) => {
|
|
1552
|
+
const TypeIcon = TYPE_ICONS[propertyType];
|
|
1553
|
+
const selected = property.definition.type === propertyType;
|
|
1554
|
+
const disabled = typeIsLocked && !selected;
|
|
1555
|
+
return (
|
|
1556
|
+
<DropdownMenuItem
|
|
1557
|
+
key={propertyType}
|
|
1558
|
+
disabled={disabled}
|
|
1559
|
+
onSelect={(event) => {
|
|
1560
|
+
event.preventDefault();
|
|
1561
|
+
void updateType(propertyType);
|
|
1562
|
+
}}
|
|
1442
1563
|
>
|
|
1443
|
-
<
|
|
1444
|
-
<span className="
|
|
1445
|
-
|
|
1446
|
-
{src.sourceName}
|
|
1447
|
-
</span>{" "}
|
|
1448
|
-
· {field.sourceFieldLabel}
|
|
1564
|
+
<TypeIcon className="mr-2 size-4 text-muted-foreground" />
|
|
1565
|
+
<span className="flex-1">
|
|
1566
|
+
{t(`editor.propertyTypes.${propertyType}`)}
|
|
1449
1567
|
</span>
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1568
|
+
{selected ? (
|
|
1569
|
+
<IconCheck className="size-4 text-muted-foreground" />
|
|
1570
|
+
) : null}
|
|
1571
|
+
</DropdownMenuItem>
|
|
1572
|
+
);
|
|
1573
|
+
})}
|
|
1574
|
+
</DropdownMenuSubContent>
|
|
1575
|
+
</DropdownMenuSub>
|
|
1576
|
+
|
|
1577
|
+
<DropdownMenuSub>
|
|
1578
|
+
<DropdownMenuSubTrigger>
|
|
1579
|
+
<IconEye className="mr-2 size-4 text-muted-foreground" />
|
|
1580
|
+
<span className="flex-1">
|
|
1581
|
+
{t("editor.properties.visibility")}
|
|
1582
|
+
</span>
|
|
1583
|
+
<span className="mr-2 text-muted-foreground">
|
|
1584
|
+
{t(
|
|
1585
|
+
`editor.propertyVisibility.${property.definition.visibility}`,
|
|
1586
|
+
)}
|
|
1587
|
+
</span>
|
|
1588
|
+
</DropdownMenuSubTrigger>
|
|
1589
|
+
<DropdownMenuSubContent className="z-[310] w-56">
|
|
1590
|
+
{DOCUMENT_PROPERTY_VISIBILITIES.map((visibility) => (
|
|
1591
|
+
<DropdownMenuItem
|
|
1592
|
+
key={visibility}
|
|
1593
|
+
onSelect={(event) => {
|
|
1594
|
+
event.preventDefault();
|
|
1595
|
+
void updateVisibility(visibility);
|
|
1596
|
+
}}
|
|
1597
|
+
>
|
|
1598
|
+
<span className="flex-1">
|
|
1599
|
+
{t(`editor.propertyVisibility.${visibility}`)}
|
|
1600
|
+
</span>
|
|
1601
|
+
{property.definition.visibility === visibility ? (
|
|
1602
|
+
<IconCheck className="size-4 text-muted-foreground" />
|
|
1603
|
+
) : null}
|
|
1604
|
+
</DropdownMenuItem>
|
|
1605
|
+
))}
|
|
1606
|
+
</DropdownMenuSubContent>
|
|
1607
|
+
</DropdownMenuSub>
|
|
1608
|
+
|
|
1609
|
+
{typeNeedsOptions ? (
|
|
1610
|
+
<div className="grid gap-2 px-1 py-2">
|
|
1611
|
+
<div className="px-1 text-xs font-medium text-muted-foreground">
|
|
1612
|
+
{t("editor.properties.options")}
|
|
1467
1613
|
</div>
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1614
|
+
<div className="grid gap-1">
|
|
1615
|
+
<DndContext
|
|
1616
|
+
sensors={optionDragSensors}
|
|
1617
|
+
collisionDetection={closestCenter}
|
|
1618
|
+
onDragEnd={handleOptionDragEnd}
|
|
1619
|
+
>
|
|
1620
|
+
<SortableContext
|
|
1621
|
+
items={optionsDraft.map((option) => option.id)}
|
|
1622
|
+
strategy={verticalListSortingStrategy}
|
|
1623
|
+
>
|
|
1624
|
+
{optionsDraft.map((option) => (
|
|
1625
|
+
<PropertyOptionSettingsRow
|
|
1626
|
+
key={option.id}
|
|
1627
|
+
option={option}
|
|
1628
|
+
disabled={configure.isPending}
|
|
1629
|
+
onRename={(name) =>
|
|
1630
|
+
void renameOption(option.id, name)
|
|
1631
|
+
}
|
|
1632
|
+
onDescriptionChange={(description) =>
|
|
1633
|
+
void describeOption(option.id, description)
|
|
1634
|
+
}
|
|
1635
|
+
onColorChange={(color) =>
|
|
1636
|
+
void recolorOption(option.id, color)
|
|
1637
|
+
}
|
|
1638
|
+
onRemove={() => void removeOption(option.id)}
|
|
1639
|
+
/>
|
|
1640
|
+
))}
|
|
1641
|
+
</SortableContext>
|
|
1642
|
+
</DndContext>
|
|
1471
1643
|
</div>
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
<
|
|
1480
|
-
{
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
{field.sourceFieldLabel}
|
|
1496
|
-
</span>
|
|
1497
|
-
<span className="ml-2 shrink-0 truncate text-[11px] text-muted-foreground">
|
|
1498
|
-
{src.sourceName}
|
|
1499
|
-
</span>
|
|
1500
|
-
</DropdownMenuItem>
|
|
1501
|
-
))}
|
|
1502
|
-
</DropdownMenuSubContent>
|
|
1503
|
-
</DropdownMenuSub>
|
|
1504
|
-
) : null}
|
|
1505
|
-
</div>
|
|
1506
|
-
</>
|
|
1507
|
-
) : sourceAttached ? (
|
|
1508
|
-
<>
|
|
1509
|
-
<DropdownMenuSeparator />
|
|
1510
|
-
<div className="grid gap-1 px-2 py-1.5 text-xs">
|
|
1511
|
-
<div className="font-medium text-foreground">
|
|
1512
|
-
{t("editor.properties.source")}
|
|
1644
|
+
<form
|
|
1645
|
+
className="flex gap-2"
|
|
1646
|
+
onSubmit={(event) => {
|
|
1647
|
+
event.preventDefault();
|
|
1648
|
+
void addOption();
|
|
1649
|
+
}}
|
|
1650
|
+
>
|
|
1651
|
+
<Input
|
|
1652
|
+
value={newOption}
|
|
1653
|
+
placeholder={t("editor.properties.addOption")}
|
|
1654
|
+
onChange={(event) => setNewOption(event.target.value)}
|
|
1655
|
+
onKeyDown={(event) => event.stopPropagation()}
|
|
1656
|
+
className="h-8"
|
|
1657
|
+
/>
|
|
1658
|
+
<Button
|
|
1659
|
+
type="submit"
|
|
1660
|
+
size="sm"
|
|
1661
|
+
variant="secondary"
|
|
1662
|
+
disabled={!newOption.trim() || configure.isPending}
|
|
1663
|
+
>
|
|
1664
|
+
{t("editor.properties.add")}
|
|
1665
|
+
</Button>
|
|
1666
|
+
</form>
|
|
1513
1667
|
</div>
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1668
|
+
) : null}
|
|
1669
|
+
|
|
1670
|
+
{showBindingEditor ? (
|
|
1671
|
+
<>
|
|
1672
|
+
<DropdownMenuSeparator />
|
|
1673
|
+
<div className="grid gap-1.5 px-2 py-1.5 text-xs">
|
|
1674
|
+
<div className="font-medium text-foreground">
|
|
1675
|
+
{t("database.sourcesFeedingThisColumn")}
|
|
1519
1676
|
</div>
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1677
|
+
{boundSourceFields.length > 0 ? (
|
|
1678
|
+
<div className="grid gap-1">
|
|
1679
|
+
{boundSourceFields.map(({ source: src, field }) => (
|
|
1680
|
+
<div
|
|
1681
|
+
key={field.id}
|
|
1682
|
+
className="flex min-w-0 items-center gap-1.5"
|
|
1683
|
+
>
|
|
1684
|
+
<IconLink className="size-3.5 shrink-0 text-muted-foreground" />
|
|
1685
|
+
<span className="min-w-0 flex-1 truncate text-muted-foreground">
|
|
1686
|
+
<span className="text-foreground">
|
|
1687
|
+
{src.sourceName}
|
|
1688
|
+
</span>{" "}
|
|
1689
|
+
· {field.sourceFieldLabel}
|
|
1690
|
+
</span>
|
|
1691
|
+
<button
|
|
1692
|
+
type="button"
|
|
1693
|
+
aria-label={`Unbind ${field.sourceFieldLabel} from ${src.sourceName}`}
|
|
1694
|
+
disabled={bindSourceField.isPending}
|
|
1695
|
+
className="shrink-0 rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-50"
|
|
1696
|
+
onClick={() =>
|
|
1697
|
+
void bindSourceField.mutateAsync({
|
|
1698
|
+
documentId,
|
|
1699
|
+
sourceFieldId: field.id,
|
|
1700
|
+
propertyId: null,
|
|
1701
|
+
})
|
|
1702
|
+
}
|
|
1703
|
+
>
|
|
1704
|
+
<IconX className="size-3.5" />
|
|
1705
|
+
</button>
|
|
1706
|
+
</div>
|
|
1707
|
+
))}
|
|
1708
|
+
</div>
|
|
1709
|
+
) : (
|
|
1710
|
+
<div className="text-muted-foreground">
|
|
1711
|
+
{t("database.noSourceFieldsBoundYet")}
|
|
1712
|
+
</div>
|
|
1713
|
+
)}
|
|
1714
|
+
{bindableSourceFields.length > 0 ? (
|
|
1715
|
+
<DropdownMenuSub>
|
|
1716
|
+
<DropdownMenuSubTrigger className="mt-0.5 rounded px-1.5 py-1 text-xs">
|
|
1717
|
+
<IconPlus className="mr-1.5 size-3.5 text-muted-foreground" />
|
|
1718
|
+
{t("database.bindAFieldFromASource")}
|
|
1719
|
+
</DropdownMenuSubTrigger>
|
|
1720
|
+
<DropdownMenuSubContent className="z-[310] max-h-80 w-64 overflow-auto">
|
|
1721
|
+
{bindableSourceFields.map(
|
|
1722
|
+
({ source: src, field }) => (
|
|
1723
|
+
<DropdownMenuItem
|
|
1724
|
+
key={field.id}
|
|
1725
|
+
disabled={bindSourceField.isPending}
|
|
1726
|
+
onSelect={(event) => {
|
|
1727
|
+
event.preventDefault();
|
|
1728
|
+
void bindSourceField.mutateAsync({
|
|
1729
|
+
documentId,
|
|
1730
|
+
sourceFieldId: field.id,
|
|
1731
|
+
propertyId: property.definition.id,
|
|
1732
|
+
});
|
|
1733
|
+
}}
|
|
1734
|
+
>
|
|
1735
|
+
<IconLink className="mr-2 size-3.5 shrink-0 text-muted-foreground" />
|
|
1736
|
+
<span className="min-w-0 flex-1 truncate">
|
|
1737
|
+
{field.sourceFieldLabel}
|
|
1738
|
+
</span>
|
|
1739
|
+
<span className="ml-2 shrink-0 truncate text-[11px] text-muted-foreground">
|
|
1740
|
+
{src.sourceName}
|
|
1741
|
+
</span>
|
|
1742
|
+
</DropdownMenuItem>
|
|
1743
|
+
),
|
|
1744
|
+
)}
|
|
1745
|
+
</DropdownMenuSubContent>
|
|
1746
|
+
</DropdownMenuSub>
|
|
1747
|
+
) : null}
|
|
1748
|
+
</div>
|
|
1749
|
+
</>
|
|
1750
|
+
) : sourceAttached ? (
|
|
1751
|
+
<>
|
|
1752
|
+
<DropdownMenuSeparator />
|
|
1753
|
+
<div className="grid gap-1 px-2 py-1.5 text-xs">
|
|
1754
|
+
<div className="font-medium text-foreground">
|
|
1755
|
+
{t("editor.properties.source")}
|
|
1526
1756
|
</div>
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1757
|
+
{sourceField ? (
|
|
1758
|
+
<>
|
|
1759
|
+
<div className="min-w-0 break-words text-muted-foreground">
|
|
1760
|
+
{sourceField.sourceFieldLabel} (
|
|
1761
|
+
{sourceField.sourceFieldKey})
|
|
1762
|
+
</div>
|
|
1763
|
+
<div className="text-muted-foreground">
|
|
1764
|
+
{sourceField.readOnly
|
|
1765
|
+
? t("editor.properties.readOnly")
|
|
1766
|
+
: sourceField.writeOwner === "source"
|
|
1767
|
+
? t("editor.properties.sourceOwned")
|
|
1768
|
+
: t("editor.properties.localEditsAllowed")}
|
|
1769
|
+
</div>
|
|
1770
|
+
</>
|
|
1771
|
+
) : (
|
|
1772
|
+
<div className="text-muted-foreground">
|
|
1773
|
+
{t("editor.properties.notMappedToBuilder")}
|
|
1774
|
+
</div>
|
|
1775
|
+
)}
|
|
1531
1776
|
</div>
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
</>
|
|
1535
|
-
) : null}
|
|
1777
|
+
</>
|
|
1778
|
+
) : null}
|
|
1536
1779
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1780
|
+
<DropdownMenuSeparator />
|
|
1781
|
+
<DropdownMenuItem
|
|
1782
|
+
disabled={duplicate.isPending}
|
|
1783
|
+
onSelect={(event) => {
|
|
1784
|
+
event.preventDefault();
|
|
1785
|
+
void duplicateProperty();
|
|
1786
|
+
}}
|
|
1787
|
+
>
|
|
1788
|
+
<IconCopy className="mr-2 size-4 text-muted-foreground" />
|
|
1789
|
+
{t("editor.properties.duplicateProperty")}
|
|
1790
|
+
</DropdownMenuItem>
|
|
1791
|
+
<DropdownMenuItem
|
|
1792
|
+
disabled={remove.isPending}
|
|
1793
|
+
className="text-destructive focus:bg-destructive/10 focus:text-destructive"
|
|
1794
|
+
onSelect={(event) => {
|
|
1795
|
+
event.preventDefault();
|
|
1796
|
+
setOpen(false);
|
|
1797
|
+
setConfirmDeleteOpen(true);
|
|
1798
|
+
}}
|
|
1799
|
+
>
|
|
1800
|
+
<IconTrash className="mr-2 size-4" />
|
|
1801
|
+
{t("editor.properties.deleteProperty")}
|
|
1802
|
+
</DropdownMenuItem>
|
|
1803
|
+
</>
|
|
1804
|
+
)}
|
|
1560
1805
|
</DropdownMenuContent>
|
|
1561
1806
|
</DropdownMenu>
|
|
1562
1807
|
|
|
@@ -1616,6 +1861,14 @@ function PropertyOptionSettingsRow({
|
|
|
1616
1861
|
const [draftDescription, setDraftDescription] = useState(
|
|
1617
1862
|
option.description ?? "",
|
|
1618
1863
|
);
|
|
1864
|
+
const {
|
|
1865
|
+
attributes,
|
|
1866
|
+
listeners,
|
|
1867
|
+
setNodeRef,
|
|
1868
|
+
transform,
|
|
1869
|
+
transition,
|
|
1870
|
+
isDragging,
|
|
1871
|
+
} = useSortable({ id: option.id, disabled });
|
|
1619
1872
|
|
|
1620
1873
|
useEffect(() => {
|
|
1621
1874
|
setDraftName(option.name);
|
|
@@ -1634,45 +1887,39 @@ function PropertyOptionSettingsRow({
|
|
|
1634
1887
|
}
|
|
1635
1888
|
|
|
1636
1889
|
return (
|
|
1637
|
-
<div
|
|
1890
|
+
<div
|
|
1891
|
+
ref={setNodeRef}
|
|
1892
|
+
style={{ transform: CSS.Transform.toString(transform), transition }}
|
|
1893
|
+
className={cn(
|
|
1894
|
+
"grid gap-1 rounded px-2 py-1 hover:bg-muted/50",
|
|
1895
|
+
isDragging && "relative z-10 bg-muted/50",
|
|
1896
|
+
)}
|
|
1897
|
+
>
|
|
1638
1898
|
<div className="flex items-center gap-2">
|
|
1639
|
-
<
|
|
1640
|
-
|
|
1641
|
-
className={cn("size-3 shrink-0 rounded-full", optionClass(option))}
|
|
1642
|
-
/>
|
|
1643
|
-
<Input
|
|
1644
|
-
value={draftName}
|
|
1899
|
+
<button
|
|
1900
|
+
type="button"
|
|
1645
1901
|
disabled={disabled}
|
|
1646
|
-
aria-label={t("editor.properties.
|
|
1902
|
+
aria-label={t("editor.properties.reorderOption", {
|
|
1647
1903
|
name: option.name,
|
|
1648
1904
|
})}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
submitRename();
|
|
1656
|
-
event.currentTarget.blur();
|
|
1657
|
-
}
|
|
1658
|
-
if (event.key === "Escape") {
|
|
1659
|
-
setDraftName(option.name);
|
|
1660
|
-
event.currentTarget.blur();
|
|
1661
|
-
}
|
|
1662
|
-
}}
|
|
1663
|
-
className="h-7 min-w-0 flex-1 border-0 bg-transparent px-1 text-sm shadow-none focus-visible:bg-background focus-visible:ring-1"
|
|
1664
|
-
/>
|
|
1665
|
-
</div>
|
|
1666
|
-
<div className="flex items-center justify-between gap-2 pl-5">
|
|
1905
|
+
className="size-5 shrink-0 cursor-grab touch-none rounded text-muted-foreground/60 hover:text-muted-foreground active:cursor-grabbing disabled:opacity-50"
|
|
1906
|
+
{...attributes}
|
|
1907
|
+
{...listeners}
|
|
1908
|
+
>
|
|
1909
|
+
<IconGripVertical className="size-3.5" />
|
|
1910
|
+
</button>
|
|
1667
1911
|
<DropdownMenuSub>
|
|
1668
1912
|
<DropdownMenuSubTrigger
|
|
1669
1913
|
disabled={disabled}
|
|
1670
|
-
|
|
1914
|
+
aria-label={t("editor.properties.color")}
|
|
1915
|
+
className="size-5 shrink-0 justify-center rounded-full p-0 [&_svg]:hidden"
|
|
1671
1916
|
>
|
|
1672
|
-
<
|
|
1673
|
-
|
|
1917
|
+
<span
|
|
1918
|
+
aria-hidden
|
|
1919
|
+
className={cn("block size-3 rounded-full", optionClass(option))}
|
|
1920
|
+
/>
|
|
1674
1921
|
</DropdownMenuSubTrigger>
|
|
1675
|
-
<DropdownMenuSubContent className="w-44">
|
|
1922
|
+
<DropdownMenuSubContent className="z-[310] w-44">
|
|
1676
1923
|
{OPTION_COLORS.map((color) => (
|
|
1677
1924
|
<DropdownMenuItem
|
|
1678
1925
|
key={color}
|
|
@@ -1682,32 +1929,52 @@ function PropertyOptionSettingsRow({
|
|
|
1682
1929
|
}}
|
|
1683
1930
|
>
|
|
1684
1931
|
<span
|
|
1685
|
-
aria-hidden
|
|
1686
1932
|
className={cn(
|
|
1687
|
-
"
|
|
1933
|
+
"inline-flex items-center rounded px-1.5 py-0.5 text-xs font-medium capitalize",
|
|
1688
1934
|
OPTION_COLOR_CLASSES[color],
|
|
1689
1935
|
)}
|
|
1690
|
-
|
|
1691
|
-
<span className="flex-1 capitalize">
|
|
1936
|
+
>
|
|
1692
1937
|
{t(`editor.propertyOptionColors.${color}`)}
|
|
1693
1938
|
</span>
|
|
1694
1939
|
{option.color === color ? (
|
|
1695
|
-
<IconCheck className="size-4 text-muted-foreground" />
|
|
1940
|
+
<IconCheck className="ml-auto size-4 shrink-0 text-muted-foreground" />
|
|
1696
1941
|
) : null}
|
|
1697
1942
|
</DropdownMenuItem>
|
|
1698
1943
|
))}
|
|
1699
1944
|
</DropdownMenuSubContent>
|
|
1700
1945
|
</DropdownMenuSub>
|
|
1946
|
+
<Input
|
|
1947
|
+
value={draftName}
|
|
1948
|
+
disabled={disabled}
|
|
1949
|
+
aria-label={t("editor.properties.renameOption", {
|
|
1950
|
+
name: option.name,
|
|
1951
|
+
})}
|
|
1952
|
+
onChange={(event) => setDraftName(event.target.value)}
|
|
1953
|
+
onBlur={submitRename}
|
|
1954
|
+
onKeyDown={(event) => {
|
|
1955
|
+
event.stopPropagation();
|
|
1956
|
+
if (event.key === "Enter") {
|
|
1957
|
+
event.preventDefault();
|
|
1958
|
+
submitRename();
|
|
1959
|
+
event.currentTarget.blur();
|
|
1960
|
+
}
|
|
1961
|
+
if (event.key === "Escape") {
|
|
1962
|
+
setDraftName(option.name);
|
|
1963
|
+
event.currentTarget.blur();
|
|
1964
|
+
}
|
|
1965
|
+
}}
|
|
1966
|
+
className="h-7 min-w-0 flex-1 border-0 bg-transparent px-1 text-sm shadow-none focus-visible:bg-background focus-visible:ring-1"
|
|
1967
|
+
/>
|
|
1701
1968
|
<button
|
|
1702
1969
|
type="button"
|
|
1703
1970
|
aria-label={t("editor.properties.removeOption", {
|
|
1704
1971
|
name: option.name,
|
|
1705
1972
|
})}
|
|
1706
1973
|
disabled={disabled}
|
|
1707
|
-
className="h-7 rounded px-1.5 text-
|
|
1974
|
+
className="h-7 shrink-0 rounded px-1.5 text-muted-foreground hover:text-destructive disabled:opacity-50"
|
|
1708
1975
|
onClick={onRemove}
|
|
1709
1976
|
>
|
|
1710
|
-
|
|
1977
|
+
<IconX className="size-3.5" />
|
|
1711
1978
|
</button>
|
|
1712
1979
|
</div>
|
|
1713
1980
|
<Textarea
|
|
@@ -1723,7 +1990,7 @@ function PropertyOptionSettingsRow({
|
|
|
1723
1990
|
onDescriptionChange(nextDescription);
|
|
1724
1991
|
}
|
|
1725
1992
|
}}
|
|
1726
|
-
className="
|
|
1993
|
+
className="block min-h-0 w-full resize-none rounded border-0 bg-transparent px-1 text-xs leading-5 text-muted-foreground shadow-none placeholder:text-muted-foreground/60 focus:resize-y focus:bg-background focus:ring-1 focus:ring-ring"
|
|
1727
1994
|
/>
|
|
1728
1995
|
</div>
|
|
1729
1996
|
);
|