Ship file viewer, editor improvements, and private package releases
This commit is contained in:
1 parent
645914955c
commit
a4a31032b9
158 files changed
+10523
-137
No files matched your search
@@ -0,0 +1,16 @@
|
||||
import Viewer from "@wrnexus/viewer/components/Viewer.wrn"
|
||||
|
||||
page FilePreviewPage {
|
||||
layout = "public"
|
||||
render = "hybrid"
|
||||
hydrate = "load"
|
||||
|
||||
view {
|
||||
<main class="file-preview-page">
|
||||
<Viewer src="/viewer/sample.pdf" name="sample.pdf" layout="page" />
|
||||
</main>
|
||||
}
|
||||
style {
|
||||
.file-preview-page { display: flex; flex-direction: column; height: calc(100vh - 4rem); padding: 1rem; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import Viewer from "@wrnexus/viewer/components/Viewer.wrn"
|
||||
|
||||
page FileViewerDemo {
|
||||
layout = "public"
|
||||
render = "hybrid"
|
||||
hydrate = "load"
|
||||
|
||||
view {
|
||||
<main class="file-viewer-demo">
|
||||
<h1>File previews</h1>
|
||||
<h2>Browse several files</h2>
|
||||
<Viewer files={[{src: '/viewer/sample.png', name: 'sample.png'}, {src: '/viewer/sample.html', name: 'sample.html'}, {src: '/viewer/sample.wav', name: 'sample.wav'}, {src: '/viewer/sample.docx', name: 'sample.docx'}]} />
|
||||
<h2>Rich PDF canvas</h2>
|
||||
<Viewer src="/viewer/sample.pdf" name="rich-sample.pdf" mime="application/pdf" richPdf={true} />
|
||||
<p>Images, documents, media, tables, trees, vectors, and archives share the app theme and preview controls.</p>
|
||||
<h2>Image</h2>
|
||||
<Viewer src="/viewer/sample.png" name="sample.png" />
|
||||
<h2>Document</h2>
|
||||
<Viewer src="/viewer/sample.pdf" name="sample.pdf" />
|
||||
<h2>Video</h2>
|
||||
<Viewer src="/viewer/sample.webm" name="sample.webm" />
|
||||
<h2>Open a preview over the page</h2>
|
||||
<p>Any element with <code>data-viewer-open</code> opens the file in a dialog.</p>
|
||||
<p>
|
||||
<button type="button" data-viewer-open data-src="/viewer/sample.webm" data-name="sample.webm">Preview the video</button>
|
||||
<button type="button" data-viewer-open data-src="/viewer/sample.pptx" data-name="sample.pptx">Preview the deck</button>
|
||||
</p>
|
||||
<h2>Covering the page</h2>
|
||||
<p>A viewer can mount already expanded, with a Close control that returns it inline.</p>
|
||||
<Viewer src="/viewer/sample.png" name="expanded.png" fullScreen={false} />
|
||||
<h2>Audio</h2>
|
||||
<Viewer src="/viewer/sample.wav" name="sample.wav" />
|
||||
<h2>Structured data</h2>
|
||||
<Viewer src="/viewer/sample.json" name="sample.json" />
|
||||
<Viewer src="/viewer/sample.xml" name="sample.xml" />
|
||||
<h2>Spreadsheet</h2>
|
||||
<Viewer src="/viewer/sample.csv" name="sample.csv" />
|
||||
<Viewer src="/viewer/sample.xlsx" name="sample.xlsx" />
|
||||
<h2>Vector graphic</h2>
|
||||
<Viewer src="/viewer/sample.svg" name="sample.svg" />
|
||||
<h2>Archive</h2>
|
||||
<Viewer src="/viewer/sample.tar" name="sample.tar" />
|
||||
<Viewer src="/viewer/sample.zip" name="sample.zip" />
|
||||
<h2>HTML preview and source</h2>
|
||||
<Viewer src="/viewer/sample.html" name="sample.html" />
|
||||
<h2>From uploader storage</h2>
|
||||
<p>A public object serves itself; a private one resolves to a short-lived signed link.</p>
|
||||
<Viewer key="brand-logo.png" store="media" />
|
||||
<Viewer key="quarterly-report.pdf" store="reports" />
|
||||
<Viewer key="regional-figures.csv" store="reports" />
|
||||
<h2>Office documents</h2>
|
||||
<p>Presentations, OpenDocument files and RTF read in the browser; a converter, where one is installed, is preferred for page-faithful output.</p>
|
||||
<Viewer src="/viewer/sample.pptx" name="sample.pptx" />
|
||||
<Viewer src="/viewer/sample.odt" name="sample.odt" />
|
||||
<Viewer src="/viewer/sample.ods" name="sample.ods" />
|
||||
<Viewer src="/viewer/sample.rtf" name="sample.rtf" />
|
||||
<h2>Optional parser or server-side converter</h2>
|
||||
<Viewer src="/viewer/sample.docx" name="sample.docx" />
|
||||
<h2>Unsupported file</h2>
|
||||
<Viewer src="/viewer/sample.bin" name="sample.bin" />
|
||||
</main>
|
||||
}
|
||||
style {
|
||||
.file-viewer-demo { display: grid; gap: 1rem; max-width: 64rem; margin: 2rem auto; padding: 1rem; }
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ export interface Routes {
|
||||
"/chat": Record<string, never>;
|
||||
"/client-only": Record<string, never>;
|
||||
"/dashboard": Record<string, never>;
|
||||
"/file-preview": Record<string, never>;
|
||||
"/file-viewer": Record<string, never>;
|
||||
"/hello": Record<string, never>;
|
||||
"/island-demo": Record<string, never>;
|
||||
"/language-tools": Record<string, never>;
|
||||
@@ -33,6 +35,8 @@ export interface RouteNames {
|
||||
"chat": "/chat";
|
||||
"client.only": "/client-only";
|
||||
"dashboard": "/dashboard";
|
||||
"file.preview": "/file-preview";
|
||||
"file.viewer": "/file-viewer";
|
||||
"hello": "/hello";
|
||||
"island.demo": "/island-demo";
|
||||
"language.tools": "/language-tools";
|
||||
@@ -126,6 +130,8 @@ export function route<N extends RouteName>(
|
||||
"chat": "/chat",
|
||||
"client.only": "/client-only",
|
||||
"dashboard": "/dashboard",
|
||||
"file.preview": "/file-preview",
|
||||
"file.viewer": "/file-viewer",
|
||||
"hello": "/hello",
|
||||
"island.demo": "/island-demo",
|
||||
"language.tools": "/language-tools",
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@wrnexus/core": "workspace:*",
|
||||
"@wrnexus/validation": "workspace:*",
|
||||
"@wrnexus/db": "workspace:*",
|
||||
"@wrnexus/ui": "workspace:*"
|
||||
"@wrnexus/ui": "workspace:*",
|
||||
"@wrnexus/uploader": "workspace:*",
|
||||
"@wrnexus/validation": "workspace:*",
|
||||
"@wrnexus/viewer": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wrnexus/test": "workspace:*",
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
region,units,revenue
|
||||
"North, upper",120,15400.50
|
||||
"South ""central""",95,11200.00
|
||||
East,140,18990.75
|
||||
West,88,9400.25
|
||||
|
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<title>HTML source preview</title>
|
||||
<body><h1>HTML preview in a sandbox</h1><p>Use Source to inspect this document. Scripts and external resources are disabled.</p></body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "WRNexus file viewer",
|
||||
"readOnly": true,
|
||||
"features": ["image controls", "native PDF", "media playback", "source preview"]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{\rtf1\ansi{\fonttbl{\f0 Times New Roman;}}\f0\fs24 Rich Text Format\par\par This document is read in the browser with no dependency at all \u8212? RTF is text once the control words are gone.\par\par Accented text survives: Caf\'e9.}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><circle cx="60" cy="60" r="52" fill="none" stroke="currentColor" stroke-width="6"/><path d="M38 62l16 16 30-34" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round"/></svg>
|
||||
|
After Width: | Height: | Size: 256 B |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<catalog>
|
||||
<product id="a-1"><name>Desk lamp</name><price currency="INR">1499</price></product>
|
||||
<product id="a-2"><name>Notebook</name><price currency="INR">249</price></product>
|
||||
</catalog>
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
region,units,revenue
|
||||
"North, upper",120,15400.50
|
||||
"South ""central""",95,11200.00
|
||||
East,140,18990.75
|
||||
West,88,9400.25
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
@@ -2,6 +2,9 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import type { AppConfig } from "@wrnexus/styles";
|
||||
import { viewerPlugin } from "@wrnexus/viewer/plugin";
|
||||
import { localBinaryConverter, firstOf, sameOriginSource } from "@wrnexus/viewer/convert";
|
||||
import { uploadedConversionSource } from "@wrnexus/viewer/uploader";
|
||||
|
||||
/**
|
||||
* App configuration. Optional — delete this file and the app still runs.
|
||||
@@ -13,6 +16,38 @@ import type { AppConfig } from "@wrnexus/styles";
|
||||
* framework-level headers and optional CORS.
|
||||
*/
|
||||
const config: AppConfig = {
|
||||
// Server-side previews for formats no browser renders (.docx, .psd, .mkv,
|
||||
// .zip). Each recipe needs its binary on the host — LibreOffice, ImageMagick,
|
||||
// ffmpeg, 7-Zip. A missing binary is not an error: that format simply keeps
|
||||
// its download card, so this is safe to leave enabled anywhere.
|
||||
plugins: [
|
||||
viewerPlugin({
|
||||
convert: {
|
||||
converters: [localBinaryConverter()],
|
||||
// Converted output inherits the access check of its input: only a valid
|
||||
// signed link opens an object, so a private report cannot be converted
|
||||
// into a public preview.
|
||||
resolveSource: firstOf(uploadedConversionSource(), sameOriginSource()),
|
||||
},
|
||||
uploads: {
|
||||
stores: ["media", "reports"],
|
||||
secret: process.env.VIEWER_FILE_SECRET ?? "example-app-development-secret-value",
|
||||
ttlMs: 5 * 60 * 1000,
|
||||
// DEMO ONLY. A real app checks the signed-in user against the object
|
||||
// here — this hook is the whole access control for private previews.
|
||||
authorize: () => true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
storage: {
|
||||
default: "media",
|
||||
stores: {
|
||||
media: { driver: "local", access: "public", dir: "uploads/public" },
|
||||
reports: { driver: "local", access: "private", dir: "uploads/private" },
|
||||
},
|
||||
},
|
||||
|
||||
head: [
|
||||
// --- Use a CSS framework via CDN (uncomment one) ---
|
||||
// Bootstrap:
|
||||
@@ -65,6 +100,17 @@ const config: AppConfig = {
|
||||
// Defaults already include CSP, COOP, X-Frame-Options, Trusted Types in
|
||||
// production, strong HSTS in production, nosniff, referrer policy, and a
|
||||
// restrictive Permissions-Policy. CORS is opt-in:
|
||||
|
||||
// The /file-viewer page embeds a same-origin PDF in a frame. The framework
|
||||
// defaults (`frame-ancestors 'none'` plus `X-Frame-Options: DENY`) stop an
|
||||
// app framing even its own assets, so same-origin framing is allowed here.
|
||||
// Cross-origin framing stays blocked.
|
||||
frameOptions: "SAMEORIGIN",
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
"frame-ancestors": ["'self'"],
|
||||
},
|
||||
},
|
||||
cors: {
|
||||
enabled: true,
|
||||
origin: ["http://localhost:5173", "http://localhost:3000"],
|
||||
|
||||
Reference in new issue
Block a user