From 4b9efd25bf0301028bbe1217a5ff7f08bc469459 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 2 Feb 2024 15:08:58 -0800 Subject: [PATCH 1/3] allow creating cells directly --- src-tauri/src/main.rs | 12 ++++++++---- src/App.tsx | 5 +++-- src/hooks/useNotebook.ts | 11 ++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 21e1e0b..b0f791d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -94,12 +94,16 @@ impl AppState { } // Return cell_id - async fn create_cell(&self, window_id: &str) -> Option { + async fn create_cell(&self, window_id: &str, cell_type: Option<&str>) -> Option { debug!("Creating a new cell in window with ID: {}", window_id); + let cell_type = cell_type.unwrap_or("code"); let mut notebooks = self.notebooks.lock().await; if let Some(notebook) = notebooks.get_mut(window_id) { - let new_cell = notebook.add_code_cell(""); + let new_cell = match cell_type { + "markdown" => notebook.add_markdown_cell(""), + _ => notebook.add_code_cell(""), + }; Some(new_cell.id().to_string()) } else { None @@ -126,9 +130,9 @@ impl AppState { } #[tauri::command] -async fn create_cell(state: State<'_, AppState>, window: Window) -> Result, String> { +async fn create_cell(state: State<'_, AppState>, window: Window, cell_type: &str) -> Result, String> { let window_id = window.label(); // Use the window label as a unique identifier - Ok(state.create_cell(window_id).await) + Ok(state.create_cell(window_id, Some(cell_type)).await) } #[tauri::command] diff --git a/src/App.tsx b/src/App.tsx index 4cdbc4d..f7bbdf5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,13 +4,14 @@ import { Button} from "@/components/ui/button"; import { useNotebook } from '@/hooks/useNotebook'; function App() { - const { cells, createCell } = useNotebook(); + const { cells, createCodeCell, createMarkdownCell } = useNotebook(); return (
{cells.map((cellId: string) => ( ))} - + +
); } diff --git a/src/hooks/useNotebook.ts b/src/hooks/useNotebook.ts index 8a3e09d..ebee88b 100644 --- a/src/hooks/useNotebook.ts +++ b/src/hooks/useNotebook.ts @@ -1,15 +1,16 @@ -import { useState } from 'react'; +import { useState, useCallback } from 'react'; import { invoke } from "@tauri-apps/api/tauri"; export function useNotebook() { const [cells, setCells] = useState([]); - async function createCell() { - const id = (await invoke("create_cell")) as string; + async function createCell(cellType: "code" | "markdown") { + const id = (await invoke("create_cell", { cellType })) as string; setCells(oldCells => [...oldCells, id]); } - // TODO(kyle): Listen to events from the backend to update the cell list + const createCodeCell = useCallback(() => createCell("code"), [createCell]); + const createMarkdownCell = useCallback(() => createCell("markdown"), [createCell]); - return { cells, createCell }; + return { cells, createCell, createCodeCell, createMarkdownCell}; } \ No newline at end of file From 58970a9b660070e6775186821740ba42901cb65d Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 2 Feb 2024 15:40:57 -0800 Subject: [PATCH 2/3] hooray --- src/App.tsx | 20 ++++++++++++++------ src/components/Cell.tsx | 9 --------- src/hooks/useNotebook.ts | 13 +++++++++---- 3 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 src/components/Cell.tsx diff --git a/src/App.tsx b/src/App.tsx index f7bbdf5..b4a7bbd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,23 @@ -import Cell from "@/components/Cell"; - -import { Button} from "@/components/ui/button"; +import { Button } from "@/components/ui/button"; import { useNotebook } from '@/hooks/useNotebook'; +import { MarkdownCell } from "@/components/markdown-cell" +import { CodeCell } from '@/components/code-cell' + function App() { const { cells, createCodeCell, createMarkdownCell } = useNotebook(); return (
- {cells.map((cellId: string) => ( - - ))} + {cells.map((cellInfo) => { + switch (cellInfo.cellType) { + case "code": + return + case "markdown": + return + default: + return "Unknown cell type" + } + })}
diff --git a/src/components/Cell.tsx b/src/components/Cell.tsx deleted file mode 100644 index e052ff1..0000000 --- a/src/components/Cell.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import {CodeCell} from "@/components/code-cell" - -const Cell = ({ cellId }: { cellId: string }) => { - // Until we have cell types coming in from the backend, we'll assume this is a code cell - return - -}; - -export default Cell; diff --git a/src/hooks/useNotebook.ts b/src/hooks/useNotebook.ts index ebee88b..43fde74 100644 --- a/src/hooks/useNotebook.ts +++ b/src/hooks/useNotebook.ts @@ -1,13 +1,18 @@ import { useState, useCallback } from 'react'; import { invoke } from "@tauri-apps/api/tauri"; +type CellInfo = { + id: string, + cellType: "code" | "markdown" +} + export function useNotebook() { - const [cells, setCells] = useState([]); + const [cells, setCells] = useState([]); - async function createCell(cellType: "code" | "markdown") { + const createCell = useCallback(async (cellType: "code" | "markdown") => { const id = (await invoke("create_cell", { cellType })) as string; - setCells(oldCells => [...oldCells, id]); - } + setCells(oldCells => [...oldCells, { id, cellType }]); + }, []); const createCodeCell = useCallback(() => createCell("code"), [createCell]); const createMarkdownCell = useCallback(() => createCell("markdown"), [createCell]); From 41a60eee7ee7fba4878fcad0b4b5d990a8d5eca3 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 2 Feb 2024 16:07:20 -0800 Subject: [PATCH 3/3] process cell updates from the Tauri backend --- src-tauri/src/main.rs | 20 ++++++++++----- src/components/Editor.tsx | 4 +-- src/components/markdown-cell.tsx | 10 ++++++++ src/hooks/useCell.ts | 44 ++++++++++++++++++++++---------- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b0f791d..37ea879 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,6 +1,7 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +use serde_json::json; use tokio::sync::Mutex; use std::collections::HashMap; @@ -94,7 +95,8 @@ impl AppState { } // Return cell_id - async fn create_cell(&self, window_id: &str, cell_type: Option<&str>) -> Option { + async fn create_cell(&self, window: Window, cell_type: Option<&str>) -> Option { + let window_id = window.label(); debug!("Creating a new cell in window with ID: {}", window_id); let cell_type = cell_type.unwrap_or("code"); @@ -111,7 +113,8 @@ impl AppState { } // Update a specific cell within the specified notebook - async fn update_cell(&self, window_id: &str, cell_id: &str, new_content: &str) -> bool { + async fn update_cell(&self, window: Window, cell_id: &str, new_content: &str) -> bool { + let window_id = window.label(); debug!( "Updating cell with ID: {} in window with ID: {} with new content: {}", cell_id, window_id, new_content @@ -121,6 +124,13 @@ impl AppState { if let Some(notebook) = notebooks.get_mut(window_id) { if let Some(cell) = notebook.get_mut_cell(cell_id) { cell.set_source(new_content); + + window.emit( + format!("cell-{}", cell_id).as_str(), + Some(json!({ + "source": new_content + })) + ).unwrap(); } true } else { @@ -131,8 +141,7 @@ impl AppState { #[tauri::command] async fn create_cell(state: State<'_, AppState>, window: Window, cell_type: &str) -> Result, String> { - let window_id = window.label(); // Use the window label as a unique identifier - Ok(state.create_cell(window_id, Some(cell_type)).await) + Ok(state.create_cell(window, Some(cell_type)).await) } #[tauri::command] @@ -151,8 +160,7 @@ async fn update_cell( cell_id: &str, new_content: &str, ) -> Result { - let window_id = window.label(); // Use the window label as a unique identifier - Ok(state.update_cell(window_id, cell_id, new_content).await) + Ok(state.update_cell(window, cell_id, new_content).await) } // The main entry point for the Tauri application diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index db41662..bf655b8 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -6,7 +6,7 @@ import { lightTheme } from "@/codemirror-themes"; import { StateField } from "@codemirror/state"; import { EditorView, keymap } from "@codemirror/view"; -import { useCodeCell } from "@/hooks/useCell"; +import { useCell } from "@/hooks/useCell"; import { invoke } from "@tauri-apps/api/tauri"; @@ -82,7 +82,7 @@ const baseExtensions = [ export const Editor = ({ cellId, className, language }: { cellId: string, className?: string, language: string }) => { const ref = useRef(null); - const { content, updateCell } = useCodeCell(cellId); + const { content, updateCell } = useCell(cellId); // We need to compute a derived extensions state based on the language of the editor const extensions = useMemo(() => { diff --git a/src/components/markdown-cell.tsx b/src/components/markdown-cell.tsx index 115ee61..483b610 100644 --- a/src/components/markdown-cell.tsx +++ b/src/components/markdown-cell.tsx @@ -3,6 +3,8 @@ import { useRemark } from "react-remark"; import { useMarkdownCell } from "@/hooks/useCell"; +import { Editor } from "@/components//Editor"; + export const MarkdownCell = ({ cellId }: { cellId: string }) => { const { content } = useMarkdownCell(cellId); @@ -12,6 +14,8 @@ export const MarkdownCell = ({ cellId }: { cellId: string }) => { setMarkdownSource(content); }, [content]); + console.log(content) + return (
@@ -19,6 +23,12 @@ export const MarkdownCell = ({ cellId }: { cellId: string }) => { {/* placeholder */}
+ +
{reactContent}
); diff --git a/src/hooks/useCell.ts b/src/hooks/useCell.ts index 03b88d1..206c8cc 100644 --- a/src/hooks/useCell.ts +++ b/src/hooks/useCell.ts @@ -35,18 +35,43 @@ function cellReducer(state: CellState, action: Action) { } } -export function useMarkdownCell(cellId: string) { +export function useCell(cellId: string) { const [content, setContent] = useState(""); const updateCell = useCallback(async (newContent: string) => { try { - await invoke("update_cell", { cellId, newContent }); setContent(newContent); + await invoke("update_cell", { cellId, newContent }); } catch (error) { console.error(error); // Handle error } - }, [cellId]); + }, [cellId]); + + + useEffect(() => { + const setupListener = async () => { + const unlisten = await listen(`cell-${cellId}`, (event) => { + const cellUpdate = event.payload as any; + + setContent(cellUpdate.source) + }); + + return () => { + unlisten(); + }; + }; + + setupListener(); + }, [cellId]); + + + + return { content, setContent, updateCell } +} + +export function useMarkdownCell(cellId: string) { + const { content, updateCell } = useCell(cellId); // TODO: Push/pull this from cell metadata const metadata = { }; @@ -55,7 +80,8 @@ export function useMarkdownCell(cellId: string) { } export function useCodeCell(cellId: string) { - const [content, setContent] = useState(""); + const { content, updateCell } = useCell(cellId); + const [state, dispatch] = useReducer(cellReducer, initialState); const executeCell = useCallback(async () => { @@ -69,16 +95,6 @@ export function useCodeCell(cellId: string) { dispatch({ type: 'reset' }); }, [cellId]); - const updateCell = useCallback(async (newContent: string) => { - try { - await invoke("update_cell", { cellId, newContent }); - setContent(newContent); - } catch (error) { - console.error(error); - // Handle error - } - }, [cellId]); - useEffect(() => { const setupListener = async () => { const unlisten = await listen(`cell-outputs-${cellId}`, (event) => {