diff --git a/app/package.json b/app/package.json index 79aabaf..4176401 100644 --- a/app/package.json +++ b/app/package.json @@ -18,7 +18,11 @@ "@tanstack/react-query": "^5.37.1", "@tauri-apps/api": "^1", "@uiw/react-md-editor": "^4.0.4", + "hast-util-to-mdast": "^10.1.0", "javascript-time-ago": "^2.5.10", + "jotai": "^2.8.1", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^9.0.1", diff --git a/app/src/components/Header.module.scss b/app/src/components/Header.module.scss index 9804ffb..f6eb526 100644 --- a/app/src/components/Header.module.scss +++ b/app/src/components/Header.module.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; - padding: 12px; + padding: 2px 12px; gap: 12px; background: rgb(204, 201, 255); diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index c573206..a88315c 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -1,10 +1,14 @@ import styles from "./Header.module.scss"; +import NoteAddIcon from "@mui/icons-material/NoteAdd"; import SearchBar from "./SearchBar"; export default function Header() { return (
Panorama +
); diff --git a/app/src/components/NodeDisplay.module.scss b/app/src/components/NodeDisplay.module.scss index 892d869..ca56f97 100644 --- a/app/src/components/NodeDisplay.module.scss +++ b/app/src/components/NodeDisplay.module.scss @@ -7,24 +7,17 @@ border-radius: 4px; display: flex; + align-items: stretch; flex-direction: column; } -.mdContent { - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.mdEditor { - flex-grow: 1; -} - .header { padding: 2px 12px; color: rgb(106, 103, 160); background: rgb(204, 201, 255); background: linear-gradient(90deg, rgba(204, 201, 255, 1) 0%, rgba(255, 255, 255, 1) 100%); + + font-size: 0.6rem; } .body { @@ -34,4 +27,13 @@ display: flex; flex-direction: column; +} + +.title { + padding: 12px; + border-bottom: 1px solid lightgray; +} + +.untitled { + color: gray; } \ No newline at end of file diff --git a/app/src/components/NodeDisplay.tsx b/app/src/components/NodeDisplay.tsx index 6540146..f4da92d 100644 --- a/app/src/components/NodeDisplay.tsx +++ b/app/src/components/NodeDisplay.tsx @@ -4,6 +4,7 @@ import ReactTimeAgo from "react-time-ago"; import Markdown from "react-markdown"; import MDEditor, { commands } from "@uiw/react-md-editor"; import { useState } from "react"; +import JournalPage from "./nodes/JournalPage"; export interface NodeDisplayProps { id: string; @@ -26,9 +27,12 @@ export default function NodeDisplay({ id }: NodeDisplayProps) { {isSuccess ? ( ) : ( - ID {id} + <>ID {id} )} +
+ {data.title ?? (untitled)} +
{isSuccess ? ( @@ -42,49 +46,23 @@ export default function NodeDisplay({ id }: NodeDisplayProps) { function NodeDisplayHeaderLoaded({ id, data }) { return ( - + <> Type {data.type} · Last updated{" "} · {id} - + ); } function NodeDisplayLoaded({ id, data }) { - const [value, setValue] = useState(() => data.content); - const [isEditing, setIsEditing] = useState(() => false); - return ( - <> -
- JSON -
{JSON.stringify(data, null, 2)}
-
+ switch (data.type) { + case "panorama/journal/page": + return ; - - -
- { - isEditing ? ( - <> - - - ) : ( - <> - - - ) - // {data.content} - } -
- - ); + default: + return ( + <> + Don't know how to render node of type {data.type} + + ); + } } diff --git a/app/src/components/SearchBar.module.scss b/app/src/components/SearchBar.module.scss index df61ce4..b802f46 100644 --- a/app/src/components/SearchBar.module.scss +++ b/app/src/components/SearchBar.module.scss @@ -2,8 +2,20 @@ background-color: rgba(255, 255, 255, 0.5); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(10px); - // box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.25); + box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.25); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; min-width: 500px; min-height: 200px; + + padding: 12px; +} + +.entry { + padding: 2px 6px; + font-size: 14pt; + border-radius: 4px; + border: none; + outline: none; } \ No newline at end of file diff --git a/app/src/components/SearchBar.tsx b/app/src/components/SearchBar.tsx index 4b00259..9d81ebb 100644 --- a/app/src/components/SearchBar.tsx +++ b/app/src/components/SearchBar.tsx @@ -33,6 +33,7 @@ export default function SearchBar() { <>
setShowMenu(true)} @@ -44,7 +45,6 @@ export default function SearchBar() { {showMenu && ( - {/* */}
- {/*
*/}
)} @@ -61,6 +60,6 @@ export default function SearchBar() { ); } -function SearchMenu({}) { - return <>Search; +function SearchMenu() { + return <>Search suggestions...; } diff --git a/app/src/components/nodes/JournalPage.module.scss b/app/src/components/nodes/JournalPage.module.scss new file mode 100644 index 0000000..3622f1f --- /dev/null +++ b/app/src/components/nodes/JournalPage.module.scss @@ -0,0 +1,9 @@ +.mdContent { + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.mdEditor { + flex-grow: 1; +} \ No newline at end of file diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx new file mode 100644 index 0000000..84eef03 --- /dev/null +++ b/app/src/components/nodes/JournalPage.tsx @@ -0,0 +1,29 @@ +import { createContext, useCallback, useContext, useState } from "react"; +import styles from "./JournalPage.module.scss"; +import MDEditor from "@uiw/react-md-editor"; +import Markdown from "react-markdown"; +import { toMdast } from "hast-util-to-mdast"; +import { fromMarkdown } from "mdast-util-from-markdown"; +import { toMarkdown } from "mdast-util-to-markdown"; + +const MDContext = createContext(null); + +export default function JournalPage({ id, data }) { + const [content, setContent] = useState(() => data.content); + const [isEditing, setIsEditing] = useState(() => false); + + const tree = fromMarkdown(data.content); + console.log("tree", tree); + + const contextValue = { content, setContent, isEditing, setIsEditing }; + return ( + <> +
+ JSON +
{JSON.stringify(data, null, 2)}
+
+ +
+ + ); +} diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs index 5428416..10948ec 100644 --- a/crates/panorama-daemon/src/main.rs +++ b/crates/panorama-daemon/src/main.rs @@ -29,7 +29,7 @@ use tower_http::cors::{self, CorsLayer}; use crate::{ journal::get_todays_journal_id, migrations::run_migrations, - node::{get_node, update_node}, + node::{get_node, node_types, update_node}, }; #[derive(Clone)] @@ -66,12 +66,13 @@ async fn main() -> Result<()> { .route("/", get(|| async { "Hello, World!" })) .route("/node/:id", get(get_node)) .route("/node/:id", post(update_node)) + .route("/node/types", get(node_types)) .route("/journal/get_todays_journal_id", get(get_todays_journal_id)) .layer(ServiceBuilder::new().layer(cors)) .with_state(state); let listener = TcpListener::bind("0.0.0.0:5195").await?; - println!("Listening..."); + println!("Listening... {:?}", listener); axum::serve(listener, app).await?; Ok(()) diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs index 9fa6a16..c1f4664 100644 --- a/crates/panorama-daemon/src/node.rs +++ b/crates/panorama-daemon/src/node.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use axum::{ extract::{Path, State}, http::StatusCode, @@ -54,12 +56,26 @@ pub async fn get_node( )) } -#[derive(Deserialize)] -struct NodeUpdate {} +#[derive(Deserialize, Debug)] +pub struct UpdateData { + title: Option, + extra_data: Option>, +} pub async fn update_node( State(state): State, Path(node_id): Path, + Json(update_data): Json, ) -> AppResult> { + println!("Update data: {:?}", update_data); + Ok(Json(json!({}))) } + +pub async fn node_types() -> AppResult> { + Ok(Json(json!({ + "types": [ + { "id": "panorama/journal/page", "display": "Journal Entry" }, + ] + }))) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8cf89d..b2b0c4a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,21 @@ importers: '@uiw/react-md-editor': specifier: ^4.0.4 version: 4.0.4(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + hast-util-to-mdast: + specifier: ^10.1.0 + version: 10.1.0 javascript-time-ago: specifier: ^2.5.10 version: 2.5.10 + jotai: + specifier: ^2.8.1 + version: 2.8.1(@types/react@18.3.3)(react@18.3.1) + mdast-util-from-markdown: + specifier: ^2.0.0 + version: 2.0.0 + mdast-util-to-markdown: + specifier: ^2.1.0 + version: 2.1.0 react: specifier: ^18.2.0 version: 18.3.1 @@ -954,6 +966,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + hast-util-from-html@2.0.1: resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} @@ -966,6 +981,9 @@ packages: hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} + hast-util-is-body-ok-link@3.0.0: + resolution: {integrity: sha512-VFHY5bo2nY8HiV6nir2ynmEB1XkxzuUffhEGeVx7orbu/B1KaGyeGgMZldvMVx5xWrDlLLG/kQ6YkJAMkBEx0w==} + hast-util-is-element@3.0.0: resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} @@ -975,6 +993,9 @@ packages: hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + hast-util-raw@9.0.3: resolution: {integrity: sha512-ICWvVOF2fq4+7CMmtCPD5CM4QKjPbHpPotE6+8tDooV0ZuyJVUzHsrNX+O5NaRbieTf0F7FfeBOMAwi6Td0+yQ==} @@ -987,12 +1008,18 @@ packages: hast-util-to-jsx-runtime@2.3.0: resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + hast-util-to-mdast@10.1.0: + resolution: {integrity: sha512-DsL/SvCK9V7+vfc6SLQ+vKIyBDXTk2KLSbfBYkH4zeF/uR1yBajHRhkzuaUSGOB1WJSTieJBdHwxlC+HLKvZZw==} + hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} hast-util-to-string@3.0.0: resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} @@ -1062,6 +1089,18 @@ packages: javascript-time-ago@2.5.10: resolution: {integrity: sha512-EUxp4BP74QH8xiYHyeSHopx1XhMMJ9qEX4rcBdFtpVWmKRdzpxbNzz2GSbuekZr5wt0rmLehuyp0PE34EAJT9g==} + jotai@2.8.1: + resolution: {integrity: sha512-Gmk5Y3yJL/vN5S0rQ6AaWpXH5Q+HBGHThMHXfylVzXGVuO8YxPRtZf8Y9XYvl+h7ZMQXoHNdFi37vNsJFsiszQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1357,6 +1396,9 @@ packages: resolution: {integrity: sha512-BpAT/3lU9DMJ2siYVD/dSR0A/zQgD6Fb+fxkJd4j+wDVy6TYbYpK+FZqu8eM9EuNKGvi4BJR7XTZ/+zF02Dq8w==} engines: {node: '>=16'} + rehype-minify-whitespace@6.0.0: + resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==} + rehype-parse@9.0.0: resolution: {integrity: sha512-WG7nfvmWWkCR++KEkZevZb/uw41E8TsH4DsY9UxsTbIXCVGbAs4S+r8FrQ+OtH5EEQAs+5UxKC42VinkmpA1Yw==} @@ -1465,6 +1507,9 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-trailing-lines@2.1.0: + resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} + trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -1479,6 +1524,9 @@ packages: unist-util-filter@5.0.1: resolution: {integrity: sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -2388,6 +2436,11 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element: 3.0.0 + hast-util-from-html@2.0.1: dependencies: '@types/hast': 3.0.4 @@ -2416,6 +2469,10 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast-util-is-body-ok-link@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -2428,6 +2485,14 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-raw@9.0.3: dependencies: '@types/hast': 3.0.4 @@ -2498,6 +2563,23 @@ snapshots: transitivePeerDependencies: - supports-color + hast-util-to-mdast@10.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.0 + hast-util-phrasing: 3.0.1 + hast-util-to-html: 9.0.1 + hast-util-to-text: 4.0.2 + hast-util-whitespace: 3.0.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-hast: 13.1.0 + mdast-util-to-string: 4.0.0 + rehype-minify-whitespace: 6.0.0 + trim-trailing-lines: 2.1.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + hast-util-to-parse5@8.0.0: dependencies: '@types/hast': 3.0.4 @@ -2512,6 +2594,13 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.2 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -2584,6 +2673,11 @@ snapshots: dependencies: relative-time-format: 1.1.6 + jotai@2.8.1(@types/react@18.3.3)(react@18.3.1): + optionalDependencies: + '@types/react': 18.3.3 + react: 18.3.1 + js-tokens@4.0.0: {} jsesc@2.5.2: {} @@ -3108,6 +3202,14 @@ snapshots: unified: 11.0.4 unist-util-visit: 5.0.0 + rehype-minify-whitespace@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.0 + rehype-parse@9.0.0: dependencies: '@types/hast': 3.0.4 @@ -3271,6 +3373,8 @@ snapshots: trim-lines@3.0.1: {} + trim-trailing-lines@2.1.0: {} + trough@2.2.0: {} typescript@5.4.5: {} @@ -3291,6 +3395,11 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.2