add timeline header

This commit is contained in:
2026-03-07 08:14:32 +01:00
parent dc524cad24
commit 724980fd31
9 changed files with 164 additions and 88 deletions

View File

@@ -9,14 +9,15 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root' import { Route as rootRouteImport } from './routes/__root'
import { Route as TimelinesRouteImport } from './routes/timelines'
import { Route as TimelineRouteImport } from './routes/timeline' import { Route as TimelineRouteImport } from './routes/timeline'
import { Route as LoginRouteImport } from './routes/login'
import { Route as DemoRouteImport } from './routes/demo' import { Route as DemoRouteImport } from './routes/demo'
import { Route as DashboardRouteImport } from './routes/dashboard' import { Route as DashboardRouteImport } from './routes/dashboard'
import { Route as ConsentRouteImport } from './routes/consent' import { Route as ConsentRouteImport } from './routes/consent'
import { Route as IndexRouteImport } from './routes/index' import { Route as DefaultRouteImport } from './routes/_default'
import { Route as DefaultIndexRouteImport } from './routes/_default.index'
import { Route as TimelineTimelineIdRouteImport } from './routes/timeline.$timelineId' import { Route as TimelineTimelineIdRouteImport } from './routes/timeline.$timelineId'
import { Route as DefaultTimelinesRouteImport } from './routes/_default.timelines'
import { Route as DefaultLoginRouteImport } from './routes/_default.login'
import { Route as DotwellKnownOpenidConfigurationRouteImport } from './routes/[.well-known]/openid-configuration' import { Route as DotwellKnownOpenidConfigurationRouteImport } from './routes/[.well-known]/openid-configuration'
import { Route as DotwellKnownOauthProtectedResourceRouteImport } from './routes/[.well-known]/oauth-protected-resource' import { Route as DotwellKnownOauthProtectedResourceRouteImport } from './routes/[.well-known]/oauth-protected-resource'
import { Route as DotwellKnownOauthAuthorizationServerRouteImport } from './routes/[.well-known]/oauth-authorization-server' import { Route as DotwellKnownOauthAuthorizationServerRouteImport } from './routes/[.well-known]/oauth-authorization-server'
@@ -24,21 +25,11 @@ import { Route as ApiMcpSplatRouteImport } from './routes/api/mcp/$'
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$' import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
import { Route as DotwellKnownOauthAuthorizationServerApiAuthRouteImport } from './routes/[.well-known]/oauth-authorization-server.api.auth' import { Route as DotwellKnownOauthAuthorizationServerApiAuthRouteImport } from './routes/[.well-known]/oauth-authorization-server.api.auth'
const TimelinesRoute = TimelinesRouteImport.update({
id: '/timelines',
path: '/timelines',
getParentRoute: () => rootRouteImport,
} as any)
const TimelineRoute = TimelineRouteImport.update({ const TimelineRoute = TimelineRouteImport.update({
id: '/timeline', id: '/timeline',
path: '/timeline', path: '/timeline',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const LoginRoute = LoginRouteImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const DemoRoute = DemoRouteImport.update({ const DemoRoute = DemoRouteImport.update({
id: '/demo', id: '/demo',
path: '/demo', path: '/demo',
@@ -54,16 +45,30 @@ const ConsentRoute = ConsentRouteImport.update({
path: '/consent', path: '/consent',
getParentRoute: () => rootRouteImport, getParentRoute: () => rootRouteImport,
} as any) } as any)
const IndexRoute = IndexRouteImport.update({ const DefaultRoute = DefaultRouteImport.update({
id: '/_default',
getParentRoute: () => rootRouteImport,
} as any)
const DefaultIndexRoute = DefaultIndexRouteImport.update({
id: '/', id: '/',
path: '/', path: '/',
getParentRoute: () => rootRouteImport, getParentRoute: () => DefaultRoute,
} as any) } as any)
const TimelineTimelineIdRoute = TimelineTimelineIdRouteImport.update({ const TimelineTimelineIdRoute = TimelineTimelineIdRouteImport.update({
id: '/$timelineId', id: '/$timelineId',
path: '/$timelineId', path: '/$timelineId',
getParentRoute: () => TimelineRoute, getParentRoute: () => TimelineRoute,
} as any) } as any)
const DefaultTimelinesRoute = DefaultTimelinesRouteImport.update({
id: '/timelines',
path: '/timelines',
getParentRoute: () => DefaultRoute,
} as any)
const DefaultLoginRoute = DefaultLoginRouteImport.update({
id: '/login',
path: '/login',
getParentRoute: () => DefaultRoute,
} as any)
const DotwellKnownOpenidConfigurationRoute = const DotwellKnownOpenidConfigurationRoute =
DotwellKnownOpenidConfigurationRouteImport.update({ DotwellKnownOpenidConfigurationRouteImport.update({
id: '/.well-known/openid-configuration', id: '/.well-known/openid-configuration',
@@ -100,50 +105,51 @@ const DotwellKnownOauthAuthorizationServerApiAuthRoute =
} as any) } as any)
export interface FileRoutesByFullPath { export interface FileRoutesByFullPath {
'/': typeof IndexRoute '/': typeof DefaultIndexRoute
'/consent': typeof ConsentRoute '/consent': typeof ConsentRoute
'/dashboard': typeof DashboardRoute '/dashboard': typeof DashboardRoute
'/demo': typeof DemoRoute '/demo': typeof DemoRoute
'/login': typeof LoginRoute
'/timeline': typeof TimelineRouteWithChildren '/timeline': typeof TimelineRouteWithChildren
'/timelines': typeof TimelinesRoute
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
'/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute '/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
'/login': typeof DefaultLoginRoute
'/timelines': typeof DefaultTimelinesRoute
'/timeline/$timelineId': typeof TimelineTimelineIdRoute '/timeline/$timelineId': typeof TimelineTimelineIdRoute
'/api/auth/$': typeof ApiAuthSplatRoute '/api/auth/$': typeof ApiAuthSplatRoute
'/api/mcp/$': typeof ApiMcpSplatRoute '/api/mcp/$': typeof ApiMcpSplatRoute
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
} }
export interface FileRoutesByTo { export interface FileRoutesByTo {
'/': typeof IndexRoute
'/consent': typeof ConsentRoute '/consent': typeof ConsentRoute
'/dashboard': typeof DashboardRoute '/dashboard': typeof DashboardRoute
'/demo': typeof DemoRoute '/demo': typeof DemoRoute
'/login': typeof LoginRoute
'/timeline': typeof TimelineRouteWithChildren '/timeline': typeof TimelineRouteWithChildren
'/timelines': typeof TimelinesRoute
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
'/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute '/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
'/login': typeof DefaultLoginRoute
'/timelines': typeof DefaultTimelinesRoute
'/timeline/$timelineId': typeof TimelineTimelineIdRoute '/timeline/$timelineId': typeof TimelineTimelineIdRoute
'/': typeof DefaultIndexRoute
'/api/auth/$': typeof ApiAuthSplatRoute '/api/auth/$': typeof ApiAuthSplatRoute
'/api/mcp/$': typeof ApiMcpSplatRoute '/api/mcp/$': typeof ApiMcpSplatRoute
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
} }
export interface FileRoutesById { export interface FileRoutesById {
__root__: typeof rootRouteImport __root__: typeof rootRouteImport
'/': typeof IndexRoute '/_default': typeof DefaultRouteWithChildren
'/consent': typeof ConsentRoute '/consent': typeof ConsentRoute
'/dashboard': typeof DashboardRoute '/dashboard': typeof DashboardRoute
'/demo': typeof DemoRoute '/demo': typeof DemoRoute
'/login': typeof LoginRoute
'/timeline': typeof TimelineRouteWithChildren '/timeline': typeof TimelineRouteWithChildren
'/timelines': typeof TimelinesRoute
'/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren '/.well-known/oauth-authorization-server': typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
'/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute '/.well-known/oauth-protected-resource': typeof DotwellKnownOauthProtectedResourceRoute
'/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute '/.well-known/openid-configuration': typeof DotwellKnownOpenidConfigurationRoute
'/_default/login': typeof DefaultLoginRoute
'/_default/timelines': typeof DefaultTimelinesRoute
'/timeline/$timelineId': typeof TimelineTimelineIdRoute '/timeline/$timelineId': typeof TimelineTimelineIdRoute
'/_default/': typeof DefaultIndexRoute
'/api/auth/$': typeof ApiAuthSplatRoute '/api/auth/$': typeof ApiAuthSplatRoute
'/api/mcp/$': typeof ApiMcpSplatRoute '/api/mcp/$': typeof ApiMcpSplatRoute
'/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute '/.well-known/oauth-authorization-server/api/auth': typeof DotwellKnownOauthAuthorizationServerApiAuthRoute
@@ -155,58 +161,57 @@ export interface FileRouteTypes {
| '/consent' | '/consent'
| '/dashboard' | '/dashboard'
| '/demo' | '/demo'
| '/login'
| '/timeline' | '/timeline'
| '/timelines'
| '/.well-known/oauth-authorization-server' | '/.well-known/oauth-authorization-server'
| '/.well-known/oauth-protected-resource' | '/.well-known/oauth-protected-resource'
| '/.well-known/openid-configuration' | '/.well-known/openid-configuration'
| '/login'
| '/timelines'
| '/timeline/$timelineId' | '/timeline/$timelineId'
| '/api/auth/$' | '/api/auth/$'
| '/api/mcp/$' | '/api/mcp/$'
| '/.well-known/oauth-authorization-server/api/auth' | '/.well-known/oauth-authorization-server/api/auth'
fileRoutesByTo: FileRoutesByTo fileRoutesByTo: FileRoutesByTo
to: to:
| '/'
| '/consent' | '/consent'
| '/dashboard' | '/dashboard'
| '/demo' | '/demo'
| '/login'
| '/timeline' | '/timeline'
| '/timelines'
| '/.well-known/oauth-authorization-server' | '/.well-known/oauth-authorization-server'
| '/.well-known/oauth-protected-resource' | '/.well-known/oauth-protected-resource'
| '/.well-known/openid-configuration' | '/.well-known/openid-configuration'
| '/login'
| '/timelines'
| '/timeline/$timelineId' | '/timeline/$timelineId'
| '/'
| '/api/auth/$' | '/api/auth/$'
| '/api/mcp/$' | '/api/mcp/$'
| '/.well-known/oauth-authorization-server/api/auth' | '/.well-known/oauth-authorization-server/api/auth'
id: id:
| '__root__' | '__root__'
| '/' | '/_default'
| '/consent' | '/consent'
| '/dashboard' | '/dashboard'
| '/demo' | '/demo'
| '/login'
| '/timeline' | '/timeline'
| '/timelines'
| '/.well-known/oauth-authorization-server' | '/.well-known/oauth-authorization-server'
| '/.well-known/oauth-protected-resource' | '/.well-known/oauth-protected-resource'
| '/.well-known/openid-configuration' | '/.well-known/openid-configuration'
| '/_default/login'
| '/_default/timelines'
| '/timeline/$timelineId' | '/timeline/$timelineId'
| '/_default/'
| '/api/auth/$' | '/api/auth/$'
| '/api/mcp/$' | '/api/mcp/$'
| '/.well-known/oauth-authorization-server/api/auth' | '/.well-known/oauth-authorization-server/api/auth'
fileRoutesById: FileRoutesById fileRoutesById: FileRoutesById
} }
export interface RootRouteChildren { export interface RootRouteChildren {
IndexRoute: typeof IndexRoute DefaultRoute: typeof DefaultRouteWithChildren
ConsentRoute: typeof ConsentRoute ConsentRoute: typeof ConsentRoute
DashboardRoute: typeof DashboardRoute DashboardRoute: typeof DashboardRoute
DemoRoute: typeof DemoRoute DemoRoute: typeof DemoRoute
LoginRoute: typeof LoginRoute
TimelineRoute: typeof TimelineRouteWithChildren TimelineRoute: typeof TimelineRouteWithChildren
TimelinesRoute: typeof TimelinesRoute
DotwellKnownOauthAuthorizationServerRoute: typeof DotwellKnownOauthAuthorizationServerRouteWithChildren DotwellKnownOauthAuthorizationServerRoute: typeof DotwellKnownOauthAuthorizationServerRouteWithChildren
DotwellKnownOauthProtectedResourceRoute: typeof DotwellKnownOauthProtectedResourceRoute DotwellKnownOauthProtectedResourceRoute: typeof DotwellKnownOauthProtectedResourceRoute
DotwellKnownOpenidConfigurationRoute: typeof DotwellKnownOpenidConfigurationRoute DotwellKnownOpenidConfigurationRoute: typeof DotwellKnownOpenidConfigurationRoute
@@ -216,13 +221,6 @@ export interface RootRouteChildren {
declare module '@tanstack/react-router' { declare module '@tanstack/react-router' {
interface FileRoutesByPath { interface FileRoutesByPath {
'/timelines': {
id: '/timelines'
path: '/timelines'
fullPath: '/timelines'
preLoaderRoute: typeof TimelinesRouteImport
parentRoute: typeof rootRouteImport
}
'/timeline': { '/timeline': {
id: '/timeline' id: '/timeline'
path: '/timeline' path: '/timeline'
@@ -230,13 +228,6 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TimelineRouteImport preLoaderRoute: typeof TimelineRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/demo': { '/demo': {
id: '/demo' id: '/demo'
path: '/demo' path: '/demo'
@@ -258,12 +249,19 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ConsentRouteImport preLoaderRoute: typeof ConsentRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof rootRouteImport
} }
'/': { '/_default': {
id: '/' id: '/_default'
path: ''
fullPath: '/'
preLoaderRoute: typeof DefaultRouteImport
parentRoute: typeof rootRouteImport
}
'/_default/': {
id: '/_default/'
path: '/' path: '/'
fullPath: '/' fullPath: '/'
preLoaderRoute: typeof IndexRouteImport preLoaderRoute: typeof DefaultIndexRouteImport
parentRoute: typeof rootRouteImport parentRoute: typeof DefaultRoute
} }
'/timeline/$timelineId': { '/timeline/$timelineId': {
id: '/timeline/$timelineId' id: '/timeline/$timelineId'
@@ -272,6 +270,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof TimelineTimelineIdRouteImport preLoaderRoute: typeof TimelineTimelineIdRouteImport
parentRoute: typeof TimelineRoute parentRoute: typeof TimelineRoute
} }
'/_default/timelines': {
id: '/_default/timelines'
path: '/timelines'
fullPath: '/timelines'
preLoaderRoute: typeof DefaultTimelinesRouteImport
parentRoute: typeof DefaultRoute
}
'/_default/login': {
id: '/_default/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof DefaultLoginRouteImport
parentRoute: typeof DefaultRoute
}
'/.well-known/openid-configuration': { '/.well-known/openid-configuration': {
id: '/.well-known/openid-configuration' id: '/.well-known/openid-configuration'
path: '/.well-known/openid-configuration' path: '/.well-known/openid-configuration'
@@ -317,6 +329,21 @@ declare module '@tanstack/react-router' {
} }
} }
interface DefaultRouteChildren {
DefaultLoginRoute: typeof DefaultLoginRoute
DefaultTimelinesRoute: typeof DefaultTimelinesRoute
DefaultIndexRoute: typeof DefaultIndexRoute
}
const DefaultRouteChildren: DefaultRouteChildren = {
DefaultLoginRoute: DefaultLoginRoute,
DefaultTimelinesRoute: DefaultTimelinesRoute,
DefaultIndexRoute: DefaultIndexRoute,
}
const DefaultRouteWithChildren =
DefaultRoute._addFileChildren(DefaultRouteChildren)
interface TimelineRouteChildren { interface TimelineRouteChildren {
TimelineTimelineIdRoute: typeof TimelineTimelineIdRoute TimelineTimelineIdRoute: typeof TimelineTimelineIdRoute
} }
@@ -345,13 +372,11 @@ const DotwellKnownOauthAuthorizationServerRouteWithChildren =
) )
const rootRouteChildren: RootRouteChildren = { const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute, DefaultRoute: DefaultRouteWithChildren,
ConsentRoute: ConsentRoute, ConsentRoute: ConsentRoute,
DashboardRoute: DashboardRoute, DashboardRoute: DashboardRoute,
DemoRoute: DemoRoute, DemoRoute: DemoRoute,
LoginRoute: LoginRoute,
TimelineRoute: TimelineRouteWithChildren, TimelineRoute: TimelineRouteWithChildren,
TimelinesRoute: TimelinesRoute,
DotwellKnownOauthAuthorizationServerRoute: DotwellKnownOauthAuthorizationServerRoute:
DotwellKnownOauthAuthorizationServerRouteWithChildren, DotwellKnownOauthAuthorizationServerRouteWithChildren,
DotwellKnownOauthProtectedResourceRoute: DotwellKnownOauthProtectedResourceRoute:

View File

@@ -5,8 +5,6 @@ import {
createRootRouteWithContext, createRootRouteWithContext,
} from "@tanstack/react-router"; } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import Header from "../components/header";
import appCss from "../index.css?url"; import appCss from "../index.css?url";
import type { QueryClient } from "@tanstack/react-query"; import type { QueryClient } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/sonner"; import { Toaster } from "@/components/ui/sonner";
@@ -47,7 +45,7 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
function RootDocument() { function RootDocument() {
return ( return (
<html lang="en"> <html lang="en" suppressHydrationWarning>
<head> <head>
<HeadContent /> <HeadContent />
<script dangerouslySetInnerHTML={{ __html: themeScript }} /> <script dangerouslySetInnerHTML={{ __html: themeScript }} />
@@ -55,7 +53,6 @@ function RootDocument() {
<body> <body>
<ThemeProvider> <ThemeProvider>
<div className="grid min-h-svh grid-rows-[auto_1fr]"> <div className="grid min-h-svh grid-rows-[auto_1fr]">
<Header />
<Outlet /> <Outlet />
</div> </div>
<Toaster richColors /> <Toaster richColors />

View File

@@ -12,7 +12,7 @@ import {
FieldRoot, FieldRoot,
} from "@/components/ui/field"; } from "@/components/ui/field";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/_default/")({
component: HomeComponent, component: HomeComponent,
}); });

View File

@@ -2,7 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import { timelinesQueryOptions } from "@/functions/get-timelines"; import { timelinesQueryOptions } from "@/functions/get-timelines";
export const Route = createFileRoute("/timelines")({ export const Route = createFileRoute("/_default/timelines")({
loader: async ({ context }) => { loader: async ({ context }) => {
await context.queryClient.ensureQueryData(timelinesQueryOptions()); await context.queryClient.ensureQueryData(timelinesQueryOptions());
}, },

View File

@@ -0,0 +1,15 @@
import { Outlet, createFileRoute } from "@tanstack/react-router";
import Header from "@/components/header";
export const Route = createFileRoute("/_default")({
component: RouteComponent,
});
function RouteComponent() {
return (
<div>
<Header />
<Outlet />
</div>
);
}

View File

@@ -1,19 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import SignInForm from "@/components/sign-in-form";
import SignUpForm from "@/components/sign-up-form";
export const Route = createFileRoute("/login")({
component: RouteComponent,
});
function RouteComponent() {
const [showSignIn, setShowSignIn] = useState(false);
return showSignIn ? (
<SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
) : (
<SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
);
}

View File

@@ -1,9 +1,12 @@
import { useCallback, useMemo, useState } from "react"; import { useCallback, useMemo, useState } from "react";
import { createFileRoute } from "@tanstack/react-router"; import { Link, createFileRoute } from "@tanstack/react-router";
import { useSuspenseQuery } from "@tanstack/react-query"; import { useSuspenseQuery } from "@tanstack/react-query";
import { ArrowLeft, Moon, Plus, Sun } from "lucide-react";
import type { FlutterEvent, FlutterTimelineState } from "@/lib/flutter-bridge"; import type { FlutterEvent, FlutterTimelineState } from "@/lib/flutter-bridge";
import { timelineQueryOptions } from "@/functions/get-timeline"; import { timelineQueryOptions } from "@/functions/get-timeline";
import { FlutterView } from "@/components/flutter-view"; import { FlutterView } from "@/components/flutter-view";
import { Button } from "@/components/ui/button";
import UserMenu from "@/components/user-menu";
import { useEntryMovedMutation } from "@/hooks/use-entry-moved-mutation"; import { useEntryMovedMutation } from "@/hooks/use-entry-moved-mutation";
import { useEntryResizedMutation } from "@/hooks/use-entry-resized-mutation"; import { useEntryResizedMutation } from "@/hooks/use-entry-resized-mutation";
import { useTheme } from "@/lib/theme"; import { useTheme } from "@/lib/theme";
@@ -24,7 +27,7 @@ function RouteComponent() {
const [flutterHeight, setFlutterHeight] = useState<number | undefined>(); const [flutterHeight, setFlutterHeight] = useState<number | undefined>();
const entryMoved = useEntryMovedMutation(timelineId); const entryMoved = useEntryMovedMutation(timelineId);
const entryResized = useEntryResizedMutation(timelineId); const entryResized = useEntryResizedMutation(timelineId);
const { theme } = useTheme(); const { theme, toggleTheme } = useTheme();
const flutterState: FlutterTimelineState = useMemo( const flutterState: FlutterTimelineState = useMemo(
() => ({ () => ({
@@ -76,9 +79,40 @@ function RouteComponent() {
return ( return (
<div className="flex flex-col"> <div className="flex flex-col">
<h1 className="text-3xl font-serif font-bold mb-6 mx-4"> <div className="flex flex-row items-center justify-between px-2 py-1">
{timeline.title} <div className="flex items-center gap-2">
</h1> <Link
to="/timelines"
className="text-muted-foreground hover:text-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors"
aria-label="Back to timelines"
>
<ArrowLeft className="size-4" />
</Link>
<h1 className="text-lg font-serif font-bold">{timeline.title}</h1>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm">
<Plus className="size-4" />
Add Item
</Button>
<button
type="button"
onClick={toggleTheme}
className="text-muted-foreground hover:text-foreground inline-flex size-8 items-center justify-center rounded-md transition-colors"
aria-label={
theme === "dark" ? "Switch to light mode" : "Switch to dark mode"
}
>
{theme === "dark" ? (
<Sun className="size-4" />
) : (
<Moon className="size-4" />
)}
</button>
<UserMenu />
</div>
</div>
<hr />
<FlutterView <FlutterView
state={flutterState} state={flutterState}

View File

@@ -3,6 +3,6 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "flutter build web --release --wasm --base-href /flutter/ && node scripts/copy-build.mjs" "build": "flutter build web --release --wasm --base-href /flutter/ --source-maps && node scripts/copy-build.mjs"
} }
} }

View File

@@ -1,6 +1,14 @@
import { cpSync, readFileSync, writeFileSync } from "node:fs"; import {
copyFileSync,
cpSync,
existsSync,
readFileSync,
realpathSync,
writeFileSync,
} from "node:fs";
import { resolve, dirname } from "node:path"; import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import { execFileSync } from "node:child_process";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const src = resolve(__dirname, "../build/web"); const src = resolve(__dirname, "../build/web");
@@ -9,6 +17,22 @@ const dest = resolve(__dirname, "../../../apps/web/public/flutter");
cpSync(src, dest, { recursive: true }); cpSync(src, dest, { recursive: true });
console.log(`Copied Flutter build: ${src}${dest}`); console.log(`Copied Flutter build: ${src}${dest}`);
// Copy flutter.js.map from the Flutter SDK (not included in build output)
const flutterBin = execFileSync("which", ["flutter"], {
encoding: "utf-8",
}).trim();
const sdkBinDir = dirname(realpathSync(flutterBin));
const flutterJsMap = resolve(
sdkBinDir,
"cache/flutter_web_sdk/flutter_js/flutter.js.map",
);
if (existsSync(flutterJsMap)) {
copyFileSync(flutterJsMap, resolve(dest, "flutter.js.map"));
console.log("Copied flutter.js.map from SDK");
} else {
console.warn(`flutter.js.map not found at ${flutterJsMap}`);
}
// Extract buildConfig from flutter_bootstrap.js so the React app can fetch it // Extract buildConfig from flutter_bootstrap.js so the React app can fetch it
const bootstrap = readFileSync(resolve(dest, "flutter_bootstrap.js"), "utf-8"); const bootstrap = readFileSync(resolve(dest, "flutter_bootstrap.js"), "utf-8");
const match = bootstrap.match(/_flutter\.buildConfig\s*=\s*({.*?});/); const match = bootstrap.match(/_flutter\.buildConfig\s*=\s*({.*?});/);