From 508a7cbf5e17d53dffb1f15463d51d4738c2e22b Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 27 May 2024 00:43:09 -0500 Subject: [PATCH] search endpoint --- app/package.json | 2 + app/src-tauri/Cargo.toml | 2 +- app/src-tauri/tauri.conf.json | 7 ++- app/src/App.module.scss | 5 +- app/src/App.tsx | 7 ++- app/src/components/Header.module.scss | 22 +++++++++ app/src/components/Header.tsx | 33 ++++++++++--- app/src/components/NodeDisplay.tsx | 4 +- app/src/components/Sidebar.module.scss | 29 +++++++++++ app/src/components/Sidebar.tsx | 32 ++++++++++++ .../components/nodes/JournalPage.module.scss | 7 ++- app/src/components/nodes/JournalPage.tsx | 25 ++++++++-- app/src/global.scss | 4 ++ crates/panorama-daemon/src/mail.rs | 0 crates/panorama-daemon/src/main.rs | 6 ++- crates/panorama-daemon/src/node.rs | 49 ++++++++++++++++++- pnpm-lock.yaml | 23 +++++++++ 17 files changed, 237 insertions(+), 20 deletions(-) create mode 100644 app/src/components/Sidebar.module.scss create mode 100644 app/src/components/Sidebar.tsx create mode 100644 crates/panorama-daemon/src/mail.rs diff --git a/app/package.json b/app/package.json index 8b768c3..96823fd 100644 --- a/app/package.json +++ b/app/package.json @@ -15,11 +15,13 @@ "@fontsource/inter": "^5.0.18", "@mui/icons-material": "^5.15.18", "@mui/material": "^5.15.18", + "@remark-embedder/core": "^3.0.3", "@tanstack/react-query": "^5.37.1", "@tauri-apps/api": "^1", "@uidotdev/usehooks": "^2.4.1", "@uiw/react-md-editor": "^4.0.4", "classnames": "^2.5.1", + "date-fns": "^3.6.0", "hast-util-to-jsx-runtime": "^2.3.0", "hast-util-to-mdast": "^10.1.0", "javascript-time-ago": "^2.5.10", diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 3b81fb1..0d6c15c 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" tauri-build = { version = "1", features = [] } [dependencies] -tauri = { version = "1", features = [ "http-request", "shell-open"] } +tauri = { version = "1", features = [ "app-all", "http-request", "shell-open"] } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index bf2af5b..600cbe4 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -7,11 +7,16 @@ }, "package": { "productName": "panorama", - "version": "0.0.0" + "version": "0.1.0" }, "tauri": { "allowlist": { "all": false, + "app": { + "all": true, + "hide": true, + "show": true + }, "shell": { "all": false, "open": true diff --git a/app/src/App.module.scss b/app/src/App.module.scss index d2eb2d1..b52331b 100644 --- a/app/src/App.module.scss +++ b/app/src/App.module.scss @@ -8,9 +8,12 @@ height: 100%; } -.nodeContainer { +.main { flex-grow: 1; + display: flex; +} +.nodeContainer { display: flex; justify-items: stretch; diff --git a/app/src/App.tsx b/app/src/App.tsx index 07a2fea..6fe1e3a 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -2,6 +2,7 @@ import Header from "./components/Header"; import styles from "./App.module.scss"; import "@fontsource/inter"; +import "@fontsource/inter/700.css"; import "./global.scss"; import "katex/dist/katex.min.css"; import { useEffect, useState } from "react"; @@ -9,6 +10,7 @@ import NodeDisplay from "./components/NodeDisplay"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import TimeAgo from "javascript-time-ago"; import en from "javascript-time-ago/locale/en"; +import Sidebar from "./components/Sidebar"; const queryClient = new QueryClient(); @@ -41,7 +43,10 @@ function App() {
-
{nodes}
+
+ +
{nodes}
+
); diff --git a/app/src/components/Header.module.scss b/app/src/components/Header.module.scss index f6eb526..38f05fe 100644 --- a/app/src/components/Header.module.scss +++ b/app/src/components/Header.module.scss @@ -7,4 +7,26 @@ background: rgb(204, 201, 255); background: linear-gradient(90deg, rgba(204, 201, 255, 1) 0%, rgba(255, 255, 255, 1) 100%); +} + +.headerBorder { + height: 1px; + background: rgb(170, 166, 255); + background: linear-gradient(90deg, rgba(170, 166, 255, 1) 0%, rgba(255, 255, 255, 1) 100%); +} + +.brand { + display: flex; + flex-direction: column; + + .title { + font-size: 1.2em; + margin: 0; + } + + .version { + font-size: .6em; + color: rgb(0, 0, 0, 0.5); + margin: 0; + } } \ No newline at end of file diff --git a/app/src/components/Header.tsx b/app/src/components/Header.tsx index a88315c..1f48b50 100644 --- a/app/src/components/Header.tsx +++ b/app/src/components/Header.tsx @@ -1,15 +1,34 @@ import styles from "./Header.module.scss"; import NoteAddIcon from "@mui/icons-material/NoteAdd"; import SearchBar from "./SearchBar"; +import { getVersion } from "@tauri-apps/api/app"; +import ListIcon from "@mui/icons-material/List"; +import { useSetAtom } from "jotai"; +import { sidebarExpandedAtom } from "./Sidebar"; + +const version = await getVersion(); export default function Header() { + const setSidebarExpanded = useSetAtom(sidebarExpandedAtom); return ( -
- Panorama - - -
+ <> +
+ +
+ Panorama + v{version} +
+ + +
+
+ ); } diff --git a/app/src/components/NodeDisplay.tsx b/app/src/components/NodeDisplay.tsx index c9c19db..0524454 100644 --- a/app/src/components/NodeDisplay.tsx +++ b/app/src/components/NodeDisplay.tsx @@ -26,7 +26,9 @@ export default function NodeDisplay({ id }: NodeDisplayProps) { ); useEffect(() => { - setTitle(data.title); + if (data) { + setTitle(data.title); + } }, [data]); const saveChangedTitle = useCallback(() => { diff --git a/app/src/components/Sidebar.module.scss b/app/src/components/Sidebar.module.scss new file mode 100644 index 0000000..8b46c91 --- /dev/null +++ b/app/src/components/Sidebar.module.scss @@ -0,0 +1,29 @@ +.sidebar { + display: flex; + flex-direction: column; + gap: 6px; + + background-color: rgba(204, 201, 255); + padding: 6px; + + .item { + display: flex; + align-items: center; + padding: 6px; + font-size: 0.95em; + border-radius: 4px; + + &:hover { + background-color: rgba(255, 255, 255, 0.2); + cursor: pointer; + } + } + + &.expanded .item { + gap: 6px; + } + + &.collapsed .item .label { + display: none; + } +} \ No newline at end of file diff --git a/app/src/components/Sidebar.tsx b/app/src/components/Sidebar.tsx new file mode 100644 index 0000000..6a359ec --- /dev/null +++ b/app/src/components/Sidebar.tsx @@ -0,0 +1,32 @@ +import { atom, useAtomValue } from "jotai"; +import styles from "./Sidebar.module.scss"; +import classNames from "classnames"; +import EmailIcon from "@mui/icons-material/Email"; +import SettingsIcon from "@mui/icons-material/Settings"; + +export const sidebarExpandedAtom = atom(false); + +export default function Sidebar() { + const sidebarExpanded = useAtomValue(sidebarExpandedAtom); + + return ( +
+
+ + Email +
+ +
+ +
+ + Settings +
+
+ ); +} diff --git a/app/src/components/nodes/JournalPage.module.scss b/app/src/components/nodes/JournalPage.module.scss index a3badcc..7c9c0a4 100644 --- a/app/src/components/nodes/JournalPage.module.scss +++ b/app/src/components/nodes/JournalPage.module.scss @@ -2,7 +2,6 @@ flex-grow: 1; display: flex; flex-direction: column; - gap: 12px; } .mdContent { @@ -17,6 +16,12 @@ border-radius: 0; } +.dayIndicator { + background-color: lavender; + padding: 2px 12px; + font-size: 0.8em; +} + .block { padding: 12px; diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx index 1f5befa..76d805a 100644 --- a/app/src/components/nodes/JournalPage.tsx +++ b/app/src/components/nodes/JournalPage.tsx @@ -1,10 +1,12 @@ -import { useEffect, useState } from "react"; -import MDEditor from "@uiw/react-md-editor"; +import { useEffect, useRef, useState } from "react"; +import MDEditor, { PreviewType } from "@uiw/react-md-editor"; import { usePrevious, useDebounce } 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"; export interface JournalPageProps { id: string; @@ -14,11 +16,13 @@ export interface JournalPageProps { } 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 previous = usePrevious(valueToSave); const changed = valueToSave !== previous; + const [mode, setMode] = useState("preview"); useEffect(() => { if (changed) { @@ -45,12 +49,15 @@ export default function JournalPage({ id, data }: JournalPageProps) { return (
+ {day && } + newValue && setValue(newValue)} - preview="preview" + onChange={(newValue) => newValue !== undefined && setValue(newValue)} + preview={mode} visibleDragbar={false} + onDoubleClick={() => setMode("live")} previewOptions={{ remarkPlugins: [remarkMath], rehypePlugins: [rehypeKatex], @@ -59,3 +66,13 @@ export default function JournalPage({ id, data }: JournalPageProps) {
); } + +function DayIndicator({ day }) { + const parsedDate = parseDate(day, "yyyy-MM-dd", new Date()); + const formattedDate = formatDate(parsedDate, "PPPP"); + return ( +
+ Journal entry for {formattedDate} +
+ ); +} diff --git a/app/src/global.scss b/app/src/global.scss index d3827ea..e43dedc 100644 --- a/app/src/global.scss +++ b/app/src/global.scss @@ -12,4 +12,8 @@ html, #root { width: 100%; height: 100%; +} + +.spacer { + flex-grow: 1; } \ No newline at end of file diff --git a/crates/panorama-daemon/src/mail.rs b/crates/panorama-daemon/src/mail.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs index 79d675a..e0502e0 100644 --- a/crates/panorama-daemon/src/main.rs +++ b/crates/panorama-daemon/src/main.rs @@ -19,7 +19,7 @@ use std::fs; use anyhow::Result; use axum::{ http::Method, - routing::{get, post}, + routing::{get, post, put}, Router, }; use cozo::DbInstance; @@ -32,7 +32,7 @@ use crate::{ export::export, journal::get_todays_journal_id, migrations::run_migrations, - node::{get_node, node_types, update_node}, + node::{create_node, get_node, node_types, search_nodes, update_node}, }; #[derive(Clone)] @@ -67,6 +67,8 @@ async fn main() -> Result<()> { let app = Router::new() .route("/", get(|| async { "Hello, World!" })) .route("/export", get(export)) + .route("/node", put(create_node)) + .route("/node/search", get(search_nodes)) .route("/node/:id", get(get_node)) .route("/node/:id", post(update_node)) .route("/node/types", get(node_types)) diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs index aa44d9a..43c9f15 100644 --- a/crates/panorama-daemon/src/node.rs +++ b/crates/panorama-daemon/src/node.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use axum::{ - extract::{Path, State}, + extract::{Path, Query, State}, http::StatusCode, Json, }; @@ -163,3 +163,50 @@ pub async fn node_types() -> AppResult> { ] }))) } + +pub async fn create_node() -> AppResult<()> { + Ok(()) +} + +#[derive(Deserialize)] +pub struct SearchQuery { + query: String, +} + +pub async fn search_nodes( + State(state): State, + Query(query): Query, +) -> AppResult> { + let results = state.db.run_script( + " + ?[node_id, content, score] := ~journal:text_index {node_id, content, | + query: $q, + k: 10, + score_kind: 'tf_idf', + bind_score: score + } + + :order -score + ", + btmap! { + "q".to_owned() => DataValue::from(query.query), + }, + ScriptMutability::Immutable, + )?; + + let results = results + .rows + .into_iter() + .map(|row| { + json!({ + "node_id": row[0].get_str().unwrap(), + "content": row[1].get_str().unwrap(), + "score": row[2].get_float().unwrap(), + }) + }) + .collect::>(); + + Ok(Json(json!({ + "results": results + }))) +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba5dcec..9a7c421 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@mui/material': specifier: ^5.15.18 version: 5.15.18(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1) + '@remark-embedder/core': + specifier: ^3.0.3 + version: 3.0.3 '@tanstack/react-query': specifier: ^5.37.1 version: 5.39.0(react@18.3.1) @@ -41,6 +44,9 @@ importers: classnames: specifier: ^2.5.1 version: 2.5.1 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 hast-util-to-jsx-runtime: specifier: ^2.3.0 version: 2.3.0 @@ -926,6 +932,19 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@remark-embedder/core@3.0.3: + resolution: {integrity: sha512-izeW4GT5A/NgArjATndg1KKumL7IHLPZFQODJ07vSEHgCOBq2caMlnuvFGD+7ZmF4NCZks/HhZsYhfF46TOK5w==} + engines: {node: '>=12', npm: '>=6'} + dependencies: + '@babel/runtime': 7.24.6 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + hast-util-from-parse5: 8.0.1 + parse5: 7.1.2 + unified: 11.0.4 + unist-util-visit: 5.0.0 + dev: false + /@rollup/rollup-android-arm-eabi@4.18.0: resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} cpu: [arm] @@ -1524,6 +1543,10 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'}