@tulipfarm/sprout 0.3.0 → 0.4.0
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/README.md +45 -40
- package/dist/components/accordion.d.ts +2 -2
- package/dist/components/accordion.js +9 -9
- package/dist/components/agent-fanout.d.ts +2 -2
- package/dist/components/agent-fanout.js +65 -87
- package/dist/components/alert-dialog.d.ts +6 -6
- package/dist/components/alert-dialog.js +20 -20
- package/dist/components/alert.d.ts +6 -6
- package/dist/components/alert.js +13 -13
- package/dist/components/aspect-ratio.d.ts +2 -2
- package/dist/components/aspect-ratio.js +2 -2
- package/dist/components/attachment.d.ts +17 -17
- package/dist/components/attachment.js +82 -115
- package/dist/components/avatar.d.ts +10 -10
- package/dist/components/avatar.js +14 -14
- package/dist/components/badge.d.ts +4 -4
- package/dist/components/badge.js +18 -18
- package/dist/components/board.d.ts +107 -0
- package/dist/components/board.js +640 -0
- package/dist/components/brand-icon.d.ts +6 -6
- package/dist/components/brand-icon.js +55 -63
- package/dist/components/breadcrumb.d.ts +11 -11
- package/dist/components/breadcrumb.js +16 -18
- package/dist/components/button-group.d.ts +6 -6
- package/dist/components/button-group.js +14 -14
- package/dist/components/button.d.ts +11 -11
- package/dist/components/button.js +46 -56
- package/dist/components/calendar.d.ts +5 -5
- package/dist/components/calendar.js +44 -53
- package/dist/components/canvas.d.ts +4 -4
- package/dist/components/canvas.js +39 -46
- package/dist/components/card.d.ts +6 -6
- package/dist/components/card.js +17 -17
- package/dist/components/carousel.d.ts +7 -7
- package/dist/components/carousel.js +57 -75
- package/dist/components/chart.d.ts +5 -112
- package/dist/components/chart.js +5 -172
- package/dist/components/chat-confirmation.d.ts +13 -11
- package/dist/components/chat-confirmation.js +32 -38
- package/dist/components/chat-input.d.ts +4 -4
- package/dist/components/chat-input.js +74 -81
- package/dist/components/chat-plan.d.ts +2 -2
- package/dist/components/chat-plan.js +15 -20
- package/dist/components/chat-sources.d.ts +8 -8
- package/dist/components/chat-sources.js +18 -27
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/checkbox.js +9 -9
- package/dist/components/choice-list.d.ts +2 -2
- package/dist/components/choice-list.js +16 -18
- package/dist/components/code-block.d.ts +5 -5
- package/dist/components/code-block.js +26 -26
- package/dist/components/collapsible.d.ts +1 -1
- package/dist/components/collapsible.js +7 -11
- package/dist/components/combobox.d.ts +3 -3
- package/dist/components/combobox.js +24 -24
- package/dist/components/command.d.ts +13 -13
- package/dist/components/command.js +61 -73
- package/dist/components/context-menu.d.ts +5 -5
- package/dist/components/context-menu.js +22 -22
- package/dist/components/dialog.d.ts +6 -6
- package/dist/components/dialog.js +23 -28
- package/dist/components/direction.d.ts +1 -1
- package/dist/components/direction.js +1 -1
- package/dist/components/dropdown-menu.d.ts +5 -5
- package/dist/components/dropdown-menu.js +20 -20
- package/dist/components/field.d.ts +11 -11
- package/dist/components/field.js +41 -61
- package/dist/components/file-explorer.d.ts +2 -2
- package/dist/components/file-explorer.js +89 -105
- package/dist/components/file-viewer.d.ts +10 -10
- package/dist/components/file-viewer.js +112 -140
- package/dist/components/form.d.ts +7 -7
- package/dist/components/form.js +24 -28
- package/dist/components/glass-banner.d.ts +3 -3
- package/dist/components/glass-banner.js +21 -24
- package/dist/components/gradient-banner.d.ts +5 -5
- package/dist/components/gradient-banner.js +13 -22
- package/dist/components/hover-card.d.ts +4 -4
- package/dist/components/hover-card.js +6 -6
- package/dist/components/input-group.d.ts +11 -11
- package/dist/components/input-group.js +22 -36
- package/dist/components/input-otp.d.ts +10 -10
- package/dist/components/input-otp.js +13 -15
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +7 -7
- package/dist/components/kbd.d.ts +3 -3
- package/dist/components/kbd.js +5 -5
- package/dist/components/label.d.ts +2 -2
- package/dist/components/label.js +2 -2
- package/dist/components/mention-list.d.ts +3 -3
- package/dist/components/mention-list.js +10 -10
- package/dist/components/menubar.d.ts +6 -6
- package/dist/components/menubar.js +22 -22
- package/dist/components/message-actions.d.ts +2 -2
- package/dist/components/message-actions.js +29 -31
- package/dist/components/message-response.d.ts +3 -3
- package/dist/components/message-response.js +18 -18
- package/dist/components/message-scroller.d.ts +12 -12
- package/dist/components/message-scroller.js +44 -56
- package/dist/components/message.d.ts +11 -11
- package/dist/components/message.js +25 -25
- package/dist/components/navigation-menu.d.ts +2 -2
- package/dist/components/navigation-menu.js +15 -15
- package/dist/components/notice-banner.d.ts +2 -2
- package/dist/components/notice-banner.js +2 -2
- package/dist/components/page-content.d.ts +2 -2
- package/dist/components/page-content.js +2 -2
- package/dist/components/page-header.d.ts +1 -1
- package/dist/components/page-header.js +16 -16
- package/dist/components/pagination.d.ts +9 -9
- package/dist/components/pagination.js +17 -17
- package/dist/components/plan-outline.d.ts +2 -2
- package/dist/components/plan-outline.js +43 -60
- package/dist/components/popover.d.ts +6 -6
- package/dist/components/popover.js +15 -15
- package/dist/components/presence-cursor.d.ts +2 -2
- package/dist/components/presence-cursor.js +3 -3
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/progress.js +8 -8
- package/dist/components/prompt-list.d.ts +3 -3
- package/dist/components/prompt-list.js +6 -6
- package/dist/components/questionnaire.d.ts +19 -19
- package/dist/components/questionnaire.js +183 -242
- package/dist/components/radio-group.d.ts +2 -2
- package/dist/components/radio-group.js +6 -6
- package/dist/components/reasoning-effort.d.ts +3 -3
- package/dist/components/reasoning-effort.js +9 -12
- package/dist/components/record-list.d.ts +66 -0
- package/dist/components/record-list.js +151 -0
- package/dist/components/resizable.d.ts +5 -5
- package/dist/components/resizable.js +76 -102
- package/dist/components/result-card.d.ts +10 -5
- package/dist/components/result-card.js +42 -53
- package/dist/components/result-notice.d.ts +18 -0
- package/dist/components/result-notice.js +29 -0
- package/dist/components/review-card.d.ts +6 -6
- package/dist/components/review-card.js +18 -18
- package/dist/components/rich-text-editor.d.ts +2 -2
- package/dist/components/rich-text-editor.js +56 -72
- package/dist/components/schema-diagram.d.ts +3 -3
- package/dist/components/schema-diagram.js +39 -53
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/scroll-area.js +10 -14
- package/dist/components/search-dialog.d.ts +6 -6
- package/dist/components/search-dialog.js +16 -16
- package/dist/components/search-field.d.ts +1 -1
- package/dist/components/search-field.js +11 -11
- package/dist/components/select.d.ts +3 -3
- package/dist/components/select.js +20 -20
- package/dist/components/selection-slot.d.ts +2 -2
- package/dist/components/selection-slot.js +6 -9
- package/dist/components/separator.d.ts +15 -15
- package/dist/components/separator.js +22 -27
- package/dist/components/settings-list.d.ts +3 -3
- package/dist/components/settings-list.js +5 -5
- package/dist/components/sheet.d.ts +6 -6
- package/dist/components/sheet.js +27 -27
- package/dist/components/sidebar.d.ts +22 -22
- package/dist/components/sidebar.js +57 -58
- package/dist/components/skeleton.d.ts +5 -5
- package/dist/components/skeleton.js +14 -21
- package/dist/components/slider.d.ts +2 -2
- package/dist/components/slider.js +6 -6
- package/dist/components/spinner.d.ts +5 -5
- package/dist/components/spinner.js +22 -26
- package/dist/components/sql-viewer.d.ts +14 -14
- package/dist/components/sql-viewer.js +99 -128
- package/dist/components/state-panel-samples.d.ts +2 -2
- package/dist/components/state-panel-samples.js +209 -245
- package/dist/components/state-panel.d.ts +2 -35
- package/dist/components/state-panel.js +2 -29
- package/dist/components/state-sample.d.ts +7 -7
- package/dist/components/state-sample.js +18 -31
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/switch.js +7 -7
- package/dist/components/table-nuqs.d.ts +2 -2
- package/dist/components/table-nuqs.js +11 -12
- package/dist/components/table.d.ts +4 -4
- package/dist/components/table.js +166 -235
- package/dist/components/tabs.d.ts +2 -2
- package/dist/components/tabs.js +12 -12
- package/dist/components/textarea.d.ts +2 -2
- package/dist/components/textarea.js +6 -8
- package/dist/components/timeline.d.ts +3 -0
- package/dist/components/timeline.js +3 -0
- package/dist/components/toast.d.ts +2 -2
- package/dist/components/toast.js +19 -25
- package/dist/components/toggle-group.d.ts +2 -2
- package/dist/components/toggle-group.js +18 -20
- package/dist/components/toggle.d.ts +2 -2
- package/dist/components/toggle.js +8 -8
- package/dist/components/tool-activity.d.ts +1 -1
- package/dist/components/tool-activity.js +83 -110
- package/dist/components/toolbar-status.d.ts +2 -2
- package/dist/components/toolbar-status.js +7 -7
- package/dist/components/tooltip.d.ts +5 -5
- package/dist/components/tooltip.js +12 -11
- package/dist/components/typed-text.d.ts +2 -2
- package/dist/components/typed-text.js +10 -12
- package/dist/components/view-switcher.d.ts +51 -0
- package/dist/components/view-switcher.js +114 -0
- package/dist/index.d.ts +98 -93
- package/dist/index.js +98 -93
- package/dist/internal/chart-kit.d.ts +142 -0
- package/dist/internal/chart-kit.js +200 -0
- package/dist/internal/charts/bars.d.ts +61 -0
- package/dist/internal/charts/bars.js +86 -0
- package/dist/internal/charts/lines.d.ts +67 -0
- package/dist/internal/charts/lines.js +70 -0
- package/dist/internal/charts/more.d.ts +77 -0
- package/dist/internal/charts/more.js +68 -0
- package/dist/internal/charts/parts.d.ts +54 -0
- package/dist/internal/charts/parts.js +82 -0
- package/dist/internal/charts/timeline-dates.d.ts +38 -0
- package/dist/internal/charts/timeline-dates.js +113 -0
- package/dist/internal/charts/timeline-window.d.ts +31 -0
- package/dist/internal/charts/timeline-window.js +26 -0
- package/dist/internal/charts/timeline.d.ts +72 -0
- package/dist/internal/charts/timeline.js +287 -0
- package/dist/internal/classes.d.ts +1 -1
- package/dist/internal/classes.js +7 -9
- package/dist/internal/code-tokens.d.ts +2 -2
- package/dist/internal/code-tokens.js +12 -12
- package/dist/internal/fieldset.d.ts +1 -1
- package/dist/internal/fieldset.js +6 -6
- package/dist/internal/file-kind.d.ts +1 -1
- package/dist/internal/file-kind.js +43 -51
- package/dist/internal/flow.d.ts +4 -4
- package/dist/internal/flow.js +20 -23
- package/dist/internal/languages.js +999 -999
- package/dist/internal/message-scroller.d.ts +3 -3
- package/dist/internal/message-scroller.js +64 -112
- package/dist/internal/pressable.d.ts +1 -1
- package/dist/internal/pressable.js +7 -7
- package/dist/internal/state-panel.d.ts +35 -0
- package/dist/internal/state-panel.js +22 -0
- package/dist/internal/syntax.d.ts +2 -2
- package/dist/internal/syntax.js +36 -47
- package/dist/styles/accordion.css +3 -5
- package/dist/styles/agent-fanout.css +64 -60
- package/dist/styles/alert-dialog.css +12 -26
- package/dist/styles/alert.css +10 -15
- package/dist/styles/attachment.css +51 -93
- package/dist/styles/avatar.css +17 -24
- package/dist/styles/badge.css +11 -36
- package/dist/styles/board.css +571 -0
- package/dist/styles/brand-icon.css +7 -8
- package/dist/styles/breadcrumb.css +4 -7
- package/dist/styles/button-group.css +8 -14
- package/dist/styles/button.css +22 -27
- package/dist/styles/calendar.css +24 -36
- package/dist/styles/canvas.css +2 -3
- package/dist/styles/card.css +7 -23
- package/dist/styles/carousel.css +11 -12
- package/dist/styles/chart.css +215 -31
- package/dist/styles/chat-input.css +15 -39
- package/dist/styles/chat-plan.css +3 -4
- package/dist/styles/chat-sources.css +3 -6
- package/dist/styles/checkbox.css +11 -25
- package/dist/styles/choice-list.css +17 -31
- package/dist/styles/code-block.css +11 -25
- package/dist/styles/collapsible.css +10 -15
- package/dist/styles/combobox.css +11 -15
- package/dist/styles/command.css +5 -8
- package/dist/styles/context-menu.css +2 -2
- package/dist/styles/dialog.css +5 -16
- package/dist/styles/dropdown-menu.css +2 -2
- package/dist/styles/field.css +15 -21
- package/dist/styles/file-explorer.css +19 -33
- package/dist/styles/file-viewer.css +13 -17
- package/dist/styles/form.css +3 -3
- package/dist/styles/glass-banner.css +12 -47
- package/dist/styles/gradient-banner.css +17 -35
- package/dist/styles/hover-card.css +9 -15
- package/dist/styles/index.css +116 -108
- package/dist/styles/input-group.css +25 -41
- package/dist/styles/input-otp.css +5 -6
- package/dist/styles/input.css +5 -5
- package/dist/styles/kbd.css +1 -1
- package/dist/styles/label.css +9 -30
- package/dist/styles/mention-list.css +6 -8
- package/dist/styles/menubar.css +5 -6
- package/dist/styles/message-actions.css +1 -1
- package/dist/styles/message-response.css +3 -5
- package/dist/styles/message-scroller.css +14 -21
- package/dist/styles/message.css +23 -60
- package/dist/styles/navigation-menu.css +14 -29
- package/dist/styles/notice-banner.css +1 -2
- package/dist/styles/page-header.css +7 -10
- package/dist/styles/pagination.css +1 -4
- package/dist/styles/plan-outline.css +20 -37
- package/dist/styles/popover.css +10 -19
- package/dist/styles/presence-cursor.css +1 -2
- package/dist/styles/progress.css +2 -4
- package/dist/styles/prompt-list.css +6 -17
- package/dist/styles/questionnaire.css +16 -40
- package/dist/styles/radio-group.css +12 -23
- package/dist/styles/reasoning-effort.css +8 -15
- package/dist/styles/record-list.css +243 -0
- package/dist/styles/resizable.css +10 -11
- package/dist/styles/result-card.css +17 -3
- package/dist/styles/result-notice.css +80 -0
- package/dist/styles/review-card.css +2 -3
- package/dist/styles/rich-text-editor.css +1 -2
- package/dist/styles/schema-diagram.css +12 -17
- package/dist/styles/scroll-area.css +8 -10
- package/dist/styles/search-dialog.css +1 -5
- package/dist/styles/search-field.css +1 -2
- package/dist/styles/select.css +7 -7
- package/dist/styles/selection-slot.css +1 -2
- package/dist/styles/separator.css +7 -8
- package/dist/styles/settings-list.css +1 -2
- package/dist/styles/sheet.css +40 -73
- package/dist/styles/sidebar.css +17 -21
- package/dist/styles/skeleton.css +9 -11
- package/dist/styles/slider.css +11 -21
- package/dist/styles/spinner.css +2 -2
- package/dist/styles/sql-viewer.css +29 -29
- package/dist/styles/state-panel.css +8 -9
- package/dist/styles/state-sample.css +20 -33
- package/dist/styles/switch.css +11 -18
- package/dist/styles/table.css +51 -77
- package/dist/styles/tabs.css +12 -16
- package/dist/styles/textarea.css +3 -3
- package/dist/styles/themes/chatgpt.css +11 -23
- package/dist/styles/themes/claude.css +12 -24
- package/dist/styles/themes/cloudflare.css +11 -23
- package/dist/styles/themes/discord.css +11 -23
- package/dist/styles/themes/github.css +11 -23
- package/dist/styles/themes/grok.css +11 -23
- package/dist/styles/themes/linear.css +12 -25
- package/dist/styles/themes/neutral.css +11 -23
- package/dist/styles/themes/notion.css +11 -23
- package/dist/styles/themes/perplexity.css +11 -23
- package/dist/styles/themes/raycast.css +11 -23
- package/dist/styles/themes/spotify.css +11 -23
- package/dist/styles/themes/stripe.css +11 -23
- package/dist/styles/themes/supabase.css +11 -23
- package/dist/styles/themes/tulipfarm.css +9 -10
- package/dist/styles/themes/vercel.css +12 -25
- package/dist/styles/timeline.css +276 -0
- package/dist/styles/toast.css +13 -20
- package/dist/styles/toggle-group.css +17 -23
- package/dist/styles/toggle.css +8 -8
- package/dist/styles/tool-activity.css +7 -13
- package/dist/styles/toolbar-status.css +2 -7
- package/dist/styles/tooltip.css +15 -21
- package/dist/styles/view-switcher.css +279 -0
- package/package.json +21 -1
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, Suspense, useContext, useEffect, useId, useMemo, useRef, useState, } from
|
|
3
|
-
import { ArrowUpRight } from
|
|
4
|
-
import { ChevronLeft } from
|
|
5
|
-
import { ChevronRight } from
|
|
6
|
-
import { Download } from
|
|
7
|
-
import { MoreH } from
|
|
8
|
-
import { SidebarRight } from
|
|
9
|
-
import { X } from
|
|
10
|
-
import { cn } from
|
|
11
|
-
import { fileKind } from
|
|
12
|
-
import { Button } from
|
|
13
|
-
import { CodeBlock, languageForFilename } from
|
|
14
|
-
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from
|
|
15
|
-
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from
|
|
16
|
-
import { Spinner } from
|
|
17
|
-
import { StatePanel } from
|
|
2
|
+
import { createContext, Suspense, useContext, useEffect, useId, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { ArrowUpRight } from 'reicon-react/icons/ArrowUpRight';
|
|
4
|
+
import { ChevronLeft } from 'reicon-react/icons/ChevronLeft';
|
|
5
|
+
import { ChevronRight } from 'reicon-react/icons/ChevronRight';
|
|
6
|
+
import { Download } from 'reicon-react/icons/Download';
|
|
7
|
+
import { MoreH } from 'reicon-react/icons/MoreH';
|
|
8
|
+
import { SidebarRight } from 'reicon-react/icons/SidebarRight';
|
|
9
|
+
import { X } from 'reicon-react/icons/X';
|
|
10
|
+
import { cn } from '../internal/classes.js';
|
|
11
|
+
import { fileKind } from '../internal/file-kind.js';
|
|
12
|
+
import { Button } from './button.js';
|
|
13
|
+
import { CodeBlock, languageForFilename } from './code-block.js';
|
|
14
|
+
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle, } from './dialog.js';
|
|
15
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from './dropdown-menu.js';
|
|
16
|
+
import { Spinner } from './spinner.js';
|
|
17
|
+
import { StatePanel } from '../internal/state-panel.js';
|
|
18
18
|
const RenderersContext = createContext({});
|
|
19
19
|
/** Registers file renderers once for every viewer below it. */
|
|
20
|
-
export function FileRenderersProvider({ renderers, children
|
|
20
|
+
export function FileRenderersProvider({ renderers, children }) {
|
|
21
21
|
const parent = useContext(RenderersContext);
|
|
22
22
|
const value = useMemo(() => ({ ...parent, ...renderers }), [parent, renderers]);
|
|
23
23
|
return _jsx(RenderersContext, { value: value, children: children });
|
|
@@ -39,8 +39,8 @@ export function googleDocument(url) {
|
|
|
39
39
|
catch {
|
|
40
40
|
return null;
|
|
41
41
|
}
|
|
42
|
-
if (parsed.protocol !==
|
|
43
|
-
parsed.hostname !==
|
|
42
|
+
if (parsed.protocol !== 'https:' ||
|
|
43
|
+
parsed.hostname !== 'docs.google.com' ||
|
|
44
44
|
parsed.username ||
|
|
45
45
|
parsed.password ||
|
|
46
46
|
parsed.port)
|
|
@@ -50,38 +50,35 @@ export function googleDocument(url) {
|
|
|
50
50
|
return null;
|
|
51
51
|
const [, family, id] = match;
|
|
52
52
|
const canonical = `https://docs.google.com/${family}/d/${id}`;
|
|
53
|
-
const endpoint = family ===
|
|
53
|
+
const endpoint = family === 'presentation' ? 'embed' : 'preview';
|
|
54
54
|
return {
|
|
55
55
|
url: `${canonical}/edit`,
|
|
56
|
-
embedUrl: `${canonical}/${endpoint}${family ===
|
|
57
|
-
mediaType: `application/vnd.google-apps.${family ===
|
|
56
|
+
embedUrl: `${canonical}/${endpoint}${family === 'spreadsheets' ? '?widget=true&headers=false' : ''}`,
|
|
57
|
+
mediaType: `application/vnd.google-apps.${family === 'spreadsheets' ? 'spreadsheet' : family}`,
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
/** Reads the document link from a .gdoc, .gsheet, or .gslides shortcut. */
|
|
61
61
|
export function googleShortcut(text) {
|
|
62
62
|
const value = JSON.parse(text);
|
|
63
|
-
if (typeof value !==
|
|
64
|
-
|
|
65
|
-
!("url" in value) ||
|
|
66
|
-
typeof value.url !== "string")
|
|
67
|
-
throw new Error("This Google shortcut does not contain a document link.");
|
|
63
|
+
if (typeof value !== 'object' || value === null || !('url' in value) || typeof value.url !== 'string')
|
|
64
|
+
throw new Error('This Google shortcut does not contain a document link.');
|
|
68
65
|
const document = googleDocument(value.url);
|
|
69
66
|
if (!document)
|
|
70
|
-
throw new Error(
|
|
67
|
+
throw new Error('This shortcut does not contain a supported Google Docs, Sheets, or Slides link.');
|
|
71
68
|
return document.url;
|
|
72
69
|
}
|
|
73
70
|
const rasterTypes = new Set([
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
'image/png',
|
|
72
|
+
'image/jpeg',
|
|
73
|
+
'image/gif',
|
|
74
|
+
'image/webp',
|
|
75
|
+
'image/avif',
|
|
76
|
+
'image/bmp',
|
|
77
|
+
'image/x-icon',
|
|
78
|
+
'image/vnd.microsoft.icon',
|
|
79
|
+
'image/heic',
|
|
80
|
+
'image/heif',
|
|
81
|
+
'image/tiff',
|
|
85
82
|
]);
|
|
86
83
|
/**
|
|
87
84
|
* The type to give a same-origin blob URL. Stored media types are
|
|
@@ -90,15 +87,13 @@ const rasterTypes = new Set([
|
|
|
90
87
|
*/
|
|
91
88
|
export function renderableType(name, mediaType) {
|
|
92
89
|
const kind = fileKind(name, mediaType);
|
|
93
|
-
if (kind ===
|
|
94
|
-
return
|
|
90
|
+
if (kind === 'pdf')
|
|
91
|
+
return 'application/pdf';
|
|
95
92
|
const type = mediaType.trim().toLowerCase();
|
|
96
|
-
return kind ===
|
|
97
|
-
? type
|
|
98
|
-
: "application/octet-stream";
|
|
93
|
+
return kind === 'image' && rasterTypes.has(type) ? type : 'application/octet-stream';
|
|
99
94
|
}
|
|
100
95
|
function useFileContent(file, attempt = 0, enabled = true) {
|
|
101
|
-
const [loaded, setLoaded] = useState({ kind:
|
|
96
|
+
const [loaded, setLoaded] = useState({ kind: 'loading' });
|
|
102
97
|
const { source, name, mediaType } = file;
|
|
103
98
|
useEffect(() => {
|
|
104
99
|
if (!enabled)
|
|
@@ -107,35 +102,35 @@ function useFileContent(file, attempt = 0, enabled = true) {
|
|
|
107
102
|
let objectUrl;
|
|
108
103
|
const controller = new AbortController();
|
|
109
104
|
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
110
|
-
setLoaded({ kind:
|
|
105
|
+
setLoaded({ kind: 'loading' });
|
|
111
106
|
const load = async () => {
|
|
112
107
|
let blob;
|
|
113
|
-
if (source.kind ===
|
|
108
|
+
if (source.kind === 'local')
|
|
114
109
|
blob = source.file;
|
|
115
110
|
else {
|
|
116
111
|
const response = await fetch(source.url, { signal: controller.signal });
|
|
117
112
|
if (!response.ok)
|
|
118
|
-
throw new Error(
|
|
113
|
+
throw new Error('The file could not be loaded. Check the link and try again.');
|
|
119
114
|
blob = await response.blob();
|
|
120
115
|
}
|
|
121
116
|
if (blob.size > maxViewableFileBytes)
|
|
122
|
-
throw new Error(
|
|
117
|
+
throw new Error('This file is larger than the 50 MB local viewing limit.');
|
|
123
118
|
const bytes = await blob.arrayBuffer();
|
|
124
119
|
if (cancelled)
|
|
125
120
|
return;
|
|
126
121
|
objectUrl = URL.createObjectURL(new Blob([bytes], {
|
|
127
122
|
type: renderableType(name, blob.type || mediaType),
|
|
128
123
|
}));
|
|
129
|
-
setLoaded({ kind:
|
|
124
|
+
setLoaded({ kind: 'ready', bytes, url: objectUrl });
|
|
130
125
|
};
|
|
131
126
|
void load()
|
|
132
127
|
.catch((error) => {
|
|
133
128
|
if (!cancelled)
|
|
134
129
|
setLoaded({
|
|
135
|
-
kind:
|
|
136
|
-
message: error instanceof Error && error.name !==
|
|
130
|
+
kind: 'error',
|
|
131
|
+
message: error instanceof Error && error.name !== 'AbortError'
|
|
137
132
|
? error.message
|
|
138
|
-
:
|
|
133
|
+
: 'The file took too long to load. Try again.',
|
|
139
134
|
});
|
|
140
135
|
})
|
|
141
136
|
.finally(() => clearTimeout(timeout));
|
|
@@ -151,11 +146,9 @@ function useFileContent(file, attempt = 0, enabled = true) {
|
|
|
151
146
|
}
|
|
152
147
|
const ownsArrowKeys = 'input, textarea, select, iframe, [contenteditable="true"], [role="region"], [role="slider"]';
|
|
153
148
|
/** A full-screen dialog that opens a local or linked file, or steps through several. */
|
|
154
|
-
export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpenChange, renderers, finalFocus, actions, menu, panel, panelLabel =
|
|
149
|
+
export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpenChange, renderers, finalFocus, actions, menu, panel, panelLabel = 'Details', panelOpen, defaultPanelOpen, onPanelOpenChange, }) {
|
|
155
150
|
const panelId = useId();
|
|
156
|
-
const [ownPanelOpen, setOwnPanelOpen] = useState(() => defaultPanelOpen ??
|
|
157
|
-
(typeof window !== "undefined" &&
|
|
158
|
-
!!window.matchMedia?.("(min-width: 1024px)").matches));
|
|
151
|
+
const [ownPanelOpen, setOwnPanelOpen] = useState(() => defaultPanelOpen ?? (typeof window !== 'undefined' && !!window.matchMedia?.('(min-width: 1024px)').matches));
|
|
159
152
|
const panelShown = !!panel && (panelOpen ?? ownPanelOpen);
|
|
160
153
|
const togglePanel = () => {
|
|
161
154
|
setOwnPanelOpen(!panelShown);
|
|
@@ -165,10 +158,9 @@ export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpen
|
|
|
165
158
|
const count = files?.length ?? 0;
|
|
166
159
|
const refocus = useRef(null);
|
|
167
160
|
const goTo = (target) => () => {
|
|
168
|
-
const button = document.activeElement?.closest(
|
|
169
|
-
const popup = button?.closest(
|
|
170
|
-
refocus.current =
|
|
171
|
-
button && popup ? { popup, direction: button.dataset.step ?? "" } : null;
|
|
161
|
+
const button = document.activeElement?.closest('[data-step]');
|
|
162
|
+
const popup = button?.closest('[data-file-viewer]');
|
|
163
|
+
refocus.current = button && popup ? { popup, direction: button.dataset.step ?? '' } : null;
|
|
172
164
|
onIndexChange?.(target);
|
|
173
165
|
};
|
|
174
166
|
const step = files && count > 1
|
|
@@ -186,27 +178,24 @@ export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpen
|
|
|
186
178
|
if (!pending)
|
|
187
179
|
return;
|
|
188
180
|
const enabled = ":not(:disabled):not([aria-disabled='true'])";
|
|
189
|
-
const target = pending.popup.querySelector(`[data-step="${pending.direction}"]${enabled}`) ??
|
|
181
|
+
const target = pending.popup.querySelector(`[data-step="${pending.direction}"]${enabled}`) ??
|
|
182
|
+
pending.popup.querySelector(`[data-step]${enabled}`);
|
|
190
183
|
target?.focus();
|
|
191
184
|
}, [index]);
|
|
192
185
|
const handleKeyDown = (event) => {
|
|
193
|
-
if (!step ||
|
|
194
|
-
event.defaultPrevented ||
|
|
195
|
-
event.altKey ||
|
|
196
|
-
event.ctrlKey ||
|
|
197
|
-
event.metaKey)
|
|
186
|
+
if (!step || event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey)
|
|
198
187
|
return;
|
|
199
|
-
if (event.key !==
|
|
188
|
+
if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')
|
|
200
189
|
return;
|
|
201
190
|
if (event.target.closest(ownsArrowKeys))
|
|
202
191
|
return;
|
|
203
|
-
const move = event.key ===
|
|
192
|
+
const move = event.key === 'ArrowLeft' ? step.previous : step.next;
|
|
204
193
|
if (!move)
|
|
205
194
|
return;
|
|
206
195
|
event.preventDefault();
|
|
207
196
|
move();
|
|
208
197
|
};
|
|
209
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "fullscreen", showCloseButton: false, "data-file-viewer": "", "data-panel": panelShown ?
|
|
198
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "fullscreen", showCloseButton: false, "data-file-viewer": "", "data-panel": panelShown ? 'open' : undefined, className: "sprout-file-viewer", finalFocus: finalFocus, onKeyDown: handleKeyDown, children: [current && open && (_jsx(SelectedFile, { file: current, renderers: renderers, step: step, actions: actions, menu: menu, panel: panel
|
|
210
199
|
? {
|
|
211
200
|
id: panelId,
|
|
212
201
|
label: panelLabel,
|
|
@@ -216,53 +205,43 @@ export function FileViewer({ file, files, index = 0, onIndexChange, open, onOpen
|
|
|
216
205
|
: undefined }, step ? index : undefined)), current && open && panel && (_jsx("aside", { id: panelId, "aria-label": panelLabel, hidden: !panelShown, "data-slot": "file-viewer-panel", className: "sprout-file-viewer-panel", children: panel }))] }) }));
|
|
217
206
|
}
|
|
218
207
|
function sourceLabel(file, google) {
|
|
219
|
-
return google
|
|
220
|
-
? "Google document"
|
|
221
|
-
: file.source.kind === "local"
|
|
222
|
-
? "Local file · Not uploaded"
|
|
223
|
-
: "Linked file";
|
|
208
|
+
return google ? 'Google document' : file.source.kind === 'local' ? 'Local file · Not uploaded' : 'Linked file';
|
|
224
209
|
}
|
|
225
210
|
function SelectedFile({ file, renderers, step, actions, menu, panel, }) {
|
|
226
211
|
const [attempt, setAttempt] = useState(0);
|
|
227
|
-
const retry = () => setAttempt(
|
|
228
|
-
const google = file.source.kind ===
|
|
212
|
+
const retry = () => setAttempt(value => value + 1);
|
|
213
|
+
const google = file.source.kind === 'remote' ? googleDocument(file.source.url) : null;
|
|
229
214
|
const loaded = useFileContent(file, attempt, !google);
|
|
230
|
-
return (_jsxs(_Fragment, { children: [_jsxs("header", { "data-slot": "file-viewer-header", className: "sprout-file-viewer-header", children: [_jsxs("div", { className: "sprout-file-viewer-heading", children: [_jsx(DialogTitle, { children: _jsx("span", { className: "sprout-file-viewer-truncate", title: file.name, children: file.name }) }), _jsx(DialogDescription, { children: _jsx("span", { className: "sprout-file-viewer-truncate", children: sourceLabel(file, !!google) }) })] }), _jsxs("div", { "data-slot": "file-viewer-controls", className: "sprout-file-viewer-controls", children: [step && (_jsxs("div", { "data-slot": "file-viewer-steps", className: "sprout-file-viewer-steps", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous file", "data-step": "previous", disabled: !step.previous, onClick: step.previous, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { className: "sprout-file-viewer-position", "aria-hidden": "true", children: [step.position, " of ", step.count] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next file", "data-step": "next", disabled: !step.next, onClick: step.next, children: _jsx(ChevronRight, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { "aria-live": "polite", className: "sprout-file-viewer-sr-only", children: [file.name, ", file ", step.position, " of ", step.count] })] })), actions && (_jsx("div", { "data-slot": "file-viewer-actions", className: "sprout-file-viewer-actions", children: actions })), google ? (_jsxs(Button, { variant: "outline", size: "sm", nativeButton: false, render: _jsx("a", { href: google.url, target: "_blank", rel: "noopener noreferrer" }), children: [_jsx(ArrowUpRight, { size: 16, "aria-hidden": "true" }), "Open in Google"] })) : loaded.kind ===
|
|
215
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { "data-slot": "file-viewer-header", className: "sprout-file-viewer-header", children: [_jsxs("div", { className: "sprout-file-viewer-heading", children: [_jsx(DialogTitle, { children: _jsx("span", { className: "sprout-file-viewer-truncate", title: file.name, children: file.name }) }), _jsx(DialogDescription, { children: _jsx("span", { className: "sprout-file-viewer-truncate", children: sourceLabel(file, !!google) }) })] }), _jsxs("div", { "data-slot": "file-viewer-controls", className: "sprout-file-viewer-controls", children: [step && (_jsxs("div", { "data-slot": "file-viewer-steps", className: "sprout-file-viewer-steps", children: [_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Previous file", "data-step": "previous", disabled: !step.previous, onClick: step.previous, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { className: "sprout-file-viewer-position", "aria-hidden": "true", children: [step.position, " of ", step.count] }), _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Next file", "data-step": "next", disabled: !step.next, onClick: step.next, children: _jsx(ChevronRight, { size: 16, "aria-hidden": "true" }) }), _jsxs("span", { "aria-live": "polite", className: "sprout-file-viewer-sr-only", children: [file.name, ", file ", step.position, " of ", step.count] })] })), actions && (_jsx("div", { "data-slot": "file-viewer-actions", className: "sprout-file-viewer-actions", children: actions })), google ? (_jsxs(Button, { variant: "outline", size: "sm", nativeButton: false, render: _jsx("a", { href: google.url, target: "_blank", rel: "noopener noreferrer" }), children: [_jsx(ArrowUpRight, { size: 16, "aria-hidden": "true" }), "Open in Google"] })) : loaded.kind === 'ready' ? (_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "Download", nativeButton: false, render: _jsx("a", { href: loaded.url, download: file.name }), children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-file-viewer-label", children: "Download" })] })) : (_jsxs(Button, { variant: "outline", size: "sm", "aria-label": "Download", disabled: true, children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), _jsx("span", { className: "sprout-file-viewer-label", children: "Download" })] })), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "More actions" }), children: _jsx(MoreH, { size: 16, "aria-hidden": "true" }) }), _jsxs(DropdownMenuContent, { align: "end", size: "menu", children: [_jsx(DropdownMenuItem, { onClick: retry, children: google ? 'Reload viewer' : 'Reload file' }), menu && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), menu] }))] })] })] }), _jsxs("div", { "data-slot": "file-viewer-window", className: "sprout-file-viewer-window", children: [panel && (_jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": panel.label, "aria-expanded": panel.open, "aria-controls": panel.id, onClick: panel.toggle, children: _jsx(SidebarRight, { size: 16, "aria-hidden": "true" }) })), _jsx(DialogClose, { render: _jsx(Button, { variant: "ghost", size: "icon-sm", "aria-label": "Close file viewer" }), children: _jsx(X, { size: 18, "aria-hidden": "true" }) })] })] }), _jsx("div", { "data-slot": "viewable-file-content", className: "sprout-file-viewer-surface", children: google ? (_jsx(EmbeddedFile, { url: google.embedUrl, name: file.name, google: true, onRetry: retry }, `${google.embedUrl}:${attempt}`)) : (_jsx(FileBody, { loaded: loaded, file: file, renderers: renderers, onRetry: retry })) })] }));
|
|
231
216
|
}
|
|
232
217
|
/** Loads a local or linked file and shows it inline, with reload and download. */
|
|
233
218
|
export function ViewableFileContent({ file, renderers, className, ...props }) {
|
|
234
219
|
const [attempt, setAttempt] = useState(0);
|
|
235
|
-
const retry = () => setAttempt(
|
|
236
|
-
const google = file.source.kind ===
|
|
237
|
-
return (_jsx("div", { "data-slot": "viewable-file-content", ...props, className: cn(
|
|
220
|
+
const retry = () => setAttempt(value => value + 1);
|
|
221
|
+
const google = file.source.kind === 'remote' ? googleDocument(file.source.url) : null;
|
|
222
|
+
return (_jsx("div", { "data-slot": "viewable-file-content", ...props, className: cn('sprout-file-viewer-surface', className), children: google ? (_jsx(EmbeddedFile, { url: google.embedUrl, name: file.name, google: true, onRetry: retry }, `${google.embedUrl}:${attempt}`)) : (_jsx(LocalFile, { file: file, renderers: renderers, onRetry: retry }, attempt)) }));
|
|
238
223
|
}
|
|
239
224
|
function LocalFile({ file, renderers, onRetry, }) {
|
|
240
225
|
const loaded = useFileContent(file);
|
|
241
|
-
return (_jsxs(_Fragment, { children: [loaded.kind ===
|
|
226
|
+
return (_jsxs(_Fragment, { children: [loaded.kind === 'ready' && (_jsxs("div", { "data-slot": "file-viewer-toolbar", className: "sprout-file-viewer-toolbar", "data-align": "end", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: onRetry, children: "Reload file" }), _jsxs(Button, { variant: "outline", size: "sm", nativeButton: false, render: _jsx("a", { href: loaded.url, download: file.name }), children: [_jsx(Download, { size: 16, "aria-hidden": "true" }), "Download"] })] })), _jsx(FileBody, { loaded: loaded, file: file, renderers: renderers, onRetry: onRetry })] }));
|
|
242
227
|
}
|
|
243
228
|
function FileBody({ loaded, file, renderers, onRetry, }) {
|
|
244
|
-
if (loaded.kind ===
|
|
229
|
+
if (loaded.kind === 'loading')
|
|
245
230
|
return _jsx(LoadingFile, {});
|
|
246
|
-
if (loaded.kind ===
|
|
231
|
+
if (loaded.kind === 'error')
|
|
247
232
|
return _jsx(FileError, { message: loaded.message, onRetry: onRetry });
|
|
248
233
|
return (_jsx(FileContent, { name: file.name, mediaType: file.mediaType, bytes: loaded.bytes, url: loaded.url, onRetry: onRetry, renderers: renderers }));
|
|
249
234
|
}
|
|
250
|
-
const textKinds = [
|
|
251
|
-
"text",
|
|
252
|
-
"code",
|
|
253
|
-
"json",
|
|
254
|
-
"markdown",
|
|
255
|
-
"google",
|
|
256
|
-
];
|
|
235
|
+
const textKinds = ['text', 'code', 'json', 'markdown', 'google'];
|
|
257
236
|
/** Shows bytes you already loaded: images, PDF, text, JSON, Markdown, Google shortcuts, and more. */
|
|
258
|
-
export function FileContent({ name, mediaType, bytes, url, onRetry, renderers: own
|
|
237
|
+
export function FileContent({ name, mediaType, bytes, url, onRetry, renderers: own }) {
|
|
259
238
|
const renderers = useRenderers(own);
|
|
260
239
|
const kind = fileKind(name, mediaType);
|
|
261
240
|
const text = useMemo(() => {
|
|
262
241
|
if (!textKinds.includes(kind))
|
|
263
242
|
return null;
|
|
264
243
|
try {
|
|
265
|
-
return new TextDecoder(
|
|
244
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
266
245
|
}
|
|
267
246
|
catch {
|
|
268
247
|
return undefined;
|
|
@@ -270,24 +249,24 @@ export function FileContent({ name, mediaType, bytes, url, onRetry, renderers: o
|
|
|
270
249
|
}, [bytes, kind]);
|
|
271
250
|
if (text === undefined)
|
|
272
251
|
return (_jsx(FileError, { message: "This file is not valid UTF-8 text. Download it to open in another application.", onRetry: onRetry }));
|
|
273
|
-
const unavailable = (_jsx(StatePanel, { kind: "unavailable", title: "This format cannot be displayed locally", description: name.toLowerCase().endsWith(
|
|
274
|
-
?
|
|
275
|
-
:
|
|
252
|
+
const unavailable = (_jsx(StatePanel, { kind: "unavailable", title: "This format cannot be displayed locally", description: name.toLowerCase().endsWith('.ppt')
|
|
253
|
+
? 'Save this older PowerPoint file as .pptx to view the slides here. You can still download the original.'
|
|
254
|
+
: 'Download the original file to open it in a compatible application.' }));
|
|
276
255
|
let body;
|
|
277
256
|
if (bytes.byteLength === 0)
|
|
278
|
-
body =
|
|
279
|
-
else if (kind ===
|
|
257
|
+
body = _jsx(StatePanel, { kind: "empty", title: "This file is empty", description: "The file contains no data to display." });
|
|
258
|
+
else if (kind === 'image')
|
|
280
259
|
body = _jsx(ImageFile, { url: url, name: name, onRetry: onRetry });
|
|
281
|
-
else if (kind ===
|
|
260
|
+
else if (kind === 'pdf')
|
|
282
261
|
body = _jsx(EmbeddedFile, { url: url, name: name, onRetry: onRetry });
|
|
283
|
-
else if (kind ===
|
|
284
|
-
body = _jsx(GoogleShortcut, { text: text ??
|
|
285
|
-
else if (kind ===
|
|
262
|
+
else if (kind === 'google')
|
|
263
|
+
body = _jsx(GoogleShortcut, { text: text ?? '', name: name, onRetry: onRetry });
|
|
264
|
+
else if (kind === 'sheet')
|
|
286
265
|
body = renderers.sheet ? (_jsx(Suspense, { fallback: _jsx(LoadingFile, {}), children: renderers.sheet({ name, bytes, onRetry }) })) : (unavailable);
|
|
287
|
-
else if (kind ===
|
|
266
|
+
else if (kind === 'document' || kind === 'slides')
|
|
288
267
|
body = renderers.office ? (_jsx(Suspense, { fallback: _jsx(LoadingFile, {}), children: renderers.office({ name, bytes, kind, onRetry }) })) : (unavailable);
|
|
289
268
|
else if (text !== null)
|
|
290
|
-
body =
|
|
269
|
+
body = _jsx(TextFile, { text: text, name: name, kind: kind, renderMarkdown: renderers.markdown });
|
|
291
270
|
else
|
|
292
271
|
body = unavailable;
|
|
293
272
|
return (_jsx("div", { "data-slot": "file-content", "data-kind": kind, className: "sprout-file-content", children: body }));
|
|
@@ -299,22 +278,17 @@ export function FileError({ message, onRetry, className, ...props }) {
|
|
|
299
278
|
return (_jsx(StatePanel, { kind: "error", title: "Could not open this file", description: message, "data-file-state": "error", ...props, className: className, children: _jsx(Button, { variant: "outline", onClick: onRetry, children: "Try again" }) }));
|
|
300
279
|
}
|
|
301
280
|
function codeLanguageFor(name, kind) {
|
|
302
|
-
return kind ===
|
|
281
|
+
return kind === 'code' ? (languageForFilename(name) ?? 'text') : kind;
|
|
303
282
|
}
|
|
304
|
-
const previewTextKinds = [
|
|
305
|
-
"text",
|
|
306
|
-
"code",
|
|
307
|
-
"json",
|
|
308
|
-
"markdown",
|
|
309
|
-
];
|
|
283
|
+
const previewTextKinds = ['text', 'code', 'json', 'markdown'];
|
|
310
284
|
const previewBytes = 16 * 1024;
|
|
311
285
|
const previewLines = 24;
|
|
312
286
|
/** Whether `FilePreview` can show this file: images, text, code, JSON, and Markdown, but not Google shortcuts. */
|
|
313
287
|
export function isPreviewableFile(file) {
|
|
314
|
-
if (file.source.kind ===
|
|
288
|
+
if (file.source.kind === 'remote' && googleDocument(file.source.url))
|
|
315
289
|
return false;
|
|
316
290
|
const kind = fileKind(file.name, file.mediaType);
|
|
317
|
-
return kind ===
|
|
291
|
+
return kind === 'image' || previewTextKinds.includes(kind);
|
|
318
292
|
}
|
|
319
293
|
/**
|
|
320
294
|
* A small, non-interactive peek at a file: the image, or the first lines
|
|
@@ -325,30 +299,30 @@ export function FilePreview({ file, className, ...props }) {
|
|
|
325
299
|
if (!isPreviewableFile(file))
|
|
326
300
|
return null;
|
|
327
301
|
const kind = fileKind(file.name, file.mediaType);
|
|
328
|
-
return (_jsx("div", { "data-slot": "file-preview", "data-kind": kind, ...props, className: cn(
|
|
302
|
+
return (_jsx("div", { "data-slot": "file-preview", "data-kind": kind, ...props, className: cn('sprout-file-preview', className), children: kind === 'image' && file.source.kind === 'remote' ? (_jsx("img", { src: file.source.url, alt: file.name, className: "sprout-file-preview-image" })) : (_jsx(LoadedPreview, { file: file, kind: kind })) }));
|
|
329
303
|
}
|
|
330
|
-
function LoadedPreview({ file, kind
|
|
304
|
+
function LoadedPreview({ file, kind }) {
|
|
331
305
|
const loaded = useFileContent(file);
|
|
332
306
|
const text = useMemo(() => {
|
|
333
|
-
if (loaded.kind !==
|
|
307
|
+
if (loaded.kind !== 'ready' || kind === 'image')
|
|
334
308
|
return null;
|
|
335
309
|
const head = new TextDecoder().decode(loaded.bytes.slice(0, previewBytes));
|
|
336
|
-
return head.split(
|
|
310
|
+
return head.split('\n').slice(0, previewLines).join('\n');
|
|
337
311
|
}, [loaded, kind]);
|
|
338
|
-
if (loaded.kind ===
|
|
312
|
+
if (loaded.kind === 'loading')
|
|
339
313
|
return (_jsxs("span", { className: "sprout-file-preview-state", children: [_jsx(Spinner, { size: "sm", "aria-hidden": "true" }), "Loading preview\u2026"] }));
|
|
340
|
-
if (loaded.kind ===
|
|
341
|
-
return
|
|
314
|
+
if (loaded.kind === 'error')
|
|
315
|
+
return _jsx("span", { className: "sprout-file-preview-state", children: "Preview unavailable" });
|
|
342
316
|
if (text === null)
|
|
343
|
-
return
|
|
317
|
+
return _jsx("img", { src: loaded.url, alt: file.name, className: "sprout-file-preview-image" });
|
|
344
318
|
if (!text.trim())
|
|
345
|
-
return
|
|
346
|
-
return (_jsx(CodeBlock, { code: text, language: codeLanguageFor(file.name, kind), variant: "preview", lineNumbers: false, wrap: kind ===
|
|
319
|
+
return _jsx("span", { className: "sprout-file-preview-state", children: "This file is empty" });
|
|
320
|
+
return (_jsx(CodeBlock, { code: text, language: codeLanguageFor(file.name, kind), variant: "preview", lineNumbers: false, wrap: kind === 'text' || kind === 'markdown' }));
|
|
347
321
|
}
|
|
348
322
|
function TextFile({ text, name, kind, renderMarkdown, }) {
|
|
349
323
|
const [source, setSource] = useState(false);
|
|
350
324
|
const json = useMemo(() => {
|
|
351
|
-
if (kind !==
|
|
325
|
+
if (kind !== 'json')
|
|
352
326
|
return null;
|
|
353
327
|
try {
|
|
354
328
|
return JSON.stringify(JSON.parse(text), null, 2);
|
|
@@ -357,44 +331,42 @@ function TextFile({ text, name, kind, renderMarkdown, }) {
|
|
|
357
331
|
return undefined;
|
|
358
332
|
}
|
|
359
333
|
}, [kind, text]);
|
|
360
|
-
const markdown = kind ===
|
|
361
|
-
const prose = kind ===
|
|
362
|
-
return (_jsxs("div", { className: "sprout-file-text", "data-format": markdown && !source ?
|
|
334
|
+
const markdown = kind === 'markdown' && renderMarkdown;
|
|
335
|
+
const prose = kind === 'text' || kind === 'markdown';
|
|
336
|
+
return (_jsxs("div", { className: "sprout-file-text", "data-format": markdown && !source ? 'formatted' : 'source', children: [markdown && (_jsx("div", { className: "sprout-file-text-bar", children: _jsx(Button, { variant: "outline", size: "sm", "aria-pressed": source, onClick: () => setSource(value => !value), children: source ? 'Show formatted' : 'Show source' }) })), json === undefined && (_jsx("p", { role: "alert", className: "sprout-file-text-alert", children: "This file contains invalid JSON. The original text is shown below." })), markdown && !source ? (_jsx("div", { className: "sprout-file-text-scroll", tabIndex: 0, role: "region", "aria-label": name, children: _jsx("div", { className: "sprout-file-text-page", children: _jsx(Suspense, { fallback: _jsx(LoadingFile, {}), children: renderMarkdown({ text }) }) }) })) : (_jsx(CodeBlock, { code: json ?? text, language: codeLanguageFor(name, kind), variant: "file", wrap: prose }))] }));
|
|
363
337
|
}
|
|
364
|
-
function ImageFile({ url, name, onRetry
|
|
365
|
-
const [state, setState] = useState(
|
|
338
|
+
function ImageFile({ url, name, onRetry }) {
|
|
339
|
+
const [state, setState] = useState('loading');
|
|
366
340
|
const [zoom, setZoom] = useState(false);
|
|
367
|
-
if (state ===
|
|
341
|
+
if (state === 'error')
|
|
368
342
|
return (_jsx(FileError, { message: "Your browser could not decode this image. Try a PNG, JPEG, GIF, WebP, AVIF, or BMP version.", onRetry: onRetry }));
|
|
369
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sprout-file-viewer-toolbar", "data-align": "center", children: _jsx(Button, { variant: "outline", size: "sm", "aria-pressed": zoom, onClick: () => setZoom(
|
|
343
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sprout-file-viewer-toolbar", "data-align": "center", children: _jsx(Button, { variant: "outline", size: "sm", "aria-pressed": zoom, onClick: () => setZoom(value => !value), children: zoom ? 'Fit image' : 'Actual size' }) }), state === 'loading' && _jsx(LoadingFile, {}), _jsx("div", { className: "sprout-file-image-stage", "data-zoom": zoom ? 'actual' : 'fit', tabIndex: zoom ? 0 : undefined, role: zoom ? 'region' : undefined, "aria-label": zoom ? `${name} at actual size` : undefined, children: _jsx("img", { src: url, alt: name, onLoad: () => setState('ready'), onError: () => setState('error'), className: "sprout-file-image", "data-state": state }) })] }));
|
|
370
344
|
}
|
|
371
|
-
function GoogleShortcut({ text, name, onRetry
|
|
345
|
+
function GoogleShortcut({ text, name, onRetry }) {
|
|
372
346
|
let document;
|
|
373
347
|
try {
|
|
374
348
|
document = googleDocument(googleShortcut(text));
|
|
375
349
|
}
|
|
376
350
|
catch (error) {
|
|
377
|
-
return (_jsx(FileError, { message: error instanceof Error
|
|
378
|
-
? error.message
|
|
379
|
-
: "Invalid Google document shortcut.", onRetry: onRetry }));
|
|
351
|
+
return (_jsx(FileError, { message: error instanceof Error ? error.message : 'Invalid Google document shortcut.', onRetry: onRetry }));
|
|
380
352
|
}
|
|
381
353
|
if (!document)
|
|
382
354
|
return null;
|
|
383
355
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sprout-file-google-link", children: _jsx("a", { href: document.url, target: "_blank", rel: "noopener noreferrer", children: "Open in Google" }) }), _jsx(EmbeddedFile, { url: document.embedUrl, name: name, google: true, onRetry: onRetry })] }));
|
|
384
356
|
}
|
|
385
357
|
function EmbeddedFile({ url, name, google = false, onRetry, }) {
|
|
386
|
-
const [state, setState] = useState(
|
|
358
|
+
const [state, setState] = useState('loading');
|
|
387
359
|
const timer = useRef(null);
|
|
388
360
|
useEffect(() => {
|
|
389
|
-
timer.current = setTimeout(() => setState(
|
|
361
|
+
timer.current = setTimeout(() => setState(value => (value === 'loading' ? 'slow' : value)), 12000);
|
|
390
362
|
return () => {
|
|
391
363
|
if (timer.current)
|
|
392
364
|
clearTimeout(timer.current);
|
|
393
365
|
};
|
|
394
366
|
}, []);
|
|
395
|
-
return (_jsxs("div", { "data-slot": "file-embed", "data-state": state, className: "sprout-file-embed", children: [google && (_jsx("p", { className: "sprout-file-embed-notice", children: "Google controls access to this document. If it cannot open here, use Open in Google to sign in or request access." })), state ===
|
|
367
|
+
return (_jsxs("div", { "data-slot": "file-embed", "data-state": state, className: "sprout-file-embed", children: [google && (_jsx("p", { className: "sprout-file-embed-notice", children: "Google controls access to this document. If it cannot open here, use Open in Google to sign in or request access." })), state === 'loading' && (_jsx("div", { className: "sprout-file-embed-loading", children: _jsx(LoadingFile, {}) })), state === 'slow' && (_jsxs("div", { className: "sprout-file-embed-slow", children: [_jsx("p", { role: "status", children: "The embedded viewer is taking longer than expected." }), _jsx(Button, { variant: "outline", size: "sm", onClick: onRetry, children: "Reload viewer" })] })), _jsx("iframe", { title: name, src: url, className: "sprout-file-embed-frame", referrerPolicy: "no-referrer", onLoad: () => {
|
|
396
368
|
if (timer.current)
|
|
397
369
|
clearTimeout(timer.current);
|
|
398
|
-
setState(
|
|
399
|
-
}, onError: () => setState(
|
|
370
|
+
setState('loaded');
|
|
371
|
+
}, onError: () => setState('slow'), allowFullScreen: true })] }));
|
|
400
372
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type ReactElement, type ReactNode } from
|
|
2
|
-
import { type ButtonBlockerFix as ActionBlockerFix, type ButtonState as ActionControlState } from
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { type ButtonBlockerFix as ActionBlockerFix, type ButtonState as ActionControlState } from './button.js';
|
|
3
3
|
export declare const formFooterLayoutNames: readonly ["bar", "stack", "compact"];
|
|
4
4
|
export declare const formSectionLevelNames: readonly [2, 3];
|
|
5
5
|
export type FormFooterLayout = (typeof formFooterLayoutNames)[number];
|
|
@@ -12,7 +12,7 @@ export interface FormProps {
|
|
|
12
12
|
/** Locks every Field, ChoiceGroup, and CheckboxField inside. */
|
|
13
13
|
disabled?: boolean | undefined;
|
|
14
14
|
/** Return `"invalid"` after revealing errors; Form then focuses the first invalid control. */
|
|
15
|
-
onSubmit: () =>
|
|
15
|
+
onSubmit: () => 'invalid' | void;
|
|
16
16
|
/** The form's accessible name. */
|
|
17
17
|
label?: string | undefined;
|
|
18
18
|
className?: string | undefined;
|
|
@@ -23,7 +23,7 @@ export interface FormProps {
|
|
|
23
23
|
* while `disabled`, and moves focus to the first invalid control after a
|
|
24
24
|
* failed submit.
|
|
25
25
|
*/
|
|
26
|
-
export declare function Form({ id, disabled, onSubmit, label, className, children
|
|
26
|
+
export declare function Form({ id, disabled, onSubmit, label, className, children }: FormProps): import("react").JSX.Element;
|
|
27
27
|
export interface FormSectionProps {
|
|
28
28
|
title: string;
|
|
29
29
|
description?: string | undefined;
|
|
@@ -37,17 +37,17 @@ export interface FormSectionProps {
|
|
|
37
37
|
children: ReactNode;
|
|
38
38
|
}
|
|
39
39
|
/** A bordered, named region of a form with a title, description, and optional meta badge. */
|
|
40
|
-
export declare function FormSection({ title, description, meta, label, id, level, className, children
|
|
40
|
+
export declare function FormSection({ title, description, meta, label, id, level, className, children }: FormSectionProps): import("react").JSX.Element;
|
|
41
41
|
export type FormCancel =
|
|
42
42
|
/** Shown only while the form is dirty and not busy. */
|
|
43
43
|
{
|
|
44
|
-
kind:
|
|
44
|
+
kind: 'reset';
|
|
45
45
|
label: string;
|
|
46
46
|
onReset: () => void;
|
|
47
47
|
}
|
|
48
48
|
/** A link element, such as a router `<Link>`, rendered through Button `render`. */
|
|
49
49
|
| {
|
|
50
|
-
kind:
|
|
50
|
+
kind: 'leave';
|
|
51
51
|
label: string;
|
|
52
52
|
link: ReactElement;
|
|
53
53
|
};
|