create-ncblock 0.0.21 → 0.0.23
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 +1 -1
- package/scripts/scaffold-assets/AGENTS.md +1 -1
- package/sdk-version.json +1 -1
- package/templates/debug/README.md +1 -1
- package/templates/debug/src/index.tsx +187 -20
- package/templates/radar-chart/README.md +1 -1
- package/templates/radar-chart/src/index.tsx +4 -3
- package/templates/table-view/README.md +1 -1
- package/templates/table-view/src/index.tsx +20 -19
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ Hooks at a glance:
|
|
|
22
22
|
- `useCustomBlockContext()` — `{ customBlockId, parent, page }`.
|
|
23
23
|
- `useTheme()` — `"light" | "dark"`.
|
|
24
24
|
- `useDataSource(key, initialLimit?)` — `{ items, isLoading, hasMore, fetchMore, error }`.
|
|
25
|
-
- `
|
|
25
|
+
- `useManifest()` — the declared manifest: data-source keys plus their property declarations.
|
|
26
26
|
- `pages.create(input)` — creates a page; pass `parent: { type: "data_source_key", key }` to target the block's wired data source.
|
|
27
27
|
|
|
28
28
|
`<NotionCustomBlock>` runs `useCustomBlockAutoResize` for you by default — no extra wiring needed. Pass `autoResize={false}` for full-bleed views. Full signatures live in `node_modules/ncblock/docs/*.md` and the `.d.ts` files.
|
package/sdk-version.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.0.
|
|
1
|
+
{"version":"0.0.21"}
|
|
@@ -32,5 +32,5 @@ The debug template includes a message log that intercepts and displays all incom
|
|
|
32
32
|
|
|
33
33
|
- **`useCustomBlockContext()`** -- returns `{ customBlockId, parent, page }` describing the block's location in the document tree
|
|
34
34
|
- **`useTheme()`** -- returns the host's current theme (`"light"` or `"dark"`)
|
|
35
|
-
- **`
|
|
35
|
+
- **`useManifest()`** -- returns the declared manifest (data-source keys + property declarations)
|
|
36
36
|
- **`useDataSource(key)`** -- returns `{ items, collectionSchema, propertySchemasById, propertySchemasByKey, isLoading, hasMore, fetchMore, error }`. Each `item` has `{ id, propertiesById, propertiesByKey }`. The four built-ins (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`) appear in `propertiesById` / `propertySchemasById`, never in the `*ByKey` views.
|
|
@@ -2,14 +2,13 @@ import {
|
|
|
2
2
|
type NotionCreatePagePosition,
|
|
3
3
|
NotionCustomBlock,
|
|
4
4
|
type NotionCustomBlockContext,
|
|
5
|
-
type NotionDataSource,
|
|
6
5
|
type NotionDataSourceId,
|
|
7
6
|
type NotionPage,
|
|
8
7
|
type NotionPageId,
|
|
9
8
|
pages,
|
|
10
9
|
useCurrentUser,
|
|
11
10
|
useCustomBlockContext,
|
|
12
|
-
|
|
11
|
+
useManifest,
|
|
13
12
|
useTheme,
|
|
14
13
|
} from "ncblock"
|
|
15
14
|
import React, {
|
|
@@ -33,6 +32,11 @@ type LogEntry = {
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
type ThemeOverride = "host" | "light" | "dark"
|
|
35
|
+
type NotionInitFixture =
|
|
36
|
+
| "normal"
|
|
37
|
+
| "no-ready"
|
|
38
|
+
| "invalid-ready"
|
|
39
|
+
| "invalid-manifest"
|
|
36
40
|
type TrackedPage = {
|
|
37
41
|
page: NotionPage
|
|
38
42
|
draftTitle: string
|
|
@@ -45,12 +49,14 @@ const labelClass =
|
|
|
45
49
|
"text-[10px] font-semibold uppercase tracking-[0.06em] text-(--muted)"
|
|
46
50
|
const metaTextClass =
|
|
47
51
|
"font-mono text-[11px] font-normal normal-case tracking-normal text-(--muted)"
|
|
52
|
+
const HOST_NO_READY_ERROR_DELAY_SECONDS = 5
|
|
48
53
|
|
|
49
54
|
function App() {
|
|
50
55
|
const ctx = useCustomBlockContext()
|
|
51
56
|
const currentUser = useCurrentUser()
|
|
52
57
|
const hostThemeValue = useTheme()
|
|
53
|
-
const
|
|
58
|
+
const manifest = useManifest()
|
|
59
|
+
const dataSourceKeys = Object.keys(manifest?.dataSources ?? {})
|
|
54
60
|
const [isPaused, setIsPaused] = useState(false)
|
|
55
61
|
const { log, initData, send, clearLog } = useMessageLog(isPaused)
|
|
56
62
|
const [themeOverride, setThemeOverride] = useState<ThemeOverride>("host")
|
|
@@ -81,7 +87,7 @@ function App() {
|
|
|
81
87
|
parent: ctx?.parent,
|
|
82
88
|
page: ctx?.page,
|
|
83
89
|
currentUser,
|
|
84
|
-
|
|
90
|
+
manifest,
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
const metadataRows: Array<[string, string]> = [
|
|
@@ -174,7 +180,7 @@ function App() {
|
|
|
174
180
|
</CollapsibleCard>
|
|
175
181
|
|
|
176
182
|
{/* Create page */}
|
|
177
|
-
<CreatePageCard context={ctx}
|
|
183
|
+
<CreatePageCard context={ctx} dataSourceKeys={dataSourceKeys} />
|
|
178
184
|
|
|
179
185
|
{/* Send message */}
|
|
180
186
|
<SendMessageCard onSend={send} />
|
|
@@ -198,6 +204,162 @@ function App() {
|
|
|
198
204
|
)
|
|
199
205
|
}
|
|
200
206
|
|
|
207
|
+
function DevOnlyNoReadyFixture() {
|
|
208
|
+
return (
|
|
209
|
+
<div className="min-h-screen bg-(--app-bg) p-8 text-(--foreground)">
|
|
210
|
+
<div className={cardClass}>
|
|
211
|
+
<div className={labelClass}>Local init fixture</div>
|
|
212
|
+
<h1 className="mt-2 text-[24px] font-semibold">No ready message</h1>
|
|
213
|
+
<p className="mt-2 text-sm leading-6 text-(--muted)">
|
|
214
|
+
This dev-only fixture intentionally skips the SDK provider, so the
|
|
215
|
+
sandbox loads but never sends the initial <code>ready</code> message.
|
|
216
|
+
Use it only for local manual testing of Notion host init error UI.
|
|
217
|
+
</p>
|
|
218
|
+
<DevOnlyHostErrorExpectation>
|
|
219
|
+
This page is the pre-error fixture, not the Notion error state. In the
|
|
220
|
+
Notion host, wait about {HOST_NO_READY_ERROR_DELAY_SECONDS} seconds
|
|
221
|
+
after the iframe loads; the host-owned{" "}
|
|
222
|
+
<span className="font-mono">Custom block didn't connect</span> error
|
|
223
|
+
should then cover this page.
|
|
224
|
+
</DevOnlyHostErrorExpectation>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function DevOnlyInvalidReadyFixture() {
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
if (window.parent === window) {
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Dev-only fixture for local manual testing of Notion host init error UI.
|
|
237
|
+
// This intentionally bypasses the SDK so the host receives a malformed
|
|
238
|
+
// ready message that is missing the manifest and protocol fields.
|
|
239
|
+
window.parent.postMessage({ type: "ready" }, "*")
|
|
240
|
+
}, [])
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<div className="min-h-screen bg-(--app-bg) p-8 text-(--foreground)">
|
|
244
|
+
<div className={cardClass}>
|
|
245
|
+
<div className={labelClass}>Local init fixture</div>
|
|
246
|
+
<h1 className="mt-2 text-[24px] font-semibold">
|
|
247
|
+
Invalid ready message
|
|
248
|
+
</h1>
|
|
249
|
+
<p className="mt-2 text-sm leading-6 text-(--muted)">
|
|
250
|
+
This dev-only fixture posts malformed{" "}
|
|
251
|
+
<code>{'{ type: "ready" }'}</code> to the parent window and does not
|
|
252
|
+
mount the SDK provider. Use it only for local manual testing of Notion
|
|
253
|
+
host init error UI.
|
|
254
|
+
</p>
|
|
255
|
+
<DevOnlyHostErrorExpectation>
|
|
256
|
+
This page is the pre-error fixture, not the Notion error state. In the
|
|
257
|
+
Notion host, the host-owned invalid setup message error should cover
|
|
258
|
+
this page as soon as Notion processes the malformed <code>ready</code>{" "}
|
|
259
|
+
message.
|
|
260
|
+
</DevOnlyHostErrorExpectation>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function DevOnlyInvalidManifestFixture() {
|
|
267
|
+
useEffect(() => {
|
|
268
|
+
if (window.parent === window) {
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Dev-only fixture for local manual testing of Notion host init error UI.
|
|
273
|
+
// This intentionally bypasses the SDK so the host receives a ready message
|
|
274
|
+
// with a manifest schema the bridge validator must reject.
|
|
275
|
+
window.parent.postMessage(
|
|
276
|
+
{
|
|
277
|
+
type: "ready",
|
|
278
|
+
bridgeProtocolVersion: 1,
|
|
279
|
+
manifest: {
|
|
280
|
+
version: 1,
|
|
281
|
+
dataSources: {
|
|
282
|
+
default: {
|
|
283
|
+
name: "Invalid fixture data source",
|
|
284
|
+
properties: {
|
|
285
|
+
unsupportedProperty: {
|
|
286
|
+
name: "Unsupported property",
|
|
287
|
+
type: "unsupported_manual_fixture_property_type",
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
"*",
|
|
295
|
+
)
|
|
296
|
+
}, [])
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<div className="min-h-screen bg-(--app-bg) p-8 text-(--foreground)">
|
|
300
|
+
<div className={cardClass}>
|
|
301
|
+
<div className={labelClass}>Local init fixture</div>
|
|
302
|
+
<h1 className="mt-2 text-[24px] font-semibold">Invalid manifest</h1>
|
|
303
|
+
<p className="mt-2 text-sm leading-6 text-(--muted)">
|
|
304
|
+
This dev-only fixture posts a <code>ready</code> message with an
|
|
305
|
+
unsupported manifest property type and does not mount the SDK
|
|
306
|
+
provider. Use it only for local manual testing of Notion host init
|
|
307
|
+
error UI.
|
|
308
|
+
</p>
|
|
309
|
+
<DevOnlyHostErrorExpectation>
|
|
310
|
+
This page is the pre-error fixture, not the Notion error state. In the
|
|
311
|
+
Notion host, the host-owned invalid manifest error should cover this
|
|
312
|
+
page as soon as Notion validates the <code>ready</code> message.
|
|
313
|
+
</DevOnlyHostErrorExpectation>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function DevOnlyHostErrorExpectation(props: { children: React.ReactNode }) {
|
|
320
|
+
return (
|
|
321
|
+
<div className="mt-3 rounded-md border border-(--border) bg-(--app-bg) px-3 py-2 text-sm leading-6 text-(--foreground)">
|
|
322
|
+
<span className="font-semibold">Expected host behavior: </span>
|
|
323
|
+
{props.children}
|
|
324
|
+
</div>
|
|
325
|
+
)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function DebugInitErrorFallback(error: Error) {
|
|
329
|
+
return (
|
|
330
|
+
<div className="min-h-screen bg-(--app-bg) p-8 text-(--foreground)">
|
|
331
|
+
<div className={cardClass} role="alert">
|
|
332
|
+
<div className={labelClass}>Custom block init</div>
|
|
333
|
+
<h1 className="mt-2 text-[24px] font-semibold">
|
|
334
|
+
Couldn't connect to Notion
|
|
335
|
+
</h1>
|
|
336
|
+
<p className="mt-2 text-sm leading-6 text-(--muted)">
|
|
337
|
+
The debug template waited for the host handshake, but it did not
|
|
338
|
+
complete. This fallback is only for local manual testing; host-owned
|
|
339
|
+
error UI should normally cover known Notion init failures first.
|
|
340
|
+
</p>
|
|
341
|
+
<pre className="mt-3 overflow-auto rounded-md border border-(--border) bg-(--app-bg) p-3 font-mono text-[11px] text-(--muted)">
|
|
342
|
+
{error.message}
|
|
343
|
+
</pre>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function getNotionInitFixture(): NotionInitFixture {
|
|
350
|
+
const fixture = new URLSearchParams(window.location.search).get(
|
|
351
|
+
"notionInitFixture",
|
|
352
|
+
)
|
|
353
|
+
if (
|
|
354
|
+
fixture === "no-ready" ||
|
|
355
|
+
fixture === "invalid-ready" ||
|
|
356
|
+
fixture === "invalid-manifest"
|
|
357
|
+
) {
|
|
358
|
+
return fixture
|
|
359
|
+
}
|
|
360
|
+
return "normal"
|
|
361
|
+
}
|
|
362
|
+
|
|
201
363
|
// --- Hooks ---
|
|
202
364
|
|
|
203
365
|
function useMessageLog(isPaused: boolean) {
|
|
@@ -1448,7 +1610,7 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
|
1448
1610
|
|
|
1449
1611
|
type CreatePageCardProps = {
|
|
1450
1612
|
context: NotionCustomBlockContext | undefined
|
|
1451
|
-
|
|
1613
|
+
dataSourceKeys: string[]
|
|
1452
1614
|
}
|
|
1453
1615
|
|
|
1454
1616
|
type CreatePagePreset = {
|
|
@@ -1576,7 +1738,7 @@ function pageIconFromInput(raw: string): NotionPage["icon"] | undefined {
|
|
|
1576
1738
|
* SDK resolve the key to the configured data source ID locally before sending the request to the
|
|
1577
1739
|
* Notion host.
|
|
1578
1740
|
*/
|
|
1579
|
-
function CreatePageCard({ context,
|
|
1741
|
+
function CreatePageCard({ context, dataSourceKeys }: CreatePageCardProps) {
|
|
1580
1742
|
const [status, setStatus] = useState<string | null>(null)
|
|
1581
1743
|
const [busy, setBusy] = useState(false)
|
|
1582
1744
|
const [dataSourceId, setDataSourceId] = useState("")
|
|
@@ -1813,27 +1975,23 @@ function CreatePageCard({ context, dataSources }: CreatePageCardProps) {
|
|
|
1813
1975
|
Create in data source
|
|
1814
1976
|
</button>
|
|
1815
1977
|
</div>
|
|
1816
|
-
{
|
|
1978
|
+
{dataSourceKeys.length > 0 && (
|
|
1817
1979
|
<div className="mb-2 flex flex-wrap items-center gap-1.5">
|
|
1818
1980
|
<span className="font-mono text-[11px] text-(--muted)">
|
|
1819
1981
|
by data source key:
|
|
1820
1982
|
</span>
|
|
1821
|
-
{
|
|
1983
|
+
{dataSourceKeys.map(key => (
|
|
1822
1984
|
<button
|
|
1823
|
-
key={
|
|
1985
|
+
key={key}
|
|
1824
1986
|
type="button"
|
|
1825
1987
|
disabled={disabled}
|
|
1826
|
-
title={
|
|
1827
|
-
source.collectionPointer !== undefined
|
|
1828
|
-
? `Create a page in data source ${source.collectionPointer.id} via key "${source.key}"`
|
|
1829
|
-
: `Key "${source.key}" has no collection pointer yet; request will resolve with an error`
|
|
1830
|
-
}
|
|
1988
|
+
title={`Create a page via data source key "${key}" (resolves to an error if the key is not mapped to a database yet)`}
|
|
1831
1989
|
className="rounded-md border border-(--border) bg-transparent px-2 py-1 font-mono text-[11px] text-(--muted) transition-colors hover:border-(--foreground)/20 hover:bg-(--hover-bg) hover:text-(--foreground) disabled:cursor-not-allowed disabled:opacity-50"
|
|
1832
1990
|
onClick={() => {
|
|
1833
|
-
void handleCreateForKey(
|
|
1991
|
+
void handleCreateForKey(key)
|
|
1834
1992
|
}}
|
|
1835
1993
|
>
|
|
1836
|
-
{
|
|
1994
|
+
{key}
|
|
1837
1995
|
</button>
|
|
1838
1996
|
))}
|
|
1839
1997
|
</div>
|
|
@@ -1960,10 +2118,19 @@ function CreatePageCard({ context, dataSources }: CreatePageCardProps) {
|
|
|
1960
2118
|
|
|
1961
2119
|
// --- Mount ---
|
|
1962
2120
|
|
|
2121
|
+
const notionInitFixture = getNotionInitFixture()
|
|
2122
|
+
|
|
1963
2123
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
1964
2124
|
<ErrorBoundary>
|
|
1965
|
-
<
|
|
1966
|
-
|
|
1967
|
-
|
|
2125
|
+
{notionInitFixture === "no-ready" && <DevOnlyNoReadyFixture />}
|
|
2126
|
+
{notionInitFixture === "invalid-ready" && <DevOnlyInvalidReadyFixture />}
|
|
2127
|
+
{notionInitFixture === "invalid-manifest" && (
|
|
2128
|
+
<DevOnlyInvalidManifestFixture />
|
|
2129
|
+
)}
|
|
2130
|
+
{notionInitFixture === "normal" && (
|
|
2131
|
+
<NotionCustomBlock errorFallback={DebugInitErrorFallback}>
|
|
2132
|
+
<App />
|
|
2133
|
+
</NotionCustomBlock>
|
|
2134
|
+
)}
|
|
1968
2135
|
</ErrorBoundary>,
|
|
1969
2136
|
)
|
|
@@ -51,5 +51,5 @@ If the mapped collection is missing any of those fields, the template shows a se
|
|
|
51
51
|
|
|
52
52
|
- **`useCustomBlockContext()`** -- returns `{ customBlockId, parent, page }` describing the block's location in the document tree
|
|
53
53
|
- **`useTheme()`** -- returns the host's current theme (`"light"` or `"dark"`)
|
|
54
|
-
- **`
|
|
54
|
+
- **`useManifest()`** -- returns the declared manifest (data-source keys + property declarations)
|
|
55
55
|
- **`useDataSource(key)`** -- returns `{ items, collectionSchema, propertySchemasById, propertySchemasByKey, isLoading, hasMore, fetchMore, error }`. Each `item` has `{ id, propertiesById, propertiesByKey }`. The four built-ins (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`) appear in `propertiesById` / `propertySchemasById`, never in the `*ByKey` views.
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
type NotionDataSourcePage,
|
|
4
4
|
type NotionPageId,
|
|
5
5
|
useDataSource,
|
|
6
|
-
|
|
6
|
+
useManifest,
|
|
7
7
|
useTheme,
|
|
8
8
|
} from "ncblock"
|
|
9
9
|
import React from "react"
|
|
@@ -445,8 +445,9 @@ function SetupHint(props: {
|
|
|
445
445
|
|
|
446
446
|
function App() {
|
|
447
447
|
const theme = useTheme()
|
|
448
|
-
const
|
|
449
|
-
const activeDataSourceKey =
|
|
448
|
+
const manifest = useManifest()
|
|
449
|
+
const activeDataSourceKey =
|
|
450
|
+
Object.keys(manifest?.dataSources ?? {})[0] ?? "default"
|
|
450
451
|
const query = useDataSource(activeDataSourceKey)
|
|
451
452
|
const colorTheme = theme === "dark" ? "dark" : "light"
|
|
452
453
|
const mappedAnalysis = analyzeRadarSchema(query.items)
|
|
@@ -39,5 +39,5 @@ related page is loaded or `-> uuid` when it is not.
|
|
|
39
39
|
|
|
40
40
|
- **`useCustomBlockContext()`** -- returns `{ customBlockId, parent, page }` describing the block's location in the document tree
|
|
41
41
|
- **`useTheme()`** -- returns the host's current theme (`"light"` or `"dark"`)
|
|
42
|
-
- **`
|
|
42
|
+
- **`useManifest()`** -- returns the declared manifest (data-source keys + property declarations)
|
|
43
43
|
- **`useDataSource(key)`** -- returns `{ items, collectionSchema, propertySchemasById, propertySchemasByKey, isLoading, hasMore, fetchMore, error }`. Each `item` has `{ id, propertiesById, propertiesByKey }`. The four built-ins (`created_time`, `last_edited_time`, `created_by`, `last_edited_by`) appear in `propertiesById` / `propertySchemasById`, never in the `*ByKey` views.
|
|
@@ -8,13 +8,12 @@ import {
|
|
|
8
8
|
} from "@tanstack/react-table"
|
|
9
9
|
import {
|
|
10
10
|
NotionCustomBlock,
|
|
11
|
-
type NotionDataSource,
|
|
12
11
|
type NotionPagePropertyInputMap,
|
|
13
12
|
type NotionPagePropertyInputValue,
|
|
14
13
|
type NotionPropertySchema,
|
|
15
14
|
pages,
|
|
16
15
|
useDataSource,
|
|
17
|
-
|
|
16
|
+
useManifest,
|
|
18
17
|
useTheme,
|
|
19
18
|
} from "ncblock"
|
|
20
19
|
import React from "react"
|
|
@@ -1004,7 +1003,7 @@ function DataWorkspace(props: {
|
|
|
1004
1003
|
onColumnOrderChange: (next: string[]) => void
|
|
1005
1004
|
onHiddenColumnsChange: (next: Set<string>) => void
|
|
1006
1005
|
activeDataSourceKey: string
|
|
1007
|
-
|
|
1006
|
+
dataSourceKeys: string[]
|
|
1008
1007
|
onSelectDataSource: (key: string) => void
|
|
1009
1008
|
hasMore: boolean
|
|
1010
1009
|
isLoading: boolean
|
|
@@ -1023,7 +1022,7 @@ function DataWorkspace(props: {
|
|
|
1023
1022
|
onColumnOrderChange,
|
|
1024
1023
|
onHiddenColumnsChange,
|
|
1025
1024
|
activeDataSourceKey,
|
|
1026
|
-
|
|
1025
|
+
dataSourceKeys,
|
|
1027
1026
|
onSelectDataSource,
|
|
1028
1027
|
hasMore,
|
|
1029
1028
|
isLoading,
|
|
@@ -1417,7 +1416,7 @@ function DataWorkspace(props: {
|
|
|
1417
1416
|
<span className="eyebrow ml-1.5">rows</span>
|
|
1418
1417
|
</div>
|
|
1419
1418
|
|
|
1420
|
-
{
|
|
1419
|
+
{dataSourceKeys.length > 1 ? (
|
|
1421
1420
|
<label className="block">
|
|
1422
1421
|
<span className="sr-only">Choose data source</span>
|
|
1423
1422
|
<select
|
|
@@ -1425,9 +1424,9 @@ function DataWorkspace(props: {
|
|
|
1425
1424
|
onChange={event => onSelectDataSource(event.target.value)}
|
|
1426
1425
|
className="field-control min-h-9 w-full rounded-md border border-(--line) bg-(--surface-bg) px-2 text-sm text-(--foreground) outline-none hover:border-(--line-strong)"
|
|
1427
1426
|
>
|
|
1428
|
-
{
|
|
1429
|
-
<option key={
|
|
1430
|
-
{
|
|
1427
|
+
{dataSourceKeys.map(key => (
|
|
1428
|
+
<option key={key} value={key}>
|
|
1429
|
+
{key}
|
|
1431
1430
|
</option>
|
|
1432
1431
|
))}
|
|
1433
1432
|
</select>
|
|
@@ -1682,30 +1681,32 @@ function DataWorkspace(props: {
|
|
|
1682
1681
|
|
|
1683
1682
|
function App() {
|
|
1684
1683
|
const theme = useTheme()
|
|
1685
|
-
const
|
|
1684
|
+
const manifest = useManifest()
|
|
1685
|
+
const dataSourceKeys = React.useMemo(
|
|
1686
|
+
() => Object.keys(manifest?.dataSources ?? {}),
|
|
1687
|
+
[manifest],
|
|
1688
|
+
)
|
|
1686
1689
|
const [activeDataSourceKey, setActiveDataSourceKey] = React.useState(
|
|
1687
|
-
|
|
1690
|
+
dataSourceKeys[0] ?? "default",
|
|
1688
1691
|
)
|
|
1689
1692
|
|
|
1690
1693
|
React.useEffect(() => {
|
|
1691
|
-
if (
|
|
1694
|
+
if (dataSourceKeys.length === 0) {
|
|
1692
1695
|
setActiveDataSourceKey("default")
|
|
1693
1696
|
return
|
|
1694
1697
|
}
|
|
1695
1698
|
|
|
1696
|
-
if (
|
|
1697
|
-
|
|
1698
|
-
) {
|
|
1699
|
-
setActiveDataSourceKey(dataSources[0].key)
|
|
1699
|
+
if (!dataSourceKeys.includes(activeDataSourceKey)) {
|
|
1700
|
+
setActiveDataSourceKey(dataSourceKeys[0])
|
|
1700
1701
|
}
|
|
1701
|
-
}, [activeDataSourceKey,
|
|
1702
|
+
}, [activeDataSourceKey, dataSourceKeys])
|
|
1702
1703
|
|
|
1703
|
-
const queryKey =
|
|
1704
|
+
const queryKey = dataSourceKeys.length === 0 ? "default" : activeDataSourceKey
|
|
1704
1705
|
const query = useDataSource(queryKey)
|
|
1705
1706
|
const colorTheme = theme === "dark" ? "dark" : "light"
|
|
1706
1707
|
|
|
1707
1708
|
const mappedItems = query.items as TableRow[]
|
|
1708
|
-
const isUsingFallbackData =
|
|
1709
|
+
const isUsingFallbackData = dataSourceKeys.length === 0
|
|
1709
1710
|
const isCollectionEmpty =
|
|
1710
1711
|
!isUsingFallbackData && !query.isLoading && mappedItems.length === 0
|
|
1711
1712
|
const items = isUsingFallbackData ? SAMPLE_ITEMS : mappedItems
|
|
@@ -1793,7 +1794,7 @@ function App() {
|
|
|
1793
1794
|
onColumnOrderChange={setColumnOrder}
|
|
1794
1795
|
onHiddenColumnsChange={setHiddenColumns}
|
|
1795
1796
|
activeDataSourceKey={queryKey}
|
|
1796
|
-
|
|
1797
|
+
dataSourceKeys={dataSourceKeys}
|
|
1797
1798
|
onSelectDataSource={setActiveDataSourceKey}
|
|
1798
1799
|
hasMore={query.hasMore}
|
|
1799
1800
|
isLoading={query.isLoading}
|