@@ -164,8 +161,12 @@ export default function App() {
)}
- {!loading && manifestStore && (
-
+ {!loading && manifestStore && processedSource && (
+
)}
{loading &&
}
diff --git a/frontend/src/components/Editor.css b/frontend/src/components/Editor.css
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/components/Editor.tsx b/frontend/src/components/Editor.tsx
new file mode 100644
index 0000000..c533a2f
--- /dev/null
+++ b/frontend/src/components/Editor.tsx
@@ -0,0 +1,50 @@
+import "./Editor.css";
+import Loader from "./Loader";
+import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
+import JsonView from "@uiw/react-json-view";
+import { useState, useEffect } from "react";
+
+export interface EditorPayload {
+ readonly: boolean;
+ manifest: object;
+}
+
+export default function Editor() {
+ const [manifest, setManifest] = useState
(null);
+
+ useEffect(() => {
+ // TODO: listen to window theme and change JsonView corrsponding theme
+
+ const webview = WebviewWindow.getCurrent();
+ if (webview) {
+ webview
+ .once("edit-info", (event) => {
+ const payload = event.payload as EditorPayload;
+ if (payload.readonly) {
+ // TODO: set window size based on size of json?
+ setManifest(payload.manifest);
+ } else {
+ // TODO: implement manifest editing
+ }
+ })
+ .catch(reportError);
+
+ webview.emit("request-edit-info").catch(reportError);
+ }
+ }, []);
+
+ return (
+ <>
+ {!manifest && }
+
+ {manifest && (
+
+ )}
+ >
+ );
+}
diff --git a/frontend/src/Inspect.css b/frontend/src/components/Inspect.css
similarity index 100%
rename from frontend/src/Inspect.css
rename to frontend/src/components/Inspect.css
diff --git a/frontend/src/Inspect.tsx b/frontend/src/components/Inspect.tsx
similarity index 52%
rename from frontend/src/Inspect.tsx
rename to frontend/src/components/Inspect.tsx
index 5d63ba4..9f2e91b 100644
--- a/frontend/src/Inspect.tsx
+++ b/frontend/src/components/Inspect.tsx
@@ -1,21 +1,95 @@
+import { EditorPayload } from "./Editor";
import "./Inspect.css";
+import { invoke } from "@tauri-apps/api/core";
+import { Menu, MenuItem } from "@tauri-apps/api/menu";
+import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
import { LogicalSize, getCurrent } from "@tauri-apps/api/window";
import { type } from "@tauri-apps/plugin-os";
import { open } from "@tauri-apps/plugin-shell";
-import { L2ManifestStore, generateVerifyUrl } from "c2pa";
+import { C2paSourceType, L2ManifestStore, generateVerifyUrl } from "c2pa";
import { ManifestSummary } from "c2pa-wc";
import "c2pa-wc/dist/components/ManifestSummary";
import { useEffect, useRef } from "react";
+import { v4 as uuid } from "uuid";
interface UploadProps {
onError: (err: string) => void;
manifestStore: L2ManifestStore;
+ source: C2paSourceType;
}
-export default function Inspect({ onError, manifestStore }: UploadProps) {
+function sourceToBytes(source: C2paSourceType): Promise | null {
+ if (source instanceof Blob) {
+ return source.arrayBuffer().then((buffer) => new Uint8Array(buffer));
+ } else if (source instanceof HTMLImageElement) {
+ // HTMLImageElement's also aren't used, so ignore for now
+ return null;
+ } else {
+ // URL's aren't used since we pull the file blob directly, so ignore this
+ return null;
+ }
+}
+
+export default function Inspect({
+ onError,
+ manifestStore,
+ source,
+}: UploadProps) {
const summaryRef = useRef(null);
const heightRef = useRef();
+ function showContextMenu(event: React.MouseEvent) {
+ event.preventDefault();
+
+ MenuItem.new({
+ text: "View JSON Manifest",
+ action: () => {
+ const webview = new WebviewWindow(`editor-${uuid()}`, {
+ url: "#/editor",
+ title: "c2pa-preview editor",
+ parent: "main",
+ });
+
+ // TODO: are these events auto unlistened when the window is dropped?
+ webview
+ .listen("request-edit-info", () => {
+ sourceToBytes(source)
+ ?.then((bytes) => invoke("c2pa_report", bytes))
+ .then((bytes) => {
+ const decoder = new TextDecoder("utf-8");
+ const manifest = JSON.parse(
+ decoder.decode(bytes as ArrayBuffer),
+ ) as object;
+
+ webview
+ .emit("edit-info", {
+ readonly: true,
+ manifest,
+ } as EditorPayload)
+ .catch(onError);
+ })
+ .catch(onError);
+ })
+ .catch(onError);
+ webview
+ .once("tauri://error", (err) => {
+ // Tauri source code says the payload will be a string
+ onError(err.payload as string);
+ })
+ .catch(onError);
+ },
+ })
+ .then((items) => {
+ return Menu.new({
+ items: [items],
+ });
+ })
+ .then((menu) => {
+ return menu.popup();
+ })
+ .catch(onError);
+ }
+
// Set manifest in component
useEffect(() => {
const summaryElement = summaryRef?.current;
@@ -88,6 +162,7 @@ export default function Inspect({ onError, manifestStore }: UploadProps) {
ref={summaryRef}
slot="content"
class="cai-manifest-theme"
+ onContextMenu={showContextMenu}
>
)}
>
diff --git a/frontend/src/Loader.css b/frontend/src/components/Loader.css
similarity index 100%
rename from frontend/src/Loader.css
rename to frontend/src/components/Loader.css
diff --git a/frontend/src/Loader.tsx b/frontend/src/components/Loader.tsx
similarity index 100%
rename from frontend/src/Loader.tsx
rename to frontend/src/components/Loader.tsx
diff --git a/frontend/src/Upload.css b/frontend/src/components/Upload.css
similarity index 100%
rename from frontend/src/Upload.css
rename to frontend/src/components/Upload.css
diff --git a/frontend/src/Upload.tsx b/frontend/src/components/Upload.tsx
similarity index 100%
rename from frontend/src/Upload.tsx
rename to frontend/src/components/Upload.tsx
diff --git a/frontend/src/error.ts b/frontend/src/error.ts
new file mode 100644
index 0000000..5e5d8f5
--- /dev/null
+++ b/frontend/src/error.ts
@@ -0,0 +1,9 @@
+import { message } from "@tauri-apps/plugin-dialog";
+
+export function reportError(err: string) {
+ console.error(err);
+ // If this fails, whataya gonna do
+ void message(err, {
+ kind: "error",
+ });
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 6bedce9..2f4ac64 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,20 +1,36 @@
-import App from "./App";
+import App from "./components/App";
+import Editor from "./components/Editor";
import "./style.css";
import { C2paProvider } from "@contentauth/react";
import wasmSrc from "c2pa/dist/assets/wasm/toolkit_bg.wasm?url";
import workerSrc from "c2pa/dist/c2pa.worker.min.js?url";
import React from "react";
import ReactDOM from "react-dom/client";
+import { RouterProvider, createHashRouter } from "react-router-dom";
+
+// TODO: fix app + editor top-level
+const router = createHashRouter([
+ {
+ path: "/",
+ element: (
+
+
+
+ ),
+ },
+ {
+ path: "/editor",
+ element: ,
+ },
+]);
ReactDOM.createRoot(document.getElementById("root")!).render(
-
-
-
+
,
);
diff --git a/package-lock.json b/package-lock.json
deleted file mode 100644
index d64380a..0000000
--- a/package-lock.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "c2pa-preview",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {}
-}