Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useEffect, useState } from 'react';

import { formatByteSize } from '@douglasneuroinformatics/libjs';
import { Heading, Input, Popover, Tooltip } from '@douglasneuroinformatics/libui/components';
import { Heading, Input, Label, Popover, Tooltip } from '@douglasneuroinformatics/libui/components';
import { useTranslation } from '@douglasneuroinformatics/libui/hooks';
import { CopyButton } from '@opendatacapture/react-core';
import { Share2Icon } from 'lucide-react';
import { CircleHelpIcon, Share2Icon } from 'lucide-react';

import { useFilesRef } from '@/hooks/useFilesRef';
import { useAppStore } from '@/store';
Expand All @@ -13,6 +13,7 @@ import { encodeShareURL } from '@/utils/encode';
export const ShareButton = () => {
const label = useAppStore((store) => store.selectedInstrument.label);
const editorFilesRef = useFilesRef();
const [isFullscreen, setIsFullscreen] = useState(false);
const [shareURL, setShareURL] = useState(encodeShareURL({ files: editorFilesRef.current, label }));
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
Expand All @@ -21,9 +22,9 @@ export const ShareButton = () => {
// The user cannot modify the editor without closing the popover
useEffect(() => {
if (isPopoverOpen) {
setShareURL(encodeShareURL({ files: editorFilesRef.current, label }));
setShareURL(encodeShareURL({ files: editorFilesRef.current, fullscreen: isFullscreen, label }));
}
}, [isPopoverOpen, label]);
}, [isFullscreen, isPopoverOpen, label]);

return (
<Tooltip
Expand Down Expand Up @@ -51,6 +52,45 @@ export const ShareButton = () => {
{formatByteSize(shareURL.size)}.
</p>
</div>
<div className="flex items-center justify-between gap-4 pt-4">
<div className="flex items-center gap-1.5">
<Label className="text-sm font-normal" htmlFor="readonly-fullscreen">
{t({ en: 'Preview Mode', fr: 'Mode aperçu' })}
</Label>
<Tooltip>
<Tooltip.Trigger
className="text-muted-foreground h-auto w-auto cursor-help p-0"
size="icon"
type="button"
variant="ghost"
>
<CircleHelpIcon className="h-4 w-4" />
</Tooltip.Trigger>
<Tooltip.Content className="max-w-60" side="top">
<p>
{t({
en: 'Anyone with the link opens the instrument fullscreen as a preview they can try, with no editor and no ability to make changes.',
fr: "Toute personne disposant du lien ouvre l'instrument en plein écran comme un aperçu qu'elle peut essayer, sans éditeur ni possibilité de le modifier."
})}
</p>
</Tooltip.Content>
</Tooltip>
</div>
<button
aria-checked={isFullscreen}
className="focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:outline-hidden peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
data-state={isFullscreen ? 'checked' : 'unchecked'}
id="readonly-fullscreen"
role="switch"
type="button"
onClick={() => setIsFullscreen((prev) => !prev)}
>
<span
className="bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
data-state={isFullscreen ? 'checked' : 'unchecked'}
/>
</button>
</div>
<div className="flex gap-2 pt-4">
<Input readOnly className="h-9" id="link" value={shareURL.href} />
<CopyButton size="sm" text={shareURL.href} />
Expand Down
29 changes: 27 additions & 2 deletions apps/playground/src/pages/IndexPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { useEffect } from 'react';

import { Separator } from '@douglasneuroinformatics/libui/components';
import { LanguageToggle, Separator, ThemeToggle } from '@douglasneuroinformatics/libui/components';
import esbuildWasmUrl from 'esbuild-wasm/esbuild.wasm?url';

import { Header } from '@/components/Header';
import { MainContent } from '@/components/MainContent';
import { Viewer } from '@/components/Viewer';
import type { InstrumentRepository } from '@/models/instrument-repository.model';
import { useAppStore } from '@/store';
import { decodeShareURL } from '@/utils/encode';
import { decodeShareURL, isFullscreenShareURL } from '@/utils/encode';

const { initialize } = await import('esbuild-wasm');
await initialize({
Expand Down Expand Up @@ -77,6 +78,30 @@ const IndexPage = () => {
};
}, [location.href]);

const isFullscreen = isFullscreenShareURL(new URL(location.href));

if (isFullscreen) {
return (
<div className="flex h-screen w-screen flex-col overflow-hidden">
<div className="flex items-center justify-end gap-2 px-4 py-2">
<ThemeToggle className="h-9 w-9" />
<LanguageToggle
align="end"
options={{
en: 'English',
fr: 'Français'
}}
triggerClassName="h-9 w-9"
/>
</div>
<Separator />
<main className="mx-auto w-full max-w-3xl grow overflow-hidden px-4 py-4">
<Viewer />
</main>
</div>
);
}

return (
<div className="flex h-screen w-screen flex-col overflow-hidden px-4">
<Header />
Expand Down
13 changes: 12 additions & 1 deletion apps/playground/src/utils/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ function encodeFiles(files: EditorFile[]): string {
return compressToEncodedURIComponent(JSON.stringify($EditorFiles.parse(files)));
}

export function encodeShareURL({ files, label }: Pick<InstrumentRepository, 'files' | 'label'>): ShareURL {
export function encodeShareURL({
files,
fullscreen,
label
}: Pick<InstrumentRepository, 'files' | 'label'> & { fullscreen?: boolean }): ShareURL {
const url = new URL(location.origin) as ShareURL;
url.searchParams.append('files', encodeFiles(files));
url.searchParams.append('label', compressToEncodedURIComponent(label));
if (fullscreen) {
url.searchParams.append('fullscreen', '1');
}
url.size = new TextEncoder().encode(url.href).length;
return url;
}

export function isFullscreenShareURL(url: URL): boolean {
return url.searchParams.get('fullscreen') === '1';
}

export function decodeShareURL(url: URL): null | Pick<InstrumentRepository, 'files' | 'label'> {
const encodedFiles = url.searchParams.get('files');
const encodedLabel = url.searchParams.get('label');
Expand Down
Loading