From 52ae6ce480da1dbfc0c558247546f175ead7bd5d Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 27 May 2024 14:44:24 -0500 Subject: [PATCH] update --- README.md | 11 ++ app/package.json | 1 + app/src-tauri/src/main.rs | 10 +- app/src/App.tsx | 6 +- app/src/components/Header.tsx | 8 +- app/src/components/NodeDisplay.module.scss | 41 ++++-- app/src/components/NodeDisplay.tsx | 118 +++++---------- app/src/components/SearchBar.tsx | 32 +++-- app/src/components/Sidebar.module.scss | 2 + app/src/components/Sidebar.tsx | 10 +- .../components/nodes/JournalPage.module.scss | 12 ++ app/src/components/nodes/JournalPage.tsx | 88 +++++++++--- app/src/components/nodes/Mail.module.scss | 25 ++++ app/src/components/nodes/Mail.tsx | 134 ++++++++++++++++++ app/src/global.scss | 4 + app/src/lib/getNode.ts | 34 +++++ crates/panorama-daemon/src/export.rs | 3 - crates/panorama-daemon/src/journal.rs | 3 - crates/panorama-daemon/src/mail.rs | 53 +++++++ crates/panorama-daemon/src/main.rs | 8 +- crates/panorama-daemon/src/migrations.rs | 21 ++- crates/panorama-daemon/src/node.rs | 10 +- pnpm-lock.yaml | 51 +++++++ 23 files changed, 530 insertions(+), 155 deletions(-) create mode 100644 README.md create mode 100644 app/src/components/nodes/Mail.module.scss create mode 100644 app/src/components/nodes/Mail.tsx create mode 100644 app/src/lib/getNode.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..7571104 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +panorama +======== + +Personal information manager. + +Contact +------- + +Author: Michael Zhang + +License: GPL-3.0-only \ No newline at end of file diff --git a/app/package.json b/app/package.json index 80d8b19..a1bc36b 100644 --- a/app/package.json +++ b/app/package.json @@ -22,6 +22,7 @@ "@uiw/react-md-editor": "^4.0.4", "classnames": "^2.5.1", "date-fns": "^3.6.0", + "formik": "^2.4.6", "hast-util-to-jsx-runtime": "^2.3.0", "hast-util-to-mdast": "^10.1.0", "immutable": "^4.3.6", diff --git a/app/src-tauri/src/main.rs b/app/src-tauri/src/main.rs index 523550d..c89e29b 100644 --- a/app/src-tauri/src/main.rs +++ b/app/src-tauri/src/main.rs @@ -4,12 +4,12 @@ // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) + format!("Hello, {}! You've been greeted from Rust!", name) } fn main() { - tauri::Builder::default() - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + tauri::Builder::default() + .invoke_handler(tauri::generate_handler![greet]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/app/src/App.tsx b/app/src/App.tsx index 62bef67..aa22cd4 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -40,9 +40,9 @@ function App() { })(); }, [nodesOpened, openNode]); - const nodes = nodesOpened - .reverse() - .map((nodeId) => ); + const nodes = [...nodesOpened.reverse().values()].map((nodeId, idx) => ( + + )); return ( diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index cb85a18..8cbf1f5 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -5,13 +5,13 @@ import { getVersion } from "@tauri-apps/api/app"; import ListIcon from "@mui/icons-material/List"; import { useSetAtom } from "jotai"; import { sidebarExpandedAtom } from "./Sidebar"; -import { nodesOpenedAtom } from "../App"; +import { nodesOpenedAtom, useOpenNode } from "../App"; import { useCallback } from "react"; const version = await getVersion(); export default function Header() { - const setNodesOpened = useSetAtom(nodesOpenedAtom); + const openNode = useOpenNode(); const setSidebarExpanded = useSetAtom(sidebarExpandedAtom); const createNewJournalPage = useCallback(() => { @@ -29,9 +29,9 @@ export default function Header() { }), }); const data = await resp.json(); - setNodesOpened((prev) => [data.node_id, ...prev]); + openNode(data.node_id); })(); - }, [setNodesOpened]); + }, [openNode]); return ( <> diff --git a/app/src/components/NodeDisplay.module.scss b/app/src/components/NodeDisplay.module.scss index 941e8ae..034a1ee 100644 --- a/app/src/components/NodeDisplay.module.scss +++ b/app/src/components/NodeDisplay.module.scss @@ -14,17 +14,44 @@ } .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; + display: flex; + align-items: center; + gap: 6px; + padding: 0 6px; + + button { + background-color: transparent; + border: none; + display: flex; + justify-content: center; + cursor: pointer; + align-items: center; + + &:hover { + background-color: rgba(0, 0, 0, 0.25); + } + } + + span { + padding: 2px 0; + } } .footer { padding: 2px 12px; font-size: 0.6rem; + align-self: flex-start; + + // For the resize handle + // TODO: Make the entire right side resize and then remove this + width: calc(100% - 20px); + // border: 1px solid red; + // box-sizing: border-box; } .body { @@ -34,16 +61,4 @@ display: flex; flex-direction: column; -} - -.title { - padding: 12px; - border: none; - border-bottom: 1px solid lightgray; - outline: none; - font-size: 1.2em; -} - -.untitled { - color: gray; } \ No newline at end of file diff --git a/app/src/components/NodeDisplay.tsx b/app/src/components/NodeDisplay.tsx index 8d4fa12..cc61fab 100644 --- a/app/src/components/NodeDisplay.tsx +++ b/app/src/components/NodeDisplay.tsx @@ -2,88 +2,44 @@ import { useQuery } from "@tanstack/react-query"; import styles from "./NodeDisplay.module.scss"; import ReactTimeAgo from "react-time-ago"; import JournalPage from "./nodes/JournalPage"; -import { useCallback, useEffect, useState } from "react"; +import { getNode } from "../lib/getNode"; +import FirstPageIcon from "@mui/icons-material/FirstPage"; +import { useOpenNode } from "../App"; export interface NodeDisplayProps { id: string; + idx?: number | undefined; } -export default function NodeDisplay({ id }: NodeDisplayProps) { +export default function NodeDisplay({ id, idx }: NodeDisplayProps) { const query = useQuery({ queryKey: ["fetchNode", id], - queryFn: async () => { - const resp = await fetch(`http://localhost:5195/node/${id}`); - const json = await resp.json(); - return json; - }, + queryFn: getNode, }); - const { isSuccess, status, data } = query; + const { isSuccess, status, data: nodeDescriptor } = query; - const [isEditingTitle, setIsEditingTitle] = useState(false); - const [title, setTitle] = useState(() => - isSuccess && data ? data.title : undefined, - ); - - useEffect(() => { - if (data) { - setTitle(data.title); - } - }, [data]); - - const saveChangedTitle = useCallback(() => { - (async () => { - const resp = await fetch(`http://localhost:5195/node/${id}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ title: title }), - }); - setIsEditingTitle(false); - })(); - }, [title, id]); + let Component = undefined; + let data = undefined; + if (isSuccess) { + Component = nodeDescriptor.render; + data = nodeDescriptor.data; + } return (
{isSuccess ? ( - + ) : ( - <>ID {id} + <> + ID {id} ({status}) + )}
- {isEditingTitle ? ( -
{ - evt.preventDefault(); - saveChangedTitle(); - }} - > - setTitle(evt.target.value)} - onBlur={() => saveChangedTitle()} - // biome-ignore lint/a11y/noAutofocus: - autoFocus - /> - - ) : ( -
setIsEditingTitle(true)} - > - {title ?? (untitled)} -
- )} +
- {isSuccess ? ( - - ) : ( - <>Status: {status} - )} + {Component && }
{id}
@@ -91,25 +47,27 @@ export default function NodeDisplay({ id }: NodeDisplayProps) { ); } -function NodeDisplayHeaderLoaded({ id, data }) { +function NodeDisplayHeaderLoaded({ idx, id, data }) { + const openNode = useOpenNode(); return ( <> - Type {data.type} · Last updated{" "} - + {idx === 0 || ( + + )} + + Type {data.type}{" "} + {data.created_at && ( + <> + · Last updated + + )} + ); } - -function NodeDisplayLoaded({ id, data }) { - switch (data.type) { - case "panorama/journal/page": - return ; - - default: - return ( - <> - Don't know how to render node of type {data.type} - - ); - } -} diff --git a/app/src/components/SearchBar.tsx b/app/src/components/SearchBar.tsx index 299c144..5254ca4 100644 --- a/app/src/components/SearchBar.tsx +++ b/app/src/components/SearchBar.tsx @@ -93,21 +93,23 @@ function SearchMenu({ results }) { return (
- {results.map((result) => ( - - ))} + {results.map((result) => { + return ( + + ); + })}
); } diff --git a/app/src/components/Sidebar.module.scss b/app/src/components/Sidebar.module.scss index 8b46c91..7076b4e 100644 --- a/app/src/components/Sidebar.module.scss +++ b/app/src/components/Sidebar.module.scss @@ -12,6 +12,8 @@ padding: 6px; font-size: 0.95em; border-radius: 4px; + border: none; + background-color: unset; &:hover { background-color: rgba(255, 255, 255, 0.2); diff --git a/app/src/components/Sidebar.tsx b/app/src/components/Sidebar.tsx index 6a359ec..724b56b 100644 --- a/app/src/components/Sidebar.tsx +++ b/app/src/components/Sidebar.tsx @@ -3,11 +3,13 @@ import styles from "./Sidebar.module.scss"; import classNames from "classnames"; import EmailIcon from "@mui/icons-material/Email"; import SettingsIcon from "@mui/icons-material/Settings"; +import { useOpenNode } from "../App"; export const sidebarExpandedAtom = atom(false); export default function Sidebar() { const sidebarExpanded = useAtomValue(sidebarExpandedAtom); + const openNode = useOpenNode(); return (
-
+
+
diff --git a/app/src/components/nodes/JournalPage.module.scss b/app/src/components/nodes/JournalPage.module.scss index 7c9c0a4..d4b3798 100644 --- a/app/src/components/nodes/JournalPage.module.scss +++ b/app/src/components/nodes/JournalPage.module.scss @@ -4,6 +4,18 @@ flex-direction: column; } +.title { + padding: 12px; + border: none; + border-bottom: 1px solid lightgray; + outline: none; + font-size: 1.2em; +} + +.untitled { + color: gray; +} + .mdContent { flex-grow: 1; display: flex; diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx index 76d805a..06abf2d 100644 --- a/app/src/components/nodes/JournalPage.tsx +++ b/app/src/components/nodes/JournalPage.tsx @@ -1,16 +1,18 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import MDEditor, { PreviewType } from "@uiw/react-md-editor"; -import { usePrevious, useDebounce } from "@uidotdev/usehooks"; +import { usePrevious } from "@uidotdev/usehooks"; import { useQueryClient } from "@tanstack/react-query"; import styles from "./JournalPage.module.scss"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; -import remarkEmbedder from "@remark-embedder/core"; import { parse as parseDate, format as formatDate } from "date-fns"; +import { useDebounce } from "use-debounce"; export interface JournalPageProps { id: string; data: { + day?: string; + title?: string; content: string; }; } @@ -19,13 +21,19 @@ export default function JournalPage({ id, data }: JournalPageProps) { const { day } = data; const queryClient = useQueryClient(); const [value, setValue] = useState(() => data.content); - const valueToSave = useDebounce(value, 1000); + const [valueToSave] = useDebounce(value, 1000, { + leading: true, + trailing: true, + }); const previous = usePrevious(valueToSave); const changed = valueToSave !== previous; const [mode, setMode] = useState("preview"); + const [title, setTitle] = useState(() => data.title); + const [isEditingTitle, setIsEditingTitle] = useState(false); useEffect(() => { if (changed) { + // console.log("OLD", previous, "NEW", valueToSave); (async () => { console.log("Saving..."); const resp = await fetch(`http://localhost:5195/node/${id}`, { @@ -47,23 +55,63 @@ export default function JournalPage({ id, data }: JournalPageProps) { } }, [id, changed, valueToSave, queryClient]); - return ( -
- {day && } + const saveChangedTitle = useCallback(() => { + (async () => { + const resp = await fetch(`http://localhost:5195/node/${id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title: title }), + }); + setIsEditingTitle(false); + })(); + }, [title, id]); - newValue !== undefined && setValue(newValue)} - preview={mode} - visibleDragbar={false} - onDoubleClick={() => setMode("live")} - previewOptions={{ - remarkPlugins: [remarkMath], - rehypePlugins: [rehypeKatex], - }} - /> -
+ return ( + <> + {isEditingTitle ? ( +
{ + evt.preventDefault(); + saveChangedTitle(); + }} + > + setTitle(evt.target.value)} + onBlur={() => saveChangedTitle()} + // biome-ignore lint/a11y/noAutofocus: + autoFocus + /> + + ) : ( +
setIsEditingTitle(true)} + > + {title ?? (untitled)} +
+ )} +
+ {day && } + + newValue !== undefined && setValue(newValue)} + preview={mode} + visibleDragbar={false} + onDoubleClick={() => setMode("live")} + previewOptions={{ + remarkPlugins: [remarkMath], + rehypePlugins: [rehypeKatex], + }} + /> +
+ ); } diff --git a/app/src/components/nodes/Mail.module.scss b/app/src/components/nodes/Mail.module.scss new file mode 100644 index 0000000..18d3395 --- /dev/null +++ b/app/src/components/nodes/Mail.module.scss @@ -0,0 +1,25 @@ +.container { + display: flex; + flex-direction: column; +} + +.header { + display: flex; + align-items: center; + padding: 2px 12px; + + .title { + font-size: 1rem; + } +} + +.settings { + h2 { + margin: 0; + } +} + +.mailList { + flex-grow: 1; + border-top: 1px solid lightgray; +} \ No newline at end of file diff --git a/app/src/components/nodes/Mail.tsx b/app/src/components/nodes/Mail.tsx new file mode 100644 index 0000000..257eff6 --- /dev/null +++ b/app/src/components/nodes/Mail.tsx @@ -0,0 +1,134 @@ +import { useCallback, useState } from "react"; +import styles from "./Mail.module.scss"; +import { Formik } from "formik"; +import SettingsIcon from "@mui/icons-material/Settings"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +export default function Mail() { + const [showSettings, setShowSettings] = useState(false); + + return ( +
+
+
+ {showSettings ? <>Settings : <>Mail} +
+
+ +
+ + {showSettings && ( +
+ +
+ )} + +
+
+ ); +} + +function MailConfig() { + const queryClient = useQueryClient(); + const config = useQuery({ + queryKey: ["mailConfigs"], + queryFn: fetchMailConfig, + }); + + const { isSuccess, data } = config; + + return ( + <> +
+ Add a new config + { + (async () => { + const resp = await fetch("http://localhost:5195/node", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "panorama/mail/config", + extra_data: { + "panorama/mail/config/imap_hostname": values.imapHostname, + "panorama/mail/config/imap_port": values.imapPort, + "panorama/mail/config/imap_username": values.imapUsername, + "panorama/mail/config/imap_password": values.imapPassword, + }, + }), + }); + const data = await resp.json(); + console.log("result", data); + queryClient.invalidateQueries({ queryKey: ["mailConfigs"] }); + })(); + }} + initialValues={{ + imapHostname: "", + imapPort: 993, + imapUsername: "", + imapPassword: "", + }} + > + {({ values, handleSubmit, handleChange, handleBlur }) => ( +
+ + +
+ + + +
+ )} +
+
+ +
+ {isSuccess && ( +
    + {data.map((config) => ( +
  • {JSON.stringify(config)}
  • + ))} +
+ )} +
+ + ); +} + +async function fetchMailConfig() { + const resp = await fetch("http://localhost:5195/mail/config"); + const data = await resp.json(); + return data.configs; +} diff --git a/app/src/global.scss b/app/src/global.scss index e43dedc..cb9ead2 100644 --- a/app/src/global.scss +++ b/app/src/global.scss @@ -16,4 +16,8 @@ html, .spacer { flex-grow: 1; +} + +* { + box-sizing: border-box; } \ No newline at end of file diff --git a/app/src/lib/getNode.ts b/app/src/lib/getNode.ts new file mode 100644 index 0000000..915b8fa --- /dev/null +++ b/app/src/lib/getNode.ts @@ -0,0 +1,34 @@ +import { Component, FC } from "react"; +import JournalPage from "../components/nodes/JournalPage"; +import Mail from "../components/nodes/Mail"; +import { QueryFunctionContext } from "@tanstack/react-query"; + +export interface RenderProps { + id: string; + data: any; +} + +export interface NodeDescriptor { + render: FC; + data: { + type: string; + } & any; +} + +export async function getNode({ + queryKey, +}: QueryFunctionContext): Promise { + const [, node_id] = queryKey; + switch (node_id) { + case "panorama/mail": + return { data: { type: "panorama/mail" }, render: Mail }; + default: { + const resp = await fetch(`http://localhost:5195/node/${node_id}`); + const data = await resp.json(); + return { + data: { ...data, type: "panorama/journal/page" }, + render: JournalPage, + }; + } + } +} diff --git a/crates/panorama-daemon/src/export.rs b/crates/panorama-daemon/src/export.rs index f34f951..6e2e7c6 100644 --- a/crates/panorama-daemon/src/export.rs +++ b/crates/panorama-daemon/src/export.rs @@ -47,8 +47,6 @@ pub async fn export(State(state): State) -> AppResult<()> { relation_columns.insert(relation_name.clone(), columns); } - println!("columns: {relation_columns:?}"); - let base_dir = PathBuf::from("export"); fs::create_dir_all(&base_dir); @@ -68,7 +66,6 @@ pub async fn export(State(state): State) -> AppResult<()> { .join(", "); let query = format!("?[{columns}] := *{relation_name} {{ {columns} }}"); - println!("Query: {query}"); let result = tx.run_script(&query, Default::default())?; writer.write_record(result.headers).unwrap(); diff --git a/crates/panorama-daemon/src/journal.rs b/crates/panorama-daemon/src/journal.rs index fd3bbcc..c9c681e 100644 --- a/crates/panorama-daemon/src/journal.rs +++ b/crates/panorama-daemon/src/journal.rs @@ -10,7 +10,6 @@ pub async fn get_todays_journal_id( State(state): State, ) -> AppResult> { let today = todays_date(); - println!("Getting journal id for {today}!"); let result = state.db.run_script( " @@ -22,8 +21,6 @@ pub async fn get_todays_journal_id( ScriptMutability::Immutable, )?; - println!("Result: {:?}", result); - // TODO: Do this check on the server side if result.rows.len() == 0 { // Insert a new one diff --git a/crates/panorama-daemon/src/mail.rs b/crates/panorama-daemon/src/mail.rs index e69de29..3e8516e 100644 --- a/crates/panorama-daemon/src/mail.rs +++ b/crates/panorama-daemon/src/mail.rs @@ -0,0 +1,53 @@ +use axum::{extract::State, Json}; +use cozo::{DbInstance, ScriptMutability}; +use serde_json::Value; + +use crate::{error::AppResult, AppState}; + +pub async fn get_mail_config( + State(state): State, +) -> AppResult> { + let configs = fetch_mail_configs(&state.db)?; + Ok(Json(json!({ + "configs": configs, + }))) +} + +pub async fn mail_loop(db: DbInstance) { + // Fetch the mail configs +} + +#[derive(Serialize)] +struct MailConfig { + node_id: String, + imap_hostname: String, + imap_port: u16, + imap_username: String, + imap_password: String, +} + +fn fetch_mail_configs(db: &DbInstance) -> AppResult> { + let result = db.run_script( + " + ?[node_id, imap_hostname, imap_port, imap_username, imap_password] := + *node{ id: node_id }, + *mail_config{ node_id, imap_hostname, imap_port, imap_username, imap_password } + ", + Default::default(), + ScriptMutability::Immutable, + )?; + + let result = result + .rows + .into_iter() + .map(|row| MailConfig { + node_id: row[0].get_str().unwrap().to_owned(), + imap_hostname: row[1].get_str().unwrap().to_owned(), + imap_port: row[2].get_int().unwrap() as u16, + imap_username: row[3].get_str().unwrap().to_owned(), + imap_password: row[4].get_str().unwrap().to_owned(), + }) + .collect::>(); + + Ok(result) +} diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs index e8bd0a4..838d232 100644 --- a/crates/panorama-daemon/src/main.rs +++ b/crates/panorama-daemon/src/main.rs @@ -10,6 +10,7 @@ extern crate sugars; mod error; mod export; mod journal; +mod mail; mod migrations; mod node; mod query_builder; @@ -31,6 +32,7 @@ use tower_http::cors::{self, CorsLayer}; use crate::{ export::export, journal::get_todays_journal_id, + mail::{get_mail_config, mail_loop}, migrations::run_migrations, node::{create_node, get_node, node_types, search_nodes, update_node}, }; @@ -56,6 +58,8 @@ async fn main() -> Result<()> { run_migrations(&db).await?; + tokio::spawn(mail_loop(db.clone())); + let state = AppState { db }; let cors = CorsLayer::new() @@ -73,6 +77,7 @@ async fn main() -> Result<()> { .route("/node/:id", post(update_node)) .route("/node/types", get(node_types)) .route("/journal/get_todays_journal_id", get(get_todays_journal_id)) + .route("/mail/config", get(get_mail_config)) .layer(ServiceBuilder::new().layer(cors)) .with_state(state); @@ -88,7 +93,8 @@ pub fn ensure_ok(s: &str) -> Result<()> { let status = status.as_object().unwrap(); let ok = status.get("ok").unwrap().as_bool().unwrap_or(false); if !ok { - bail!("shit (error: {s})") + let display = status.get("display").unwrap().as_str().unwrap(); + bail!("shit (error: {display})") } Ok(()) } diff --git a/crates/panorama-daemon/src/migrations.rs b/crates/panorama-daemon/src/migrations.rs index f4cd53d..742b8a4 100644 --- a/crates/panorama-daemon/src/migrations.rs +++ b/crates/panorama-daemon/src/migrations.rs @@ -51,6 +51,7 @@ pub async fn run_migrations(db: &DbInstance) -> Result<()> { &format!("{{\"version\":{}}}", idx), false, ); + ensure_ok(&result)?; println!("succeeded migration {idx}!"); @@ -128,7 +129,11 @@ fn migration_01(db: &DbInstance) -> Result<()> { } { ?[key, relation, field_name, type] <- [ - ['panorama/journal/page/content', 'journal', 'content', 'string'] + ['panorama/journal/page/content', 'journal', 'content', 'string'], + ['panorama/mail/config/imap_hostname', 'mail_config', 'imap_hostname', 'string'], + ['panorama/mail/config/imap_port', 'mail_config', 'imap_port', 'int'], + ['panorama/mail/config/imap_username', 'mail_config', 'imap_username', 'string'], + ['panorama/mail/config/imap_password', 'mail_config', 'imap_password', 'string'], ] :put fqkey_to_dbkey { key, relation, field_name, type } } @@ -144,6 +149,20 @@ fn migration_01(db: &DbInstance) -> Result<()> { filters: [Lowercase, Stemmer('english'), Stopwords('en')], } } + + # Mail + { + :create mail_config { + node_id: String + => + imap_hostname: String, + imap_port: Int, + imap_username: String, + imap_password: String, + } + } + + # Calendar ", "", false, diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs index cec251f..99a667a 100644 --- a/crates/panorama-daemon/src/node.rs +++ b/crates/panorama-daemon/src/node.rs @@ -70,7 +70,6 @@ pub async fn update_node( Path(node_id): Path, Json(update_data): Json, ) -> AppResult> { - println!("Update data: {:?}", update_data); let node_id_data = DataValue::from(node_id.clone()); // TODO: Combine these into the same script @@ -158,7 +157,6 @@ pub async fn create_node( ) -> AppResult> { let node_id = Uuid::now_v7(); let node_id = node_id.to_string(); - println!("Opts: {opts:?}"); let tx = state.db.multi_transaction(true); @@ -179,15 +177,17 @@ pub async fn create_node( let result_by_relation = result .iter() .into_group_map_by(|(key, (relation, field_name, ty))| relation); - println!("Result by relation: {result_by_relation:?}"); for (relation, fields) in result_by_relation.iter() { let fields_mapping = fields .into_iter() - .map(|(key, (_, field_name, _))| { + .map(|(key, (_, field_name, ty))| { let new_value = extra_data.get(*key).unwrap(); // TODO: Make this more generic - let new_value = DataValue::from(new_value.as_str().unwrap()); + let new_value = match ty.as_str() { + "int" => DataValue::from(new_value.as_i64().unwrap()), + _ => DataValue::from(new_value.as_str().unwrap()), + }; (field_name.to_owned(), new_value) }) .collect::>(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f56528c..312502d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: date-fns: specifier: ^3.6.0 version: 3.6.0 + formik: + specifier: ^2.4.6 + version: 2.4.6(react@18.3.1) hast-util-to-jsx-runtime: specifier: ^2.3.0 version: 2.3.0 @@ -1257,6 +1260,13 @@ packages: '@types/unist': 3.0.2 dev: false + /@types/hoist-non-react-statics@3.3.5: + resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} + dependencies: + '@types/react': 18.3.3 + hoist-non-react-statics: 3.3.2 + dev: false + /@types/katex@0.16.7: resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} dev: false @@ -1567,6 +1577,11 @@ packages: character-entities: 2.0.2 dev: false + /deepmerge@2.2.1: + resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==} + engines: {node: '>=0.10.0'} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1674,6 +1689,22 @@ packages: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} dev: false + /formik@2.4.6(react@18.3.1): + resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==} + peerDependencies: + react: '>=16.8.0' + dependencies: + '@types/hoist-non-react-statics': 3.3.5 + deepmerge: 2.2.1 + hoist-non-react-statics: 3.3.2 + lodash: 4.17.21 + lodash-es: 4.17.21 + react: 18.3.1 + react-fast-compare: 2.0.4 + tiny-warning: 1.0.3 + tslib: 2.6.2 + dev: false + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2101,6 +2132,14 @@ packages: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: false + /lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + /longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} dev: false @@ -2713,6 +2752,10 @@ packages: scheduler: 0.23.2 dev: false + /react-fast-compare@2.0.4: + resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: false @@ -3099,6 +3142,10 @@ packages: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} dev: false + /tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + dev: false + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -3122,6 +3169,10 @@ packages: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} dev: false + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + /typescript@5.4.5: resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} engines: {node: '>=14.17'}