@theoremjs/react 0.3.4-next.596.g481e1de → 0.3.4-next.600.gb15be9a
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 +3 -3
- package/ui/ShapedData.js +47 -2
- package/ui/labels.d.ts +10 -0
- package/ui/labels.js +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theoremjs/react",
|
|
3
|
-
"version": "0.3.4-next.
|
|
3
|
+
"version": "0.3.4-next.600.gb15be9a",
|
|
4
4
|
"description": "React bindings for Theorem: headless hooks + transport, an Astryx chat UI, and a Web-standard server handler.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@stylexjs/stylex": "0.19.0",
|
|
61
61
|
"react": "^19.0.0",
|
|
62
62
|
"react-dom": "^19.0.0",
|
|
63
|
-
"@theoremjs/agents": "^0.3.4-next.
|
|
64
|
-
"@theoremjs/web": "^0.3.4-next.
|
|
63
|
+
"@theoremjs/agents": "^0.3.4-next.600.gb15be9a",
|
|
64
|
+
"@theoremjs/web": "^0.3.4-next.600.gb15be9a"
|
|
65
65
|
},
|
|
66
66
|
"peerDependenciesMeta": {
|
|
67
67
|
"@astryxdesign/core": {
|
package/ui/ShapedData.js
CHANGED
|
@@ -3,6 +3,7 @@ import { CodeBlock } from '@astryxdesign/core/CodeBlock';
|
|
|
3
3
|
import { Collapsible, CollapsibleGroup } from '@astryxdesign/core/Collapsible';
|
|
4
4
|
import { HStack } from '@astryxdesign/core/HStack';
|
|
5
5
|
import { useLocale } from '@astryxdesign/core/i18n';
|
|
6
|
+
import { useLightbox } from '@astryxdesign/core/Lightbox';
|
|
6
7
|
import { Link } from '@astryxdesign/core/Link';
|
|
7
8
|
import { List, ListItem } from '@astryxdesign/core/List';
|
|
8
9
|
import { Markdown } from '@astryxdesign/core/Markdown';
|
|
@@ -10,14 +11,16 @@ import { MetadataList, MetadataListItem } from '@astryxdesign/core/MetadataList'
|
|
|
10
11
|
import { SegmentedControl, SegmentedControlItem } from '@astryxdesign/core/SegmentedControl';
|
|
11
12
|
import { Table } from '@astryxdesign/core/Table';
|
|
12
13
|
import { Text } from '@astryxdesign/core/Text';
|
|
14
|
+
import { Thumbnail } from '@astryxdesign/core/Thumbnail';
|
|
13
15
|
import { Token } from '@astryxdesign/core/Token';
|
|
14
16
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
15
|
-
import { fieldLabel, fieldReading, isPlain, isProse, isRow, json, MAX_ROWS, plainReading, rowHeading, shapeOf, splitFields, unpacked, withUnit, } from '@theoremjs/web/client';
|
|
17
|
+
import { fieldLabel, fieldReading, isPlain, isProse, isRow, json, layoutChars, MAX_ROWS, plainReading, rowHeading, shapeOf, splitFields, unpacked, withUnit, } from '@theoremjs/web/client';
|
|
16
18
|
import { Component, useMemo, useState } from 'react';
|
|
17
19
|
import { Arrive, useArrive } from "./arrive.js";
|
|
18
20
|
import { useLabels } from "./labels-provider.js";
|
|
19
21
|
import { useMarkdownPlugins } from "./markdown-plugins.js";
|
|
20
22
|
import { keyedByContent } from "./row-keys.js";
|
|
23
|
+
import { VoiceNote } from "./VoiceNote.js";
|
|
21
24
|
/** Each way a plain value reads, drawn. */
|
|
22
25
|
function ProseReading({ text }) {
|
|
23
26
|
return (_jsx(Markdown, { density: "compact", headingLevelStart: 4, plugins: useMarkdownPlugins(), children: text }));
|
|
@@ -128,6 +131,45 @@ function Fields({ row, depth, units }) {
|
|
|
128
131
|
// why: The top level's first section starts open: it's usually the payload. The rest wait to be asked for.
|
|
129
132
|
defaultValue: depth === 0 ? sections.slice(0, 1).map((shown) => shown.key) : [], children: sections.map((shown) => depth === 0 ? (_jsx(ArrivingFieldSection, { shown: shown, depth: depth }, shown.key)) : (_jsx(FieldSection, { shown: shown, depth: depth }, shown.key))) })), depth === 0 && fields ? _jsx(Arrive, { children: fields }) : null] }));
|
|
130
133
|
}
|
|
134
|
+
/** A media part's source: its base64 `data` as a data URL, or its `uri`. */
|
|
135
|
+
function partSrc(part) {
|
|
136
|
+
return 'uri' in part ? part.uri : `data:${part.mimeType};base64,${part.data}`;
|
|
137
|
+
}
|
|
138
|
+
function viewable(part) {
|
|
139
|
+
return part.type === 'image' || part.type === 'video';
|
|
140
|
+
}
|
|
141
|
+
/** A list of parts in its own order; every image and video opens in one Lightbox, so the arrows walk them all. */
|
|
142
|
+
function Parts({ parts }) {
|
|
143
|
+
const t = useLabels();
|
|
144
|
+
const shown = parts.slice(0, MAX_ROWS);
|
|
145
|
+
const media = [];
|
|
146
|
+
/** Each viewable part's place in the Lightbox, by its place in the list. */
|
|
147
|
+
const slides = new Map();
|
|
148
|
+
shown.forEach((part, index) => {
|
|
149
|
+
if (!viewable(part))
|
|
150
|
+
return;
|
|
151
|
+
slides.set(index, media.length);
|
|
152
|
+
const alt = t(part.type === 'image' ? '@theorem.data.image' : '@theorem.data.video', {
|
|
153
|
+
index: String(media.length + 1),
|
|
154
|
+
});
|
|
155
|
+
media.push({ src: partSrc(part), alt, type: part.type });
|
|
156
|
+
});
|
|
157
|
+
const lightbox = useLightbox({ media, hasZoom: true });
|
|
158
|
+
return (_jsxs(VStack, { gap: 2, children: [shown.map((part, index) => {
|
|
159
|
+
const key = String(index);
|
|
160
|
+
if (part.type === 'text')
|
|
161
|
+
return _jsx(ProseReading, { text: part.text }, key);
|
|
162
|
+
const slide = slides.get(index);
|
|
163
|
+
const item = slide === undefined ? undefined : media[slide];
|
|
164
|
+
if (slide !== undefined && item) {
|
|
165
|
+
return (_jsx(Thumbnail, { src: part.type === 'image' ? item.src : undefined, alt: item.alt, label: item.alt, onClick: () => lightbox.open(slide) }, key));
|
|
166
|
+
}
|
|
167
|
+
if (part.type === 'audio') {
|
|
168
|
+
return _jsx(VoiceNote, { src: partSrc(part), mimeType: part.mimeType }, key);
|
|
169
|
+
}
|
|
170
|
+
return _jsx(Token, { label: part.mimeType, size: "sm" }, key);
|
|
171
|
+
}), _jsx(More, { total: parts.length }), lightbox.element] }));
|
|
172
|
+
}
|
|
131
173
|
/** Each shape a value takes, drawn. */
|
|
132
174
|
const SHAPES = {
|
|
133
175
|
json: (_, { value }) => _jsx(Json, { text: json(value) }),
|
|
@@ -138,6 +180,7 @@ const SHAPES = {
|
|
|
138
180
|
table: ({ columns, rows }, { units }) => (_jsx(DataTable, { columns: columns, rows: rows, units: units })),
|
|
139
181
|
rows: ({ items }, { depth }) => _jsx(Rows, { items: items, depth: depth }),
|
|
140
182
|
fields: ({ row }, { depth, units }) => _jsx(Fields, { row: row, depth: depth, units: units }),
|
|
183
|
+
parts: ({ parts }) => _jsx(Parts, { parts: parts }),
|
|
141
184
|
};
|
|
142
185
|
function Node({ value: raw, depth, units, }) {
|
|
143
186
|
const t = useLabels();
|
|
@@ -182,7 +225,9 @@ export function ShapedData({ value: raw, title, lead, ledKeys = [] }) {
|
|
|
182
225
|
const left = Object.entries(value).filter(([key]) => !ledKeys.includes(key));
|
|
183
226
|
return left.length ? Object.fromEntries(left) : undefined;
|
|
184
227
|
}, [value, ledKeys]);
|
|
185
|
-
|
|
228
|
+
// why: Base64 media draws as a picture or a player, not as text, so only the rest counts toward the limit.
|
|
229
|
+
const tooLarge = useMemo(() => text.length > LARGE_JSON_CHARS && layoutChars(value) > LARGE_JSON_CHARS, [text, value]);
|
|
230
|
+
if (tooLarge) {
|
|
186
231
|
const size = new Intl.NumberFormat(locale, {
|
|
187
232
|
style: 'unit',
|
|
188
233
|
unit: 'kilobyte',
|
package/ui/labels.d.ts
CHANGED
|
@@ -280,6 +280,16 @@ export declare const THEOREM_UI_CATALOG: {
|
|
|
280
280
|
readonly description: "Rows past the shown ones, left to the JSON view.";
|
|
281
281
|
readonly params: readonly ["count"];
|
|
282
282
|
};
|
|
283
|
+
readonly '@theorem.data.image': {
|
|
284
|
+
readonly defaultMessage: "Image {index}";
|
|
285
|
+
readonly description: "An image in tool data, named by its place among the images and videos.";
|
|
286
|
+
readonly params: readonly ["index"];
|
|
287
|
+
};
|
|
288
|
+
readonly '@theorem.data.video': {
|
|
289
|
+
readonly defaultMessage: "Video {index}";
|
|
290
|
+
readonly description: "A video in tool data, named by its place among the images and videos.";
|
|
291
|
+
readonly params: readonly ["index"];
|
|
292
|
+
};
|
|
283
293
|
readonly '@theorem.data.large': {
|
|
284
294
|
readonly defaultMessage: "Too large to lay out — showing the first {size} of its JSON";
|
|
285
295
|
readonly description: "A payload too big to draw by its shape, shown as the start of its JSON.";
|
package/ui/labels.js
CHANGED
|
@@ -255,6 +255,16 @@ export const THEOREM_UI_CATALOG = {
|
|
|
255
255
|
description: 'Rows past the shown ones, left to the JSON view.',
|
|
256
256
|
params: ['count'],
|
|
257
257
|
},
|
|
258
|
+
'@theorem.data.image': {
|
|
259
|
+
defaultMessage: 'Image {index}',
|
|
260
|
+
description: 'An image in tool data, named by its place among the images and videos.',
|
|
261
|
+
params: ['index'],
|
|
262
|
+
},
|
|
263
|
+
'@theorem.data.video': {
|
|
264
|
+
defaultMessage: 'Video {index}',
|
|
265
|
+
description: 'A video in tool data, named by its place among the images and videos.',
|
|
266
|
+
params: ['index'],
|
|
267
|
+
},
|
|
258
268
|
'@theorem.data.large': {
|
|
259
269
|
defaultMessage: 'Too large to lay out — showing the first {size} of its JSON',
|
|
260
270
|
description: 'A payload too big to draw by its shape, shown as the start of its JSON.',
|