@aglyn/plugins-data 1.0.0-beta.244 → 1.0.0-beta.247
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/package.json +10 -10
- package/src/lib/components/dataset-field-inputs.component.d.ts +37 -0
- package/src/lib/components/dataset-field-inputs.component.js +243 -0
- package/src/lib/components/dataset-field-inputs.component.js.map +1 -0
- package/src/lib/components/host-datasets-card.component.js +149 -136
- package/src/lib/components/host-datasets-card.component.js.map +1 -1
- package/src/lib/declarations.server.js +3 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/form-target/form-dataset-binding.js +3 -2
- package/src/lib/form-target/form-dataset-binding.js.map +1 -1
- package/src/lib/model/dataset-image-field.d.ts +5 -0
- package/src/lib/model/dataset-image-field.js +8 -3
- package/src/lib/model/dataset-image-field.js.map +1 -1
- package/src/lib/model/dataset-page-field.d.ts +42 -0
- package/src/lib/model/dataset-page-field.js +57 -0
- package/src/lib/model/dataset-page-field.js.map +1 -0
- package/src/lib/plugin.js +12 -2
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/dataset-ai-capability.js +4 -2
- package/src/lib/server/dataset-ai-capability.js.map +1 -1
- package/src/lib/server/dataset-drafts.d.ts +6 -1
- package/src/lib/server/dataset-drafts.js +11 -2
- package/src/lib/server/dataset-drafts.js.map +1 -1
- package/src/lib/server/dataset-record-index.d.ts +6 -0
- package/src/lib/server/dataset-record-index.js +43 -3
- package/src/lib/server/dataset-record-index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-data",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.247",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,15 +25,15 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
34
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
35
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
36
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.247",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.247",
|
|
30
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.247",
|
|
31
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.247",
|
|
32
|
+
"@aglyn/shared-util-http": "1.0.0-beta.247",
|
|
33
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.247",
|
|
34
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.247",
|
|
35
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.247",
|
|
36
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.247",
|
|
37
37
|
"@swc/helpers": "0.5.23"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CustomFieldInputProps } from '@aglyn/aglyn/plugin-manager/custom-fields';
|
|
2
|
+
/**
|
|
3
|
+
* The record editor's inputs for the two field types that hold a PLACE rather
|
|
4
|
+
* than words (AGL-3763): a photo and a page. Both are chosen from what the
|
|
5
|
+
* workspace already holds — the media library and the site's pages — so a
|
|
6
|
+
* site owner filling a record never types an address or a path.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The site the record editor is open on, for the Page field's list.
|
|
10
|
+
*
|
|
11
|
+
* Datasets belong to the organization, and a page belongs to one site, so
|
|
12
|
+
* the list can only be the pages of the site whose Data page is open. The
|
|
13
|
+
* organization Data page provides no site, and its Page field says where to
|
|
14
|
+
* go instead of guessing one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DatasetRecordSiteContext: import("react").Context<{
|
|
17
|
+
hostId?: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* A record's photo: Choose from media opens the console's media library
|
|
21
|
+
* (this site's files and the organization's), and the pick is stored as a
|
|
22
|
+
* `media:` reference so moving or replacing the file never breaks the record.
|
|
23
|
+
* A photo hosted elsewhere is still possible, behind "Use a link instead".
|
|
24
|
+
*/
|
|
25
|
+
export declare function DatasetImageFieldInput(props: CustomFieldInputProps): import("react").JSX.Element;
|
|
26
|
+
export declare namespace DatasetImageFieldInput {
|
|
27
|
+
var displayName: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A record's page: one of the site's pages, picked by name. Stored as the
|
|
31
|
+
* page's link reference, so a Button or Link bound to it follows the page to
|
|
32
|
+
* its current address.
|
|
33
|
+
*/
|
|
34
|
+
export declare function DatasetPageFieldInput(props: CustomFieldInputProps): import("react").JSX.Element;
|
|
35
|
+
export declare namespace DatasetPageFieldInput {
|
|
36
|
+
var displayName: string;
|
|
37
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { isScreenGroup, useMediaPicker } from "@aglyn/aglyn";
|
|
19
|
+
import { formatMediaRef, mediaNodeSrc, resolveMediaSrc } from "@aglyn/aglyn/app-utils/media-ref";
|
|
20
|
+
import { SCREEN_KIND_EMAIL } from "@aglyn/aglyn/app-utils/screen-kind";
|
|
21
|
+
import { mdiImageOutline } from "@aglyn/shared-data-mdi";
|
|
22
|
+
import { MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
23
|
+
import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
|
|
24
|
+
import { Box, Button, Link, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
25
|
+
import { collection, limit, query } from "firebase/firestore";
|
|
26
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
27
|
+
import { datasetPageScreenId, datasetPageValue } from "../model/dataset-page-field.js";
|
|
28
|
+
/**
|
|
29
|
+
* The record editor's inputs for the two field types that hold a PLACE rather
|
|
30
|
+
* than words (AGL-3763): a photo and a page. Both are chosen from what the
|
|
31
|
+
* workspace already holds — the media library and the site's pages — so a
|
|
32
|
+
* site owner filling a record never types an address or a path.
|
|
33
|
+
*/ /**
|
|
34
|
+
* The site the record editor is open on, for the Page field's list.
|
|
35
|
+
*
|
|
36
|
+
* Datasets belong to the organization, and a page belongs to one site, so
|
|
37
|
+
* the list can only be the pages of the site whose Data page is open. The
|
|
38
|
+
* organization Data page provides no site, and its Page field says where to
|
|
39
|
+
* go instead of guessing one.
|
|
40
|
+
*/ export const DatasetRecordSiteContext = /*#__PURE__*/ createContext({});
|
|
41
|
+
DatasetRecordSiteContext.displayName = 'DatasetRecordSiteContext';
|
|
42
|
+
/** The most pages the Page field lists — the Besigner's own page pickers read as many. */ const PAGE_LIST_LIMIT = 200;
|
|
43
|
+
function valueOf(props) {
|
|
44
|
+
return typeof props.value === 'string' ? props.value : '';
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A record's photo: Choose from media opens the console's media library
|
|
48
|
+
* (this site's files and the organization's), and the pick is stored as a
|
|
49
|
+
* `media:` reference so moving or replacing the file never breaks the record.
|
|
50
|
+
* A photo hosted elsewhere is still possible, behind "Use a link instead".
|
|
51
|
+
*/ export function DatasetImageFieldInput(props) {
|
|
52
|
+
const { onChange, disabled, label } = props;
|
|
53
|
+
const { hostId } = useContext(DatasetRecordSiteContext);
|
|
54
|
+
const { pickMedia } = useMediaPicker();
|
|
55
|
+
const value = valueOf(props);
|
|
56
|
+
const preview = resolveMediaSrc(value.trim(), {
|
|
57
|
+
hostId
|
|
58
|
+
});
|
|
59
|
+
// A stored https link opens on the link field, so it can be read and edited.
|
|
60
|
+
const [linkOpen, setLinkOpen] = useState(()=>/^https:\/\//i.test(value) || !pickMedia);
|
|
61
|
+
const choose = async ()=>{
|
|
62
|
+
var _ref, _formatMediaRef;
|
|
63
|
+
const media = await (pickMedia == null ? void 0 : pickMedia({
|
|
64
|
+
kind: 'image'
|
|
65
|
+
}));
|
|
66
|
+
if (!media) return;
|
|
67
|
+
// The asset by identity when the picker names it, else read back out of
|
|
68
|
+
// the CDN path it handed over; a library with neither gives its address.
|
|
69
|
+
onChange((_ref = (_formatMediaRef = formatMediaRef(media.mediaScope, media.mediaId)) != null ? _formatMediaRef : mediaNodeSrc({
|
|
70
|
+
cdnPath: media.url,
|
|
71
|
+
url: media.url
|
|
72
|
+
})) != null ? _ref : media.url);
|
|
73
|
+
};
|
|
74
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
75
|
+
spacing: 1,
|
|
76
|
+
children: [
|
|
77
|
+
label ? /*#__PURE__*/ _jsx(Typography, {
|
|
78
|
+
variant: "body2",
|
|
79
|
+
color: "text.secondary",
|
|
80
|
+
children: label
|
|
81
|
+
}) : null,
|
|
82
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
83
|
+
direction: "row",
|
|
84
|
+
spacing: 1.5,
|
|
85
|
+
useFlexGap: true,
|
|
86
|
+
sx: {
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
flexWrap: 'wrap'
|
|
89
|
+
},
|
|
90
|
+
children: [
|
|
91
|
+
preview ? /*#__PURE__*/ _jsx(Box, {
|
|
92
|
+
component: "img",
|
|
93
|
+
src: preview,
|
|
94
|
+
alt: "",
|
|
95
|
+
sx: {
|
|
96
|
+
width: 96,
|
|
97
|
+
height: 72,
|
|
98
|
+
objectFit: 'cover',
|
|
99
|
+
borderRadius: 1,
|
|
100
|
+
border: 1,
|
|
101
|
+
borderColor: 'divider',
|
|
102
|
+
bgcolor: 'action.hover'
|
|
103
|
+
}
|
|
104
|
+
}) : /*#__PURE__*/ _jsx(Stack, {
|
|
105
|
+
sx: {
|
|
106
|
+
width: 96,
|
|
107
|
+
height: 72,
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
justifyContent: 'center',
|
|
110
|
+
borderRadius: 1,
|
|
111
|
+
border: 1,
|
|
112
|
+
borderStyle: 'dashed',
|
|
113
|
+
borderColor: 'divider',
|
|
114
|
+
color: 'text.secondary'
|
|
115
|
+
},
|
|
116
|
+
children: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
117
|
+
path: mdiImageOutline.path,
|
|
118
|
+
size: 1
|
|
119
|
+
})
|
|
120
|
+
}),
|
|
121
|
+
pickMedia ? /*#__PURE__*/ _jsx(Button, {
|
|
122
|
+
size: "small",
|
|
123
|
+
variant: "outlined",
|
|
124
|
+
disabled: disabled,
|
|
125
|
+
onClick: choose,
|
|
126
|
+
children: value ? 'Change photo' : 'Choose from media'
|
|
127
|
+
}) : null,
|
|
128
|
+
value ? /*#__PURE__*/ _jsx(Button, {
|
|
129
|
+
size: "small",
|
|
130
|
+
color: "error",
|
|
131
|
+
disabled: disabled,
|
|
132
|
+
onClick: ()=>onChange(''),
|
|
133
|
+
children: 'Remove'
|
|
134
|
+
}) : null
|
|
135
|
+
]
|
|
136
|
+
}),
|
|
137
|
+
linkOpen ? /*#__PURE__*/ _jsx(TextField, {
|
|
138
|
+
size: "small",
|
|
139
|
+
label: "Photo link",
|
|
140
|
+
placeholder: "https://",
|
|
141
|
+
disabled: disabled,
|
|
142
|
+
// A picked photo is a reference, not a link: the field stays empty
|
|
143
|
+
// for it rather than showing a value nobody should edit by hand.
|
|
144
|
+
value: /^https:\/\//i.test(value) || !value ? value : '',
|
|
145
|
+
onChange: (event)=>onChange(event.target.value.trim()),
|
|
146
|
+
helperText: "A photo hosted on another website, starting with https://"
|
|
147
|
+
}) : /*#__PURE__*/ _jsx(Link, {
|
|
148
|
+
component: "button",
|
|
149
|
+
type: "button",
|
|
150
|
+
variant: "caption",
|
|
151
|
+
underline: "hover",
|
|
152
|
+
sx: {
|
|
153
|
+
alignSelf: 'flex-start'
|
|
154
|
+
},
|
|
155
|
+
disabled: disabled,
|
|
156
|
+
onClick: ()=>setLinkOpen(true),
|
|
157
|
+
children: 'Use a link instead'
|
|
158
|
+
})
|
|
159
|
+
]
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
DatasetImageFieldInput.displayName = 'DatasetImageFieldInput';
|
|
163
|
+
/** The Page field where no site is open: it names what is held and where to choose. */ function PageFieldWithoutSite(props) {
|
|
164
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
165
|
+
spacing: 0.5,
|
|
166
|
+
children: [
|
|
167
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
168
|
+
size: "small",
|
|
169
|
+
label: props.label,
|
|
170
|
+
value: props.chosen ? 'A page is chosen' : 'No page chosen',
|
|
171
|
+
disabled: true
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
174
|
+
variant: "caption",
|
|
175
|
+
color: "text.secondary",
|
|
176
|
+
children: 'Pages belong to a site. Open this dataset from that site’s Data page to choose one.'
|
|
177
|
+
})
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
PageFieldWithoutSite.displayName = 'PageFieldWithoutSite';
|
|
182
|
+
/**
|
|
183
|
+
* A record's page: one of the site's pages, picked by name. Stored as the
|
|
184
|
+
* page's link reference, so a Button or Link bound to it follows the page to
|
|
185
|
+
* its current address.
|
|
186
|
+
*/ export function DatasetPageFieldInput(props) {
|
|
187
|
+
var _datasetPageScreenId;
|
|
188
|
+
const { onChange, disabled, label } = props;
|
|
189
|
+
const { hostId } = useContext(DatasetRecordSiteContext);
|
|
190
|
+
const firestore = useFirestore();
|
|
191
|
+
const value = valueOf(props);
|
|
192
|
+
const selected = (_datasetPageScreenId = datasetPageScreenId(value)) != null ? _datasetPageScreenId : '';
|
|
193
|
+
const { data: screenDocs, status } = useFirestoreCollection(()=>hostId ? query(collection(firestore, 'hosts', hostId, 'screens'), limit(PAGE_LIST_LIMIT)) : null, [
|
|
194
|
+
firestore,
|
|
195
|
+
hostId
|
|
196
|
+
], {
|
|
197
|
+
idField: '$id'
|
|
198
|
+
});
|
|
199
|
+
const pages = useMemo(()=>[
|
|
200
|
+
...screenDocs != null ? screenDocs : []
|
|
201
|
+
]// Pages a visitor can open: not an email design, not a page group.
|
|
202
|
+
.filter((screen)=>!screen.deletedAt && screen.kind !== SCREEN_KIND_EMAIL && !isScreenGroup(screen)).map((screen)=>{
|
|
203
|
+
var _ref, _screen_displayName;
|
|
204
|
+
return {
|
|
205
|
+
id: String(screen.$id),
|
|
206
|
+
name: String((_ref = (_screen_displayName = screen.displayName) != null ? _screen_displayName : screen.name) != null ? _ref : screen.$id)
|
|
207
|
+
};
|
|
208
|
+
}).sort((a, b)=>a.name.localeCompare(b.name)), [
|
|
209
|
+
screenDocs
|
|
210
|
+
]);
|
|
211
|
+
const known = !selected || pages.some((page)=>page.id === selected);
|
|
212
|
+
if (!hostId) return /*#__PURE__*/ _jsx(PageFieldWithoutSite, {
|
|
213
|
+
label: label,
|
|
214
|
+
chosen: Boolean(selected)
|
|
215
|
+
});
|
|
216
|
+
return /*#__PURE__*/ _jsxs(TextField, {
|
|
217
|
+
size: "small",
|
|
218
|
+
select: true,
|
|
219
|
+
label: label,
|
|
220
|
+
disabled: disabled,
|
|
221
|
+
value: selected,
|
|
222
|
+
onChange: (event)=>onChange(event.target.value ? datasetPageValue(event.target.value) : ''),
|
|
223
|
+
helperText: status === 'loading' && !screenDocs ? 'Loading pages…' : undefined,
|
|
224
|
+
children: [
|
|
225
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
226
|
+
value: "",
|
|
227
|
+
children: 'No page'
|
|
228
|
+
}),
|
|
229
|
+
pages.map((page)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
230
|
+
value: page.id,
|
|
231
|
+
children: page.name
|
|
232
|
+
}, page.id)),
|
|
233
|
+
known ? null : /*#__PURE__*/ _jsx(MenuItem, {
|
|
234
|
+
value: selected,
|
|
235
|
+
disabled: true,
|
|
236
|
+
children: '⚠ A page this site no longer has'
|
|
237
|
+
})
|
|
238
|
+
]
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
DatasetPageFieldInput.displayName = 'DatasetPageFieldInput';
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=dataset-field-inputs.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/data/src/lib/components/dataset-field-inputs.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { isScreenGroup, useMediaPicker } from '@aglyn/aglyn'\nimport { formatMediaRef, mediaNodeSrc, resolveMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { SCREEN_KIND_EMAIL } from '@aglyn/aglyn/app-utils/screen-kind'\nimport type { CustomFieldInputProps } from '@aglyn/aglyn/plugin-manager/custom-fields'\nimport { mdiImageOutline } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useFirestore, useFirestoreCollection } from '@aglyn/tenant-feature-instance'\nimport { Box, Button, Link, MenuItem, Stack, TextField, Typography } from '@mui/material'\nimport { collection, limit, query } from 'firebase/firestore'\nimport { createContext, useContext, useMemo, useState } from 'react'\nimport { datasetPageScreenId, datasetPageValue } from '../model/dataset-page-field'\n\n/**\n * The record editor's inputs for the two field types that hold a PLACE rather\n * than words (AGL-3763): a photo and a page. Both are chosen from what the\n * workspace already holds — the media library and the site's pages — so a\n * site owner filling a record never types an address or a path.\n */\n\n/**\n * The site the record editor is open on, for the Page field's list.\n *\n * Datasets belong to the organization, and a page belongs to one site, so\n * the list can only be the pages of the site whose Data page is open. The\n * organization Data page provides no site, and its Page field says where to\n * go instead of guessing one.\n */\nexport const DatasetRecordSiteContext = createContext<{ hostId?: string }>({})\nDatasetRecordSiteContext.displayName = 'DatasetRecordSiteContext'\n\n/** The most pages the Page field lists — the Besigner's own page pickers read as many. */\nconst PAGE_LIST_LIMIT = 200\n\nfunction valueOf(props: CustomFieldInputProps): string {\n return typeof props.value === 'string' ? props.value : ''\n}\n\n/**\n * A record's photo: Choose from media opens the console's media library\n * (this site's files and the organization's), and the pick is stored as a\n * `media:` reference so moving or replacing the file never breaks the record.\n * A photo hosted elsewhere is still possible, behind \"Use a link instead\".\n */\nexport function DatasetImageFieldInput(props: CustomFieldInputProps) {\n const { onChange, disabled, label } = props\n const { hostId } = useContext(DatasetRecordSiteContext)\n const { pickMedia } = useMediaPicker()\n const value = valueOf(props)\n const preview = resolveMediaSrc(value.trim(), { hostId })\n // A stored https link opens on the link field, so it can be read and edited.\n const [linkOpen, setLinkOpen] = useState(() => /^https:\\/\\//i.test(value) || !pickMedia)\n\n const choose = async () => {\n const media = await pickMedia?.({ kind: 'image' })\n if (!media) return\n // The asset by identity when the picker names it, else read back out of\n // the CDN path it handed over; a library with neither gives its address.\n onChange(\n formatMediaRef(media.mediaScope, media.mediaId) ??\n mediaNodeSrc({ cdnPath: media.url, url: media.url }) ??\n media.url,\n )\n }\n\n return (\n <Stack spacing={1}>\n {label ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1.5} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n {preview ? (\n <Box\n component=\"img\"\n src={preview}\n alt=\"\"\n sx={{\n width: 96,\n height: 72,\n objectFit: 'cover',\n borderRadius: 1,\n border: 1,\n borderColor: 'divider',\n bgcolor: 'action.hover',\n }}\n />\n ) : (\n <Stack\n sx={{\n width: 96,\n height: 72,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: 1,\n border: 1,\n borderStyle: 'dashed',\n borderColor: 'divider',\n color: 'text.secondary',\n }}\n >\n <MdiIcon path={mdiImageOutline.path} size={1} />\n </Stack>\n )}\n {pickMedia ? (\n <Button size=\"small\" variant=\"outlined\" disabled={disabled} onClick={choose}>\n {value ? 'Change photo' : 'Choose from media'}\n </Button>\n ) : null}\n {value ? (\n <Button size=\"small\" color=\"error\" disabled={disabled} onClick={() => onChange('')}>\n {'Remove'}\n </Button>\n ) : null}\n </Stack>\n {linkOpen ? (\n <TextField\n size=\"small\"\n label=\"Photo link\"\n placeholder=\"https://\"\n disabled={disabled}\n // A picked photo is a reference, not a link: the field stays empty\n // for it rather than showing a value nobody should edit by hand.\n value={/^https:\\/\\//i.test(value) || !value ? value : ''}\n onChange={(event) => onChange(event.target.value.trim())}\n helperText=\"A photo hosted on another website, starting with https://\"\n />\n ) : (\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"caption\"\n underline=\"hover\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={disabled}\n onClick={() => setLinkOpen(true)}\n >\n {'Use a link instead'}\n </Link>\n )}\n </Stack>\n )\n}\nDatasetImageFieldInput.displayName = 'DatasetImageFieldInput'\n\n/** The Page field where no site is open: it names what is held and where to choose. */\nfunction PageFieldWithoutSite(props: { label?: string; chosen: boolean }) {\n return (\n <Stack spacing={0.5}>\n <TextField size=\"small\" label={props.label} value={props.chosen ? 'A page is chosen' : 'No page chosen'} disabled />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Pages belong to a site. Open this dataset from that site’s Data page to choose one.'}\n </Typography>\n </Stack>\n )\n}\nPageFieldWithoutSite.displayName = 'PageFieldWithoutSite'\n\n/**\n * A record's page: one of the site's pages, picked by name. Stored as the\n * page's link reference, so a Button or Link bound to it follows the page to\n * its current address.\n */\nexport function DatasetPageFieldInput(props: CustomFieldInputProps) {\n const { onChange, disabled, label } = props\n const { hostId } = useContext(DatasetRecordSiteContext)\n const firestore = useFirestore()\n const value = valueOf(props)\n const selected = datasetPageScreenId(value) ?? ''\n const { data: screenDocs, status } = useFirestoreCollection<any>(\n () =>\n hostId\n ? query(collection(firestore, 'hosts', hostId, 'screens'), limit(PAGE_LIST_LIMIT))\n : null,\n [firestore, hostId],\n { idField: '$id' },\n )\n const pages = useMemo(\n () =>\n [...(screenDocs ?? [])]\n // Pages a visitor can open: not an email design, not a page group.\n .filter((screen: any) => !screen.deletedAt && screen.kind !== SCREEN_KIND_EMAIL && !isScreenGroup(screen))\n .map((screen: any) => ({\n id: String(screen.$id),\n name: String(screen.displayName ?? screen.name ?? screen.$id),\n }))\n .sort((a, b) => a.name.localeCompare(b.name)),\n [screenDocs],\n )\n const known = !selected || pages.some((page) => page.id === selected)\n\n if (!hostId) return <PageFieldWithoutSite label={label} chosen={Boolean(selected)} />\n\n return (\n <TextField\n size=\"small\"\n select\n label={label}\n disabled={disabled}\n value={selected}\n onChange={(event) => onChange(event.target.value ? datasetPageValue(event.target.value) : '')}\n helperText={status === 'loading' && !screenDocs ? 'Loading pages…' : undefined}\n >\n <MenuItem value=\"\">{'No page'}</MenuItem>\n {pages.map((page) => (\n <MenuItem key={page.id} value={page.id}>\n {page.name}\n </MenuItem>\n ))}\n {/* A page that was deleted, or is not in this site, is named rather than shown as empty. */}\n {known ? null : (\n <MenuItem value={selected} disabled>\n {'⚠ A page this site no longer has'}\n </MenuItem>\n )}\n </TextField>\n )\n}\nDatasetPageFieldInput.displayName = 'DatasetPageFieldInput'\n"],"names":["isScreenGroup","useMediaPicker","formatMediaRef","mediaNodeSrc","resolveMediaSrc","SCREEN_KIND_EMAIL","mdiImageOutline","MdiIcon","useFirestore","useFirestoreCollection","Box","Button","Link","MenuItem","Stack","TextField","Typography","collection","limit","query","createContext","useContext","useMemo","useState","datasetPageScreenId","datasetPageValue","DatasetRecordSiteContext","displayName","PAGE_LIST_LIMIT","valueOf","props","value","DatasetImageFieldInput","onChange","disabled","label","hostId","pickMedia","preview","trim","linkOpen","setLinkOpen","test","choose","media","kind","mediaScope","mediaId","cdnPath","url","spacing","variant","color","direction","useFlexGap","sx","alignItems","flexWrap","component","src","alt","width","height","objectFit","borderRadius","border","borderColor","bgcolor","justifyContent","borderStyle","path","size","onClick","placeholder","event","target","helperText","type","underline","alignSelf","PageFieldWithoutSite","chosen","DatasetPageFieldInput","firestore","selected","data","screenDocs","status","idField","pages","filter","screen","deletedAt","map","id","String","$id","name","sort","a","b","localeCompare","known","some","page","Boolean","select","undefined"],"mappings":"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"}
|