This commit is contained in:
Michael Zhang 2024-05-25 10:11:34 -05:00
parent e3c477181d
commit 5bb6ae6aa3
12 changed files with 222 additions and 59 deletions

View file

@ -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",

View file

@ -2,7 +2,7 @@
display: flex;
align-items: center;
padding: 12px;
padding: 2px 12px;
gap: 12px;
background: rgb(204, 201, 255);

View file

@ -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 (
<div className={styles.Header}>
<span>Panorama</span>
<button type="button">
<NoteAddIcon />
</button>
<SearchBar />
</div>
);

View file

@ -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;
}

View file

@ -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 ? (
<NodeDisplayHeaderLoaded id={id} data={data} />
) : (
<small>ID {id}</small>
<>ID {id}</>
)}
</div>
<div className={styles.title}>
{data.title ?? <span className={styles.untitled}>(untitled)</span>}
</div>
<div className={styles.body}>
{isSuccess ? (
<NodeDisplayLoaded id={id} data={data} />
@ -42,49 +46,23 @@ export default function NodeDisplay({ id }: NodeDisplayProps) {
function NodeDisplayHeaderLoaded({ id, data }) {
return (
<small>
<>
Type {data.type} &middot; Last updated{" "}
<ReactTimeAgo date={data.created_at * 1000} /> &middot; {id}
</small>
</>
);
}
function NodeDisplayLoaded({ id, data }) {
const [value, setValue] = useState(() => data.content);
const [isEditing, setIsEditing] = useState(() => false);
return (
<>
<details>
<summary>JSON</summary>
<pre>{JSON.stringify(data, null, 2)}</pre>
</details>
switch (data.type) {
case "panorama/journal/page":
return <JournalPage id={id} data={data} />;
<button type="button" onClick={() => setIsEditing((prev) => !prev)}>
{isEditing ? "done" : "edit"}
</button>
<div className={styles.mdContent} data-color-mode="light">
{
isEditing ? (
<>
<MDEditor
data-color-mode="light"
className={styles.mdEditor}
value={value}
onChange={setValue}
/>
</>
) : (
<>
<MDEditor.Markdown
source={value}
style={{ whiteSpace: "pre-wrap" }}
/>
</>
)
// <Markdown>{data.content}</Markdown>
}
</div>
</>
);
default:
return (
<>
Don't know how to render node of type <code>{data.type}</code>
</>
);
}
}

View file

@ -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;
}

View file

@ -33,6 +33,7 @@ export default function SearchBar() {
<>
<div>
<input
className={styles.entry}
type="text"
placeholder="Search..."
onFocus={() => setShowMenu(true)}
@ -44,7 +45,6 @@ export default function SearchBar() {
{showMenu && (
<FloatingPortal>
<FloatingOverlay>
{/* <FloatingFocusManager context={context} modal={false}> */}
<div
ref={refs.setFloating}
className={styles.menu}
@ -53,7 +53,6 @@ export default function SearchBar() {
>
<SearchMenu />
</div>
{/* </FloatingFocusManager> */}
</FloatingOverlay>
</FloatingPortal>
)}
@ -61,6 +60,6 @@ export default function SearchBar() {
);
}
function SearchMenu({}) {
return <>Search</>;
function SearchMenu() {
return <>Search suggestions...</>;
}

View file

@ -0,0 +1,9 @@
.mdContent {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.mdEditor {
flex-grow: 1;
}

View file

@ -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 (
<>
<details>
<summary>JSON</summary>
<pre>{JSON.stringify(data, null, 2)}</pre>
</details>
<div className={styles.mdContent} data-color-mode="light"></div>
</>
);
}

View file

@ -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(())

View file

@ -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<String>,
extra_data: Option<HashMap<String, Value>>,
}
pub async fn update_node(
State(state): State<AppState>,
Path(node_id): Path<String>,
Json(update_data): Json<UpdateData>,
) -> AppResult<Json<Value>> {
println!("Update data: {:?}", update_data);
Ok(Json(json!({})))
}
pub async fn node_types() -> AppResult<Json<Value>> {
Ok(Json(json!({
"types": [
{ "id": "panorama/journal/page", "display": "Journal Entry" },
]
})))
}

View file

@ -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