diff --git a/README.md b/README.md index 59f0251..4688d98 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,6 @@ Transform your Obsidian vault into a reactive computational environment. Reactiv - Obsidian v1.4.0 or higher - React v18.2.0 -- Libraries: - - Recharts v2.10.0 - - Lightweight Charts v4.1.0 - - d3-force v3.0.0 - - lucide-react v0.263.1 - - shadcn/ui (latest components) - ## ๐Ÿš€ Quick Start @@ -109,46 +102,31 @@ export default MyComponent; -## ๐Ÿ“š Built-in Libraries & Components +## ๐Ÿ“š Available Libraries & Components -### React Core & Hooks -```javascript -import React, { useState, useEffect, useRef, useMemo } from react'; -``` -### UI Components from shadcn/ui -```javascript -import { - Card, CardHeader, CardTitle, CardContent, - Tabs, TabsList, TabsTrigger, - Button, Dialog, Form -} from '@/components/ui'; -``` -### Visualization Libraries -```javascript -//Include Responsive containers -// Recharts for data visualization -import { - LineChart, BarChart, PieChart, - Line, Bar, Pie, - Tooltip, Legend -} from 'recharts'; +ReactiveNotes provides access to popular libraries and components directly in your notes: +### Core Libraries +```jsx +// React and hooks +import React, { useState, useEffect, useRef, useMemo } from 'react'; -// Lightweight Charts for financial charts +// Data visualization +import { LineChart, BarChart, PieChart } from 'recharts'; import { createChart } from 'lightweight-charts'; -// D3-force for network graphs -import { - forceSimulation, - forceLink -} from 'd3-force'; -``` -### Icons -```javascript -import { - Upload, Activity, AlertCircle, - TrendingUp, TrendingDown -} from 'lucide-react'; +// Data processing +import Papa from 'papaparse'; // CSV parsing +import * as XLSX from 'xlsx'; // Excel files +import * as mathjs from 'mathjs'; // Mathematics +import { format, parseISO } from 'date-fns'; // Date handling + +// Interface components +import { Card, Tabs, Switch } from '@/components/ui'; +import { TrendingUp, Activity, Settings } from 'lucide-react'; // 70+ icons ``` +See the full [component reference](./COMPONENTS.md) for a complete list of available libraries, components and utilities. +Even if needed libraries are not provided within this list CDN imports are always an option. + ## ๐Ÿ’ป Core Features ### 1. Component Systems @@ -161,21 +139,20 @@ import { - Real-time component updates - Error reporting -### 3. Canvas Manipulation -- Direct canvas access for custom drawing -- Real-time canvas updates -- Image processing capabilities -- Custom overlays and annotations -- Interactive drawing tools +### 2. Canvas Manipulation +- HTML Canvas API support for custom drawing +- THREE.js integration for 3D graphics +- Lightweight Charts for financial visualization +- D3 force layout integration for network graphs +- Support for both 2D and 3D rendering contexts - -### 5. Performance +### 3. Performance - Efficient re-rendering -- Memory leak prevention +- Proper cleanup of resources in useEffect hooks - Lazy loading support -### 2. State Management +### 4. State Management ```javascript // Local state with React const [local, setLocal] = useState(0); @@ -189,7 +166,7 @@ const [stored, setStored] = useStorage('key', defaultValue); - Cross-note state management ### Note-Specific Data Persistence -- Each note maintains independent state through frontmatter +- Each note maintains independent state through frontmatter storage under a key called react_data - Component states persist across sessions - Data automatically travels with notes when shared - Multiple instances of same component can have different states @@ -210,19 +187,97 @@ const Counter = () => { return ; }; ``` +#### Browser LocalStorage + +For components that need to share state across notes or maintain state independent of specific notes, browser localStorage is available. -### 3. File System Integration ```javascript -// Read files with encoding -const text = await window.fs.readFile('data.txt', { - encoding: 'utf8' -}); +// Create a localStorage hook +const useLocalStorage = (key, defaultValue) => { + const [state, setState] = useState(() => { + try { + const storedValue = localStorage.getItem(key); + return storedValue ? JSON.parse(storedValue) : defaultValue; + } catch (error) { + console.error(`Error reading localStorage key "${key}":`, error); + return defaultValue; + } + }); -// Read binary files -const binary = await window.fs.readFile('data.xlsx'); + useEffect(() => { + localStorage.setItem(key, JSON.stringify(state)); + }, [key, state]); + + return [state, setState]; +}; + +// Use it in components +const [value, setValue] = useLocalStorage('shared-key', defaultValue); +//This key is shared across all notes ``` -### 4. CDN Library Support +Key features: +- Data persists across browser sessions +- Shared state across all notes +- Independent of note content +- Approximately 5-10MB storage per domain +- Persists until browser data is cleared + +### 5. File Reading Utility + +The plugin provides a flexible file reading utility through the `readFile` function that helps you interactively select and read files from your vault. +```javascript +const fileData = await readFile( path,extensions); +``` +### Parameters +- `path` (optional): Direct path to a specific file. If provided, skips the file selector. Default: `null` +- `extensions` (optional): Array of file extensions to filter by. Default: `['txt', 'md', 'json', 'csv']` + +### Return Value + +Returns a Promise that resolves to an object containing: + +- `path`: Full file path +- `name`: File name without extension +- `extension`: File extension +- `content`: String content of the file + +Returns `null` if the operation is canceled or fails, allowing you to handle this case gracefully. + +```javascript +// Interactive file selection with file type filtering +const textFile = await readFile(null,['txt', 'md']); +if (textFile) { + console.log(`Selected: ${textFile.name}`); + console.log(`Content: ${textFile.content}`); +} + +// Direct file access by path (no modal) +const jsonFile = await readFile('path/to/config.json'); +if (jsonFile) { + const config = JSON.parse(jsonFile.content); +} + +// Read CSV files +const csvFile = await readFile(null,['csv']); +if (csvFile) { + // Process CSV content + const lines = csvFile.content.split('\n'); + const headers = lines[0].split(','); + // ... +} + +// Read binary files like Excel spreadsheets +const excelFile = await readFile(null,['xlsx', 'xls']); +if (excelFile) { + // excelFile.content contains the file data + // Process with SheetJS or other libraries +} +``` +This utility is particularly useful for components that need to process files from your vault, such as data visualizations, document analysis, and custom importers. + + +### 6. CDN Library Support Import additional libraries from cdnjs: ```javascript import Chart from 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js'; @@ -233,24 +288,18 @@ Requirements: - Imports at component top - Browser-compatible libraries only -### 5. Theme Integration +### 7. Theme Integration ```javascript -const theme = getTheme(); // Returns 'dark' or 'light' +const theme = getTheme(); // Returns 'dark' or 'light' correspodning to obsidian theme const styles = { background: theme === 'dark' ? 'var(--background-primary)' : 'white', color: theme === 'dark' ? 'var(--text-normal)' : 'black' }; +// Or use 'dark:' within your components ``` -### File System Access -```javascript -// Read files -const csv = await window.fs.readFile('data.csv', { encoding: 'utf8' }); -const binary = await window.fs.readFile('data.xlsx'); -``` - ## ๐ŸŽจ Component Examples ### Interactive Data Visualization @@ -366,6 +415,17 @@ const NetworkGraph = () => { **Tabbed UI** ![alt text](assets/TabbedUI.gif) +**Progression Dashboard** + +Knowledge progression dashboard using persistant storage and ability to make new notes/categories +![alt text](assets/ProgressionDashboard.gif) + +**Integrated use case with MCP** + + MCP fetches data from youtube and uses Reactive Notes to display react component for visualising fetched data. + +![alt text](assets/IntegratedUsage.gif) + ## ๐Ÿ› ๏ธDevelopment Guide ### Component Structure ```react @@ -515,8 +575,8 @@ Best practices for optimal performance: 1. **File System** ```javascript // File operations - await window.fs.readFile(path, options); - const text = await window.fs.readFile(path, { encoding: 'utf8' }); + let file = await readFile(path, extensions); + let content=file.content; ``` 2. **Theme Management** diff --git a/assets/IntegratedUsage.gif b/assets/IntegratedUsage.gif new file mode 100644 index 0000000..d97f1f2 Binary files /dev/null and b/assets/IntegratedUsage.gif differ diff --git a/assets/ProgressionDashboard.gif b/assets/ProgressionDashboard.gif new file mode 100644 index 0000000..da29522 Binary files /dev/null and b/assets/ProgressionDashboard.gif differ diff --git a/assets/gamingObsidian.gif b/assets/gamingObsidian.gif new file mode 100644 index 0000000..1671420 Binary files /dev/null and b/assets/gamingObsidian.gif differ diff --git a/main.tsx b/main.tsx index aaa0a1a..20fea6b 100644 --- a/main.tsx +++ b/main.tsx @@ -1,4 +1,4 @@ -import { Plugin, MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownRenderer, TFile, App, stringifyYaml, Notice } from 'obsidian'; +import { Plugin, MarkdownPostProcessorContext, MarkdownRenderChild, MarkdownRenderer, TFile, App, stringifyYaml, Notice, SuggestModal } from 'obsidian'; import { createRoot } from 'react-dom/client'; import { transform } from '@babel/standalone'; import * as React from 'react'; @@ -312,7 +312,100 @@ private getThreeJs() { }), //lazy load THREE if needed ...needsThree ? this.getThreeJs() : undefined, - }; +readFile: async (path=null, extensions = ['txt', 'md', 'json','csv']) => { + return new Promise((resolve) => { + let isResolved = false; + let cancelTimeoutHandle: number | undefined = undefined; // To store timeout handle for cancellation + // Create a SuggestModal subclass that lists vault files + + // If a specific path is provided, read that file directly without showing modal + if (path) { + const file = this.app.vault.getAbstractFileByPath(path); + if (file && file instanceof TFile) { + this.app.vault.cachedRead(file).then(content => { + isResolved = true; + resolve({ + path: file.path, + name: file.name, + content: content + }); + }).catch(err => { + console.error("Failed to read file:", err); + isResolved = true; + resolve(null); + }); + } else { + console.error("File not found:", path); + isResolved = true; + resolve(null); + } + return; + } + class FileSuggestModal extends SuggestModal { + getSuggestions(query:string) { + // Get all files with the right extension + const files = this.app.vault.getFiles() + .filter(file => extensions.includes(file.extension)); + + // Filter by query if provided + if (query) { + return files.filter(file => + file.path.toLowerCase().includes(query.toLowerCase()) + ); + } + return files; + } + + renderSuggestion(file:TFile, el: HTMLElement) { + el.createEl("div", { text: file.name }); + el.createEl("small", { text: file.path }); + } + + onChooseSuggestion(file:TFile, evt: MouseEvent|KeyboardEvent) { + console.log("File content:"); + if (cancelTimeoutHandle !== undefined) { + clearTimeout(cancelTimeoutHandle); + cancelTimeoutHandle = undefined; + } + this.app.vault.cachedRead(file).then(content => {isResolved = true; + resolve({ + path: file.path, + name: file.basename, + extension: file.extension, + content: content + }); + }).catch(err => { + console.error("Failed to read file:", err); + resolve(null);});} + } + + // Show the modal + const modal = new FileSuggestModal(this.app); + modal.setPlaceholder("Select a file"); + modal.onClose = () => { + // If onClose is called, and a suggestion hasn't ALREADY been chosen and processed, + // this implies a cancellation or an edge case where onClose fires very quickly. + // We use a setTimeout to yield to the event loop, giving onChooseSuggestion + // a chance to run if it was triggered by the same user action that closed the modal. + if (cancelTimeoutHandle !== undefined) { // Clear previous timeout if any (e.g. rapid open/close) + clearTimeout(cancelTimeoutHandle); + } + cancelTimeoutHandle = window.setTimeout(() => { // window.setTimeout for NodeJS.Timeout type + // After yielding, if the promise hasn't been resolved by onChooseSuggestion + // and a suggestion wasn't flagged as chosen, then resolve as null (cancellation). + if (!isResolved) { + resolve(null); + } + // If suggestionChosen IS true here, it means onChooseSuggestion ran, + // set the flag, and it's responsible for resolving (or has already). + // If promiseResolved IS true, it's already handled. + }, 0); // Yield to the event loop. + }; + modal.open(); + + }); + } + }; // Execute the code and get the component const Component = await new Function( @@ -392,6 +485,7 @@ export default class ReactNotesPlugin extends Plugin { ); // Register a global Markdown postprocessor for HTML this.registerMarkdownPostProcessor((element, context) => { + // Only process elements that are within ReactiveNotes components if (element.closest('.react-component-container')) { this.parseMarkdownInHtml(element); } diff --git a/manifest.json b/manifest.json index 1d0cf87..f7032fd 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "reactive-notes", "name": "Reactive Notes", - "version": "1.0.1", + "version": "1.0.2", "minAppVersion": "1.4.0", "description": "Transform your vault into a reactive computational environment. Create dynamic React components directly in your notes.", "author": "Elias Noesis", diff --git a/package-lock.json b/package-lock.json index 5fade6c..24b7c0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "reactive-notes", - "version": "1.0.1", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 7ce8aa9..05fcb7b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactive-notes", - "version": "1.0.1", + "version": "1.0.2", "description": "Transform your vault into a reactive computational environment with React components.", "main": "main.js", "scripts": { diff --git a/src/components/componentRegistry.tsx b/src/components/componentRegistry.tsx index e753d26..a41d54e 100644 --- a/src/components/componentRegistry.tsx +++ b/src/components/componentRegistry.tsx @@ -1,5 +1,4 @@ -// src/config/componentRegistry.ts -//Can be modified to add more components and libraries +// src/config/componentRegistry.tsx import React from 'react'; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, @@ -29,8 +28,8 @@ import { import { format, addDays, differenceInDays, parseISO } from 'date-fns'; import { parse, stringify } from 'csv/sync'; import * as mathjs from 'mathjs'; - - +import * as XLSX from 'xlsx'; +import Papa from 'papaparse'; // Core React hooks wrapper // Type for React utilities (non-component exports) @@ -106,6 +105,8 @@ RadialBarChart, RadialBar, dateFns: { format, addDays, differenceInDays, parseISO }, csv: { parse, stringify }, math: mathjs, + XLSX, + Papa, } as const; diff --git a/versions.json b/versions.json index 39cbfbb..8a45d43 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.1": "1.4.0" + "1.0.2": "1.4.0" }