Compare commits
12 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3adb99123 | ||
|
|
881c11b5da | ||
|
|
58a461fdb7 | ||
|
|
4ee872e2a3 | ||
|
|
41ae4b0cd7 | ||
|
|
2e92684a5e | ||
|
|
d4abaa3031 | ||
|
|
6352387216 | ||
|
|
34fb6894e2 | ||
|
|
72a620b59b | ||
|
|
431657eace | ||
|
|
08e0ebd958 |
4
.gitignore
vendored
|
|
@ -49,4 +49,6 @@ src/core/tailwind.ts
|
|||
# Build artifacts (commonly ignored)
|
||||
main.js.map
|
||||
*.tsbuildinfo
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
forgottenStuff.txt
|
||||
343
COMPONENTS.md
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
# ReactiveNotes Component Reference
|
||||
|
||||
This document provides a reference guide to the libraries, components, and utilities available in ReactiveNotes. All of these are automatically available in your React components without requiring explicit import statements.
|
||||
> Note: Need a library that's not listed here? You can use CDN imports from cdnjs.cloudflare.com for additional libraries. For permanent additions to the core library set, please open an issue on GitHub with your request.
|
||||
## Table of Contents
|
||||
- [React Core](#react-core)
|
||||
- [UI Components](#ui-components)
|
||||
- [Data Visualization](#data-visualization)
|
||||
- [Icons](#icons)
|
||||
- [Data Processing](#data-processing)
|
||||
- [File System](#file-system)
|
||||
- [Utilities](#utilities)
|
||||
|
||||
## React Core
|
||||
|
||||
```jsx
|
||||
// React hooks
|
||||
useState, useEffect, useRef, useMemo, useCallback
|
||||
|
||||
// Core React object
|
||||
React.createElement, React.Fragment
|
||||
```
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
const MyComponent = () => {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return <div>Count: {count}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## UI Components
|
||||
|
||||
### shadcn/ui Components
|
||||
|
||||
ReactiveNotes includes some components from [shadcn/ui](https://ui.shadcn.com/):
|
||||
|
||||
**Available Components:**
|
||||
- **Card:** `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`
|
||||
- **Navigation:** `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`
|
||||
- **Controls:** `Switch`
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Card Title</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>Main content goes here</CardContent>
|
||||
</Card>
|
||||
|
||||
<Tabs defaultValue="tab1">
|
||||
<TabsList>
|
||||
<TabsTrigger value="tab1">First Tab</TabsTrigger>
|
||||
<TabsTrigger value="tab2">Second Tab</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="tab1">Tab 1 content</TabsContent>
|
||||
<TabsContent value="tab2">Tab 2 content</TabsContent>
|
||||
</Tabs>
|
||||
```
|
||||
|
||||
## Data Visualization
|
||||
|
||||
### Recharts
|
||||
|
||||
ReactiveNotes includes the full [Recharts](https://recharts.org/) library:
|
||||
|
||||
**Available Components:**
|
||||
- **Chart types:** `LineChart`, `BarChart`, `PieChart`, `AreaChart`, `ScatterChart`, `RadarChart`, `RadialBarChart`, `ComposedChart`, `Treemap`, `ScatterChart`, `RadarChart`
|
||||
- **Chart elements:** `Line`, `Bar`, `Pie`, `Area`, `Scatter`, `Radar`, `RadialBar`
|
||||
- **Axes & Grid:** `XAxis`, `YAxis`, `CartesianGrid`, `PolarGrid`, `PolarAngleAxis`, `PolarRadiusAxis`
|
||||
- **Utilities:** `ResponsiveContainer`, `Tooltip`, `Legend`, `Cell`, `ReferenceLine`
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={data}>
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Line type="monotone" dataKey="value" stroke="#8884d8" />
|
||||
<Tooltip />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
```
|
||||
|
||||
### Lightweight Charts
|
||||
|
||||
For financial charting, ReactiveNotes includes [Lightweight Charts](https://www.tradingview.com/lightweight-charts/):
|
||||
|
||||
**Available Methods:**
|
||||
- `createChart()` - Create a new chart instance
|
||||
- Chart instance methods: `addAreaSeries()`, `addBarSeries()`, `addCandlestickSeries()`, `addHistogramSeries()`, `addLineSeries()`
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
useEffect(() => {
|
||||
const chart = createChart(container, options);
|
||||
const series = chart.addCandlestickSeries();
|
||||
series.setData(data);
|
||||
|
||||
return () => chart.remove();
|
||||
}, []);
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
ReactiveNotes includes a large collection of [Lucide Icons](https://lucide.dev/):
|
||||
A good portion are accessible directly while the complete collection can be accessed via LucideIcons.<IconName> at runtime
|
||||
**Available Icon Categories:**
|
||||
- **Interface:** `Home`, `Settings`, `Search`, `ChevronLeft/Right/Up/Down`, `ArrowLeft/Right/Up/Down`, etc.
|
||||
- **Media:** `Play`, `Pause`, `Image`
|
||||
- **Data:** `BarChart2`, `BarChart3`, `TrendingUp`, `Activity`
|
||||
- **Actions:** `Plus`, `Minus`, `Check`, `X`, `Edit`, `Save`, `Trash`
|
||||
- **Themes:** `Sun`, `Moon`
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
<TrendingUp className="h-6 w-6 text-green-500" />
|
||||
<Activity className="h-6 w-6 text-blue-500" />
|
||||
<Settings className="h-6 w-6" />
|
||||
```
|
||||
|
||||
## Data Processing
|
||||
|
||||
### CSV Parsing (Papa Parse)
|
||||
|
||||
**Key Functions:**
|
||||
- `Papa.parse()` - Parse CSV text into arrays/objects
|
||||
- `Papa.unparse()` - Convert arrays/objects to CSV
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
Papa.parse(csvString, {
|
||||
header: true,
|
||||
dynamicTyping: true,
|
||||
skipEmptyLines: true,
|
||||
complete: (results) => console.log(results.data)
|
||||
});
|
||||
```
|
||||
|
||||
### Excel Files (SheetJS)
|
||||
|
||||
**Key Functions:**
|
||||
- `XLSX.read()` - Read spreadsheet data from various formats
|
||||
- `XLSX.utils.sheet_to_json()` - Convert worksheet to array of objects
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
const workbook = XLSX.read(data, { type: 'binary' });
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet);
|
||||
```
|
||||
|
||||
### Date Handling (date-fns)
|
||||
|
||||
**Available Functions:**
|
||||
- `format` - Format dates
|
||||
- `addDays` - Add days to a date
|
||||
- `differenceInDays` - Get day difference
|
||||
- `parseISO` - Parse ISO date strings
|
||||
Available through the dateFns object:
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
const formattedDate = format(new Date(), 'MMMM d, yyyy');
|
||||
const nextWeek = addDays(new Date(), 7);
|
||||
```
|
||||
|
||||
### Math (mathjs)
|
||||
|
||||
**Key Features:**
|
||||
- Basic arithmetic and algebra
|
||||
- Statistical functions
|
||||
- Matrix operations
|
||||
- Symbolic math
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
const sum = math.add(2, 3);
|
||||
const mean = math.mean([1, 2, 3, 4, 5]);
|
||||
const derivative = math.derivative('x^2', 'x');
|
||||
```
|
||||
## CSV Processing
|
||||
**Available through the csv object:**
|
||||
```jsx
|
||||
const parsed = csv.parse(csvString, options);
|
||||
const csvOutput = csv.stringify(dataArray);
|
||||
```
|
||||
|
||||
## File System
|
||||
|
||||
ReactiveNotes provides file access within Obsidian via the `readFile` function:
|
||||
|
||||
**Parameters:**
|
||||
- `path` (optional) - Direct path to file (skips file picker if provided)
|
||||
- `extensions` (optional) - Array of file extensions to filter by
|
||||
|
||||
|
||||
**Return Value:**
|
||||
An object containing:
|
||||
- `path` - Full file path
|
||||
- `name` - File name without extension
|
||||
- `extension` - File extension
|
||||
- `content` - File content as string
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
// With file picker
|
||||
const file = await readFile(['md', 'txt']);
|
||||
if (file) console.log(file.content);
|
||||
|
||||
// Direct path access
|
||||
const configFile = await readFile(null, 'path/to/config.json');
|
||||
if (configFile) {
|
||||
const config = JSON.parse(configFile.content);
|
||||
}
|
||||
```
|
||||
## State Management
|
||||
**useStorage Hook**
|
||||
The `useStorage` hook provides persistent state storage in note frontmatter:
|
||||
```jsx
|
||||
const [value, setValue, error] = useStorage(
|
||||
key, // Storage key
|
||||
defaultValue, // Default value if key doesn't exist
|
||||
notePath, // Optional: path to another note (null for current note)
|
||||
extProp // Optional: true to store at root level, false for react_data (default: false)
|
||||
);
|
||||
```
|
||||
**Parameters:**
|
||||
|
||||
`key` (string) - The storage key
|
||||
`defaultValue` (any) - Default value if not found
|
||||
`notePath` (string, optional) - Path to another note, null for current note
|
||||
`extProp` (boolean, optional) - When true, stores at frontmatter root level instead of under react_data
|
||||
|
||||
Return Value:
|
||||
|
||||
`[0]` (any) - Current stored value
|
||||
`[1]`(function) - Setter function to update value
|
||||
`[2]` (string) - Error message if update failed, otherwise null
|
||||
|
||||
### Frontmatter Functions
|
||||
|
||||
#### getFrontmatter
|
||||
|
||||
Access frontmatter data in the current note or any note:
|
||||
```jsx
|
||||
const data = await getFrontmatter(
|
||||
key, // Optional: specific key to retrieve
|
||||
defaultValue, // Optional: default value if key not found
|
||||
notePath, // Optional: path to another note
|
||||
extProp // Optional: true for root frontmatter, false for react_data, Default: true
|
||||
);
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `key` (string, optional) - Specific key to retrieve, null for entire frontmatter
|
||||
- `defaultValue` (any, optional) - Default value if key not found
|
||||
- `notePath` (string, optional) - Path to another note, null for current note
|
||||
- `extProp` (boolean, optional) - When true, accesses root frontmatter, defaults to true
|
||||
|
||||
#### updateFrontmatter
|
||||
|
||||
Update frontmatter in the current note or any note:
|
||||
|
||||
|
||||
```jsx
|
||||
await updateFrontmatter(
|
||||
key, // Key to update
|
||||
value, // New value
|
||||
notePath, // Optional: path to another note
|
||||
extProp // Optional: true for root frontmatter, false for react_data, Default: false
|
||||
);
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `key` (string) - Key to update
|
||||
- `value` (any) - New value to set
|
||||
- `notePath` (string, optional) - Path to another note, null for current note
|
||||
- `extProp` (boolean, optional) - When true, updates root frontmatter, defaults to false
|
||||
|
||||
|
||||
## Utilities
|
||||
|
||||
### Network Graphs (d3-force)
|
||||
|
||||
**Available Functions:**
|
||||
- `forceSimulation()` - Create force simulation
|
||||
- `forceLink()` - Create link forces
|
||||
- `forceManyBody()` - Create charge forces
|
||||
- `forceCenter()` - Create centering force
|
||||
|
||||
Basic usage:
|
||||
```jsx
|
||||
const simulation = forceSimulation(nodes)
|
||||
.force('link', forceLink(links).id(d => d.id))
|
||||
.force('charge', forceManyBody().strength(-200))
|
||||
.force('center', forceCenter(width/2, height/2));
|
||||
```
|
||||
|
||||
### Theme Integration
|
||||
|
||||
ReactiveNotes provides a `getTheme()` function to detect Obsidian's current theme:
|
||||
|
||||
```jsx
|
||||
const theme = getTheme(); // Returns 'dark' or 'light'
|
||||
|
||||
// In JSX with conditional styling
|
||||
<div className={theme === 'dark' ? 'bg-gray-800' : 'bg-white'}>
|
||||
Content adapts to theme
|
||||
</div>
|
||||
```
|
||||
### User Notifications
|
||||
Display notifications to users:
|
||||
```jsx
|
||||
Notice(message, timeout);
|
||||
```
|
||||
|
||||
### Note Context
|
||||
Access information about the current note:
|
||||
```jsx
|
||||
// Available note information
|
||||
noteContext.path // Full path to current note
|
||||
noteContext.basename // Filename without extension
|
||||
noteContext.frontmatter // All frontmatter properties
|
||||
```
|
||||
---
|
||||
|
||||
## Further Resources
|
||||
|
||||
For detailed documentation on the included libraries:
|
||||
|
||||
- [Recharts Documentation](https://recharts.org/en-US/)
|
||||
- [shadcn/ui Documentation](https://ui.shadcn.com/)
|
||||
- [Lucide Icons Gallery](https://lucide.dev/icons/)
|
||||
- [Papa Parse Documentation](https://www.papaparse.com/docs)
|
||||
- [SheetJS Documentation](https://docs.sheetjs.com/)
|
||||
- [date-fns Documentation](https://date-fns.org/docs/Getting-Started)
|
||||
- [mathjs Documentation](https://mathjs.org/docs/index.html)
|
||||
- [d3-force Documentation](https://github.com/d3/d3-force)
|
||||
|
|
@ -9,11 +9,14 @@ First off, thank you for considering contributing to ReactiveNotes! It's people
|
|||
Before diving in, check our current focus areas:
|
||||
|
||||
- 📱 Mobile support optimization
|
||||
- 🎨 Enhanced visualization capabilities
|
||||
- 🔄 State management improvements
|
||||
- ~~🎨 Enhanced visualization capabilities~~
|
||||
- ~~🔄 State management improvements~~
|
||||
* 🧩 Component Templates *(New Focus)*
|
||||
* 🖱️ GUI for Component Snippets *(New Focus)*
|
||||
- 🧪 Testing infrastructure
|
||||
- 📚 Documentation enhancements
|
||||
- ~~📚 Documentation enhancements~~
|
||||
|
||||
> **Extra note:** If utilising or implementing pre-existing functionality, the general idea is to mirror standard use cases to minimise learning curves for contributors and users alike.
|
||||
## 🔧 Development Setup
|
||||
|
||||
### Prerequisites
|
||||
|
|
@ -68,11 +71,10 @@ ReactiveNotes/
|
|||
│ │ ├── core/
|
||||
│ │ └── ui/
|
||||
│ ├── services/ # Core services
|
||||
│ │ ├── storage.ts
|
||||
│ │ └── marketData.ts
|
||||
│ │ └── storage.ts
|
||||
│ ├── hooks/ # Custom React hooks
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── main.tsx # Plugin entry point
|
||||
├── main.tsx # Plugin entry point
|
||||
├── styles/ # CSS and Tailwind configs
|
||||
├── tests/ # Test files
|
||||
└── types/ # TypeScript definitions
|
||||
|
|
@ -315,7 +317,6 @@ We follow [Semantic Versioning](https://semver.org/):
|
|||
## 🤝 Community Guidelines
|
||||
|
||||
### Communication
|
||||
- Be respectful and inclusive
|
||||
- Keep discussions technical
|
||||
- Provide context with questions
|
||||
- Use appropriate channels
|
||||
|
|
@ -328,7 +329,7 @@ We follow [Semantic Versioning](https://semver.org/):
|
|||
|
||||
### Recognition
|
||||
Contributors are recognized in:
|
||||
- CONTRIBUTORS.md
|
||||
- CONTRIBUTORS.md (coming soon)
|
||||
- Release notes
|
||||
- Community spotlights
|
||||
|
||||
|
|
|
|||
484
README.md
|
|
@ -17,18 +17,17 @@ Transform your Obsidian vault into a reactive computational environment. Reactiv
|
|||
- **Dynamic & Interactive**: Turn static notes into living documents
|
||||
- **Extensible Foundation**: Build your own tools and visualizations
|
||||
## Compatibility
|
||||
|
||||
- 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)
|
||||
|
||||
|
||||
---
|
||||
|
||||
**New to ReactiveNotes? Start here!**
|
||||
|
||||
**[🎨 See it in Action! (Examples)](./docs/EXAMPLES/README.md)**: Explore a gallery of what you can build.
|
||||
|
||||
---
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Installation
|
||||
|
|
@ -57,172 +56,46 @@ export default Greeting;
|
|||
````
|
||||
|
||||
**Component Requirements:**
|
||||
- Must include a default export
|
||||
- Must be self-contained in one code block
|
||||
- Component name should match default export
|
||||
- Component to Render must be the first component within codeblock
|
||||
|
||||
### Component Structure
|
||||
|
||||
A typical component structure follows this basic structure:
|
||||
````
|
||||
```react
|
||||
// Optional: CDN imports at the top
|
||||
import ExternalLib from 'https://cdnjs.cloudflare.com/...';
|
||||
|
||||
// Component definition
|
||||
const MyComponent = () => {
|
||||
// 1. Hooks
|
||||
const [state, setState] = useState(null);
|
||||
const componentRef = useRef(null);
|
||||
|
||||
// 2. Effects & Lifecycle
|
||||
useEffect(() => {
|
||||
// Setup code
|
||||
return () => {
|
||||
// Cleanup code
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 3. Helper functions
|
||||
const handleEvent = () => {
|
||||
// Event handling logic
|
||||
};
|
||||
|
||||
// 4. Render
|
||||
return (
|
||||
<div>
|
||||
{/* Your JSX here */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Required: Default export
|
||||
export default MyComponent;
|
||||
```
|
||||
````
|
||||
**Key Structure Points**:
|
||||
1. Imports always at the top
|
||||
2. Component name matches default export
|
||||
3. Hooks before effects
|
||||
4. Helper functions before render
|
||||
5. Single default export at bottom
|
||||
|
||||
|
||||
|
||||
## 📚 Built-in 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';
|
||||
|
||||
// Lightweight Charts for financial charts
|
||||
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';
|
||||
```
|
||||
## 💻 Core Features
|
||||
|
||||
### 1. Component Systems
|
||||
- Write React components directly in notes
|
||||
- Full TypeScript and React 18 support
|
||||
- Hot reloading during development
|
||||
- Error Boundaries and Suspense
|
||||
- Resource cleanup and lifecycle management
|
||||
- Custom component registration
|
||||
- Real-time component updates
|
||||
- Error reporting
|
||||
### 1. React Components in Notes
|
||||
Build dynamic UIs directly in your notes with React 18 and TypeScript. Features robust error handling, lifecycle management, and real-time updates.
|
||||
|
||||
### 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
|
||||
|
||||
### 3. State Management
|
||||
Effortlessly manage component state with multiple options:
|
||||
- **Persistent Note State**: Use the `useStorage` hook to save and load component data directly within your note's frontmatter. State travels with your note!
|
||||
- **Cross-Note Persistence**: Extend `useStorage` to interact with the frontmatter of *other* notes in your vault.
|
||||
- **Root Level Frontmatter**: Opt to store data at the root of the frontmatter, outside the default `react_data` section, for broader compatibility.
|
||||
- **Standard React State**: Utilize `useState` and other React hooks for local, non-persistent component state.
|
||||
- **Browser `localStorage`**: Access global, non-note-specific storage for preferences or shared data across all notes.\
|
||||
For greater detail and examples.
|
||||
➡️ **[Deep Dive into State Management](./docs/03_STATE_MANAGEMENT.md)**
|
||||
|
||||
|
||||
### 4. File Reading Utility
|
||||
|
||||
### 5. Performance
|
||||
- Efficient re-rendering
|
||||
- Memory leak prevention
|
||||
- Lazy loading support
|
||||
|
||||
### 2. State Management
|
||||
The plugin provides a flexible file reading utility through the `readFile` function that helps you interactively select and read files from your vault.
|
||||
```javascript
|
||||
// Local state with React
|
||||
const [local, setLocal] = useState(0);
|
||||
|
||||
// Persistent state in note frontmatter
|
||||
const [stored, setStored] = useStorage('key', defaultValue);
|
||||
const fileData = await readFile( path,extensions);
|
||||
```
|
||||
- Persistent storage between sessions
|
||||
- State synchronization across components
|
||||
- Frontmatter integration
|
||||
- Cross-note state management
|
||||
### 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']`
|
||||
|
||||
### Note-Specific Data Persistence
|
||||
- Each note maintains independent state through frontmatter
|
||||
- Component states persist across sessions
|
||||
- Data automatically travels with notes when shared
|
||||
- Multiple instances of same component can have different states
|
||||
- Perfect for creating reusable interactive templates
|
||||
This utility is particularly useful for components that need to process files from your vault, such as data visualizations, document analysis, and custom importers.\
|
||||
➡️ **[Read Files Usage and Examples](./docs/04_FILE_SYSTEM_ACCESS.md)**
|
||||
|
||||
Example:
|
||||
|
||||
```javascript
|
||||
// In Note1.md - counter starts at 0
|
||||
const Counter = () => {
|
||||
const [count1, setCount1] = useStorage('counter', 0);
|
||||
return <button onClick={() => setCount1(count1 + 1)}>Count: {count1}</button>;
|
||||
};
|
||||
|
||||
// In Note2.md - same component, independent state
|
||||
const Counter = () => {
|
||||
const [count2, setCount2] = useStorage('counter', 0);
|
||||
return <button onClick={() => setCount2(count2 + 1)}>Count: {count2}</button>;
|
||||
};
|
||||
```
|
||||
|
||||
### 3. File System Integration
|
||||
```javascript
|
||||
// Read files with encoding
|
||||
const text = await window.fs.readFile('data.txt', {
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
// Read binary files
|
||||
const binary = await window.fs.readFile('data.xlsx');
|
||||
```
|
||||
|
||||
### 4. CDN Library Support
|
||||
### 5. 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';
|
||||
|
|
@ -230,27 +103,121 @@ import Chart from 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.u
|
|||
|
||||
Requirements:
|
||||
- HTTPS URLs from cdnjs.cloudflare.com only
|
||||
- Imports at component top
|
||||
- Imports must exist alone in their own line
|
||||
- Browser-compatible libraries only
|
||||
|
||||
### 5. Theme Integration
|
||||
### 6. 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
|
||||
```
|
||||
|
||||
### 7. Vault Code Imports
|
||||
|
||||
Import React code from other notes in your vault:
|
||||
|
||||
### File System Access
|
||||
```javascript
|
||||
// Read files
|
||||
const csv = await window.fs.readFile('data.csv', { encoding: 'utf8' });
|
||||
const binary = await window.fs.readFile('data.xlsx');
|
||||
// Import code from another note
|
||||
vaultImport('Components/SharedComponents.md', 0); // 0 = first code block
|
||||
vaultImport('Hooks/CustomHooks.md', 1); // 1 = second code block
|
||||
|
||||
const MyComponent = () => {
|
||||
// Use imported components and hooks from these files
|
||||
const data = useImportedHook();
|
||||
return <ImportedComponent data={data} />;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### 8. Definition Storage Blocks
|
||||
|
||||
Not all react code blocks need to be valid React you can use React code blocks to store and organize utility functions, hooks, and other definitions:
|
||||
|
||||
```javascript
|
||||
// This displays as a definition storage block instead of rendering
|
||||
const utilities = () => ({
|
||||
formatDate: (date) => date.toLocaleDateString(),
|
||||
calculateSum: (arr) => arr.reduce((a, b) => a + b, 0),
|
||||
parseData: (raw) => JSON.parse(raw)
|
||||
});
|
||||
|
||||
export default utilities;
|
||||
```
|
||||

|
||||
When a code block returns an object instead of JSX, it automatically displays as a storage block showing all exported definitions. If you have multiple objects containing definitions only the first object will be previewed.
|
||||
Perfect for:
|
||||
|
||||
Utility function collections
|
||||
Custom hook libraries
|
||||
Configuration objects
|
||||
Shared constants
|
||||
|
||||
Import these definitions into other components using vaultImport().
|
||||
|
||||
### 9. LaTeX Math Rendering with MathJax
|
||||
- Display complex mathematical and scientific notations beautifully.
|
||||
- Supports standard LaTeX syntax within your React components.
|
||||
- Automatic post rendering of inline ($...$) and display ($$...$$) math according to standard latex rules.
|
||||
- To display a dollar ($) character either first escape this character /$ or turn off MathJax post-rendering
|
||||
|
||||
|
||||
## 📚 Available Libraries & Components
|
||||
|
||||
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';
|
||||
|
||||
// Data visualization
|
||||
import { LineChart, BarChart, PieChart } from 'recharts';
|
||||
import { createChart } from 'lightweight-charts';
|
||||
|
||||
// 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
|
||||
import * as LucideIcons from 'lucide-react'; // For complete access just use LucideIcons.IconName
|
||||
```
|
||||
Checkout the full [component reference](./COMPONENTS.md) for a complete list of available libraries, components and utilities.
|
||||
|
||||
If needed, libraries not provided within this list can be imported via CDN imports.
|
||||
|
||||
### Available in Component Scope
|
||||
<details>
|
||||
All components have access to these objects and functions:
|
||||
|
||||
```javascript
|
||||
// Note context
|
||||
noteContext.frontmatter // All frontmatter properties (not just react_data)
|
||||
noteContext.path // Full path to current note
|
||||
noteContext.basename // Note filename without extension
|
||||
|
||||
// Utilities
|
||||
getTheme() // Returns 'dark' or 'light'
|
||||
readFile(path, exts) // File reading utility
|
||||
|
||||
// Update frontmatter properties, either in react_data (extProp:false) or at root level(extProp:true) Defaults to react_data
|
||||
updateFrontmatter(key, value, notePath?, extProp?)
|
||||
// If properties are null returns entire frontmatter or current note if path not provided, extProp defaults to true: entire frontmatter
|
||||
getFrontmatter(key?,defaultValue?,notePath?, extProp?)
|
||||
|
||||
vaultImport(path, index) // Preprocessor directive to include code from other notes (not a runtime function)
|
||||
Notice(message, timeout?) // Display non-blocking notifications (use instead of alert() which blocks the UI thread)
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
## 🎨 Component Examples
|
||||
|
||||
### Interactive Data Visualization
|
||||
|
|
@ -276,6 +243,7 @@ const DataChart = () => {
|
|||
};
|
||||
```
|
||||
````
|
||||

|
||||
### Canvas Manipulation
|
||||
````
|
||||
const DrawingCanvas = () => {
|
||||
|
|
@ -351,111 +319,40 @@ const NetworkGraph = () => {
|
|||
};
|
||||
```
|
||||
````
|
||||

|
||||
### Other Demonstrated Examples
|
||||
**Simulator** (Use within reason for performance)
|
||||

|
||||
|
||||
**File Upload**
|
||||

|
||||
|
||||
**3D Animation**
|
||||

|
||||
|
||||
## 🛠️Development Guide
|
||||
### Component Structure
|
||||
```react
|
||||
// Basic component template
|
||||
const MyComponent = () => {
|
||||
// 1. Hooks and state
|
||||
const [data, setData] = useState(null);
|
||||
const componentRef = useRef(null);
|
||||
|
||||
// 2. Effects and lifecycle
|
||||
useEffect(() => {
|
||||
// Setup code
|
||||
return () => {
|
||||
// Cleanup code
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 3. Event handlers
|
||||
const handleUpdate = useCallback(() => {
|
||||
// Handler code
|
||||
}, []);
|
||||
|
||||
// 4. Render
|
||||
return (
|
||||
<div>
|
||||
{/* Component JSX */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
**Tabbed UI**
|
||||

|
||||
|
||||
export default MyComponent;
|
||||
```
|
||||
**Progression Dashboard**
|
||||
|
||||
### Best Practices
|
||||
Knowledge progression dashboard using persistant storage and ability to make new notes/categories
|
||||

|
||||
|
||||
1. **Component Design**
|
||||
```react
|
||||
const ResponsiveComponent = () => {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
{/* Content adapts to container */}
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
**Integrated use case with MCP**
|
||||
|
||||
2. **State Management**
|
||||
```react
|
||||
const DataComponent = () => {
|
||||
// Persistent data
|
||||
const [savedData, setSavedData] = useStorage('data-key', []);
|
||||
|
||||
// Temporary UI state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Computed values
|
||||
const processedData = useMemo(() => {
|
||||
return savedData.map(item => /* process */);
|
||||
}, [savedData]);
|
||||
};
|
||||
```
|
||||
MCP fetches data from youtube and uses Reactive Notes to display react component for visualising fetched data.
|
||||
|
||||
3. **Error Handling**
|
||||
```react
|
||||
const SafeComponent = () => {
|
||||
return (
|
||||
<ErrorBoundary
|
||||
fallback={({ error }) => (
|
||||
<div className="text-red-500">
|
||||
Error: {error.message}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{/* Protected content */}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
```
|
||||
1. **Error Checking**:
|
||||
- Check browser console for errors
|
||||
- Verify imports are working
|
||||
- Ensure all required exports are present
|
||||

|
||||
|
||||
2. **Performance**:
|
||||
- Keep components focused and minimal
|
||||
- Use CDN imports sparingly
|
||||
- Clean up resources in useEffect
|
||||
**Games utilising event listeners for key action mapping**
|
||||

|
||||
|
||||
3. **Testing**:
|
||||
- Test in both light and dark themes
|
||||
- Verify mobile responsiveness (Hasn't been tested for mobile yet)
|
||||
- Check CDN imports load correctly
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
1. **Component not rendering**
|
||||
- Verify default export is present
|
||||
- Check console for React errors
|
||||
- Ensure all imports are available and supported
|
||||
- Validate JSX syntax
|
||||
|
||||
2. **CDN imports not working**
|
||||
|
|
@ -464,7 +361,6 @@ export default MyComponent;
|
|||
- Confirm import syntax
|
||||
|
||||
3. **State persistence issues**
|
||||
- Check frontmatter permissions
|
||||
- Verify storage key uniqueness
|
||||
- Validate data serialization
|
||||
- Monitor storage size limits
|
||||
|
|
@ -474,22 +370,8 @@ export default MyComponent;
|
|||
- Implement proper cleanup in useEffect
|
||||
- Optimize re-renders with useMemo/useCallback
|
||||
- Monitor memory usage with large datasets
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
### Mobile Compatibility
|
||||
- Currently optimized for desktop use
|
||||
- Mobile support is under development
|
||||
- Test thoroughly on mobile devices
|
||||
|
||||
### Performance and Resource Management
|
||||
Best practices for optimal performance:
|
||||
|
||||
- Clean up subscriptions and intervals
|
||||
- Dispose of chart instances
|
||||
- Release canvas resources
|
||||
- Free up WebGL contexts
|
||||
- Use persistent storage thoughtfully
|
||||
Having issues or have questions?
|
||||
Bring them to my attention by creating an issue.
|
||||
|
||||
### Security Considerations
|
||||
- CDN imports are restricted to cdnjs.cloudflare.com
|
||||
|
|
@ -497,37 +379,10 @@ Best practices for optimal performance:
|
|||
- Component code runs in sandbox
|
||||
- No external network requests
|
||||
|
||||
## 📋 Technical Reference
|
||||
### Available APIs
|
||||
|
||||
1. **File System**
|
||||
```javascript
|
||||
// File operations
|
||||
await window.fs.readFile(path, options);
|
||||
const text = await window.fs.readFile(path, { encoding: 'utf8' });
|
||||
```
|
||||
|
||||
2. **Theme Management**
|
||||
```javascript
|
||||
// Theme utilities
|
||||
const theme = getTheme();
|
||||
const isDark = theme === 'dark';
|
||||
```
|
||||
|
||||
3. **Storage**
|
||||
```javascript
|
||||
// Storage hooks
|
||||
const [value, setValue] = useStorage(key, defaultValue);
|
||||
const stored = useStorage(key, null, storage);
|
||||
```
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
1. **Environment Constraints**
|
||||
- CDN imports limited to cdnjs.cloudflare.com
|
||||
- Components must be self-contained in one code block
|
||||
- Network requests follow Obsidian's security policies
|
||||
|
||||
2. **Performance Considerations**
|
||||
- Large datasets may impact performance
|
||||
|
|
@ -536,30 +391,15 @@ Best practices for optimal performance:
|
|||
- Mobile performance varies
|
||||
|
||||
3. **Work in Progress**
|
||||
- Mobile support still under development
|
||||
- Some touch interactions need optimization
|
||||
- Advanced debugging tools coming soon
|
||||
- Mobile environment not yet tested
|
||||
|
||||
## 🔮 Future Plans
|
||||
## 🔮 Future Plans & Upcoming Features
|
||||
|
||||
### Upcoming Features
|
||||
1. Advanced Visualization
|
||||
- More chart types
|
||||
- Enhanced animations
|
||||
- 3D visualization support
|
||||
- Custom chart templates
|
||||
* **Preset Component Templates**: Kickstart your development with a library of ready-to-use component templates for common use cases.
|
||||
* **AI-Powered Component Generation**: Leverage AI (e.g., via MCP integration) to assist in generating boilerplate or even complete React components based on your descriptions.
|
||||
* **GUI for Component Snippets**: An intuitive graphical user interface to easily save your favorite or frequently used component code snippets and load them into your notes.
|
||||
|
||||
2. Developer Experience
|
||||
- Component templates
|
||||
- Debug tools
|
||||
- Performance monitoring
|
||||
- Testing utilities
|
||||
|
||||
3. Mobile Support
|
||||
- Touch optimization
|
||||
- Responsive layouts
|
||||
- Performance improvements
|
||||
- Mobile-specific components
|
||||
* **Mobile-Specific Components & UI**: Introducing components and UI patterns specifically designed for the mobile experience.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
|
|
|
|||
BIN
assets/FileUpload.gif
Normal file
|
After Width: | Height: | Size: 12 MiB |
BIN
assets/IntegratedUsage.gif
Normal file
|
After Width: | Height: | Size: 9.7 MiB |
BIN
assets/ProgressionDashboard.gif
Normal file
|
After Width: | Height: | Size: 7.3 MiB |
BIN
assets/ResponsiveDatavis.gif
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
assets/Simulator.gif
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/TabbedUI.gif
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
assets/Three_js.gif
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/codeStorageBox.gif
Normal file
|
After Width: | Height: | Size: 653 KiB |
BIN
assets/gamingObsidian.gif
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
assets/graphVis.gif
Normal file
|
After Width: | Height: | Size: 132 KiB |
219
docs/03_STATE_MANAGEMENT.md
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
# Feature: State Management in ReactiveNotes
|
||||
|
||||
ReactiveNotes provides robust mechanisms for managing state within your components, allowing for both transient UI state and persistent data storage that travels with your notes.
|
||||
|
||||
## 1. Local Component State (React `useState`)
|
||||
|
||||
For temporary UI state that doesn't need to persist across sessions or be saved to the note, you can use React's standard `useState` hook.
|
||||
|
||||
```javascript
|
||||
// Example: A simple counter
|
||||
const MyCounter = () => {
|
||||
const [count, setCount] = React.useState(0);
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(prev => prev + 1)}>
|
||||
Clicked {count} times
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default MyCounter;
|
||||
```
|
||||
This state is reset every time the component is unmounted or the note is reloaded.
|
||||
## 2. Persistent State with `useStorage` (Frontmatter Integration)
|
||||
|
||||
For state that you want to save with the note and persist across Obsidian sessions, ReactiveNotes provides the powerful `useStorage` hook. This hook directly interacts with the current note's frontmatter.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
The `useStorage` hook is the primary way to create persistent state within your components.
|
||||
|
||||
```tsx
|
||||
const [storedValue, setStoredValue, error] = useStorage(
|
||||
key, // String: Property name to store in frontmatter (under `react_data` by default)
|
||||
defaultValue, // Any: Default value if the key doesn't exist in frontmatter
|
||||
notePath = null, // Optional String: Path to another note (null = current note)
|
||||
extProp = false // Optional Boolean:
|
||||
// false (default): Store under `react_data.<key>` in frontmatter
|
||||
// true: Store directly as `<key>` at the root level of frontmatter
|
||||
);
|
||||
```
|
||||
|
||||
- **`storedValue`**: The current value from frontmatter, or `defaultValue` if not found.
|
||||
|
||||
- **`setStoredValue`**: A function to update the value. It automatically saves the new value to the note's frontmatter.
|
||||
|
||||
- **`error`**: Contains an error message string if loading or saving failed, otherwise `null`.
|
||||
|
||||
|
||||
**Example: A Persistent To-Do List Item**
|
||||
|
||||
```jsx
|
||||
const PersistentTask = () => {
|
||||
// Uses 'myTask' as the key in frontmatter, defaults to an object if not found.
|
||||
const [task, setTask, storageError] = useStorage('myTask', { text: 'Default Task', completed: false });
|
||||
const [componentError, setComponentError] = React.useState(null); // Local error for this component example
|
||||
|
||||
const toggleComplete = () => {
|
||||
try {
|
||||
setTask(prevTask => ({ ...prevTask, completed: !prevTask.completed }));
|
||||
setComponentError(null); // Clear local error on success
|
||||
} catch (e) {
|
||||
// This catch block might not be strictly necessary for setTask errors,
|
||||
// as useStorage aims to handle errors internally and expose them via storageError.
|
||||
// However, it can be useful for other logic within this function.
|
||||
setComponentError("Failed to update task: " + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
// Display any error from useStorage or local component logic
|
||||
if (storageError || componentError) {
|
||||
return <div style={{color: 'red'}}>{storageError || componentError}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={task.completed}
|
||||
onChange={toggleComplete}
|
||||
id="taskCheckbox"
|
||||
/>
|
||||
<label htmlFor="taskCheckbox" style={{ textDecoration: task.completed ? 'line-through' : 'none', marginLeft: '8px' }}>
|
||||
{task.text}
|
||||
</label>
|
||||
{/* You could add an input here to change task.text and call setTask with the new object */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default PersistentTask;
|
||||
```
|
||||
|
||||
### Storing at Root Level of Frontmatter
|
||||
|
||||
If you want to store data directly at the root of the frontmatter (outside the default `react_data` object), set the `extProp` argument to `true`.
|
||||
|
||||
```jsx
|
||||
// Stores 'draft' under a top-level 'status' key in the current note's frontmatter
|
||||
const [status, setStatus] = useStorage('status', 'draft', null, true);
|
||||
```
|
||||
|
||||
This can be useful for interacting with frontmatter keys used by other plugins or for general note metadata.
|
||||
|
||||
### Cross-Note State Management
|
||||
|
||||
You can read and write state to _another_ note's frontmatter by providing the `notePath` argument.
|
||||
|
||||
```jsx
|
||||
// Reads/writes 'tags' array from/to 'Templates/MyProjectTemplate.md' frontmatter (at the root level)
|
||||
const [tags, setTags] = useStorage('tags', ['react'], 'Templates/MyProjectTemplate.md', true);
|
||||
```
|
||||
|
||||
**Important:** Ensure the target note exists. Writing to a non-existent note path may result in errors or unexpected behavior.
|
||||
|
||||
### Deleting Keys from Frontmatter
|
||||
|
||||
To remove a key managed by `useStorage` from the frontmatter, set its value to `undefined`.
|
||||
|
||||
```jsx
|
||||
setMyValue(undefined); // This will remove 'myValue' (or react_data.myValue) from the frontmatter
|
||||
```
|
||||
|
||||
## 3. Direct Frontmatter Manipulation (Advanced)
|
||||
|
||||
While `useStorage` is the recommended and most convenient way to interact with frontmatter from within React components, ReactiveNotes also provides global helper functions for more direct manipulation. These might be useful in utility scripts or more complex, non-component-based scenarios.
|
||||
|
||||
- **`getFrontmatter(key?, defaultValue?, notePath?, extProp?)`**: Asynchronously retrieves values from frontmatter.
|
||||
|
||||
- Returns the entire frontmatter object if `key` is null or undefined.
|
||||
|
||||
- **`updateFrontmatter(key, value, notePath?, extProp?)`**: Asynchronously updates or sets values in frontmatter.
|
||||
|
||||
|
||||
```jsx
|
||||
// Example of using global helpers (less common within typical components)
|
||||
// This function is NOT a React component.
|
||||
async function updateGlobalProjectStatus(newStatus) {
|
||||
try {
|
||||
// Updates the 'projectStatus' key at the root of 'ProjectPlan.md'
|
||||
await updateFrontmatter('projectStatus', newStatus, 'ProjectPlan.md', true);
|
||||
new Notice('Project status updated in ProjectPlan.md!');
|
||||
} catch (e) {
|
||||
new Notice('Failed to update project status: ' + e.message, 5000);
|
||||
console.error("Error updating frontmatter:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// To read the status:
|
||||
async function readGlobalProjectStatus() {
|
||||
try {
|
||||
const status = await getFrontmatter('projectStatus', 'unknown', 'ProjectPlan.md', true);
|
||||
console.log('Current project status:', status);
|
||||
return status;
|
||||
} catch (e) {
|
||||
console.error("Error reading frontmatter:", e);
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** These functions are asynchronous. For reactive updates that reflect in your UI, always prefer the `useStorage` hook within your React components.
|
||||
|
||||
## 4. Browser `localStorage` (Global, Non-Note-Specific State)
|
||||
|
||||
For state that needs to be shared across _all_ notes or persist independently of any specific note (e.g., global UI preferences for your plugin's components that aren't tied to a single file), you can use the browser's standard `localStorage` API.
|
||||
|
||||
ReactiveNotes does not provide a specific hook for `localStorage` out-of-the-box (as it's a standard browser feature), but you can easily create a custom hook or use `localStorage` directly.
|
||||
|
||||
Example: useLocalStorage Custom Hook
|
||||
|
||||
(This is a common pattern for creating a React-friendly localStorage wrapper)
|
||||
|
||||
```jsx
|
||||
const useLocalStorage = (key, defaultValue) => {
|
||||
const [state, setState] = React.useState(() => {
|
||||
let storedValue;
|
||||
try {
|
||||
storedValue = localStorage.getItem(key);
|
||||
return storedValue ? JSON.parse(storedValue) : defaultValue;
|
||||
} catch (error) {
|
||||
console.error(`Error reading localStorage key "${key}":`, error);
|
||||
return defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(state));
|
||||
} catch (error) {
|
||||
console.error(`Error setting localStorage key "${key}":`, error);
|
||||
}
|
||||
}, [key, state]);
|
||||
|
||||
return [state, setState];
|
||||
};
|
||||
|
||||
// Usage in a component:
|
||||
const GlobalSettingsComponent = () => {
|
||||
const [userThemePreference, setUserThemePreference] = useLocalStorage('reactiveNotesUserTheme', 'system');
|
||||
// ... component logic using userThemePreference ...
|
||||
return (
|
||||
<select value={userThemePreference} onChange={e => setUserThemePreference(e.target.value)}>
|
||||
<option value="system">System Default</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
export default GlobalSettingsComponent;
|
||||
```
|
||||
|
||||
**Key Characteristics of `localStorage`:**
|
||||
|
||||
- Data persists across browser/Obsidian sessions (Is not Vault specific).
|
||||
|
||||
- Shared globally across all notes (not tied to a specific note's frontmatter).
|
||||
|
||||
- Subject to browser storage limits (typically 5-10MB per domain).
|
||||
|
||||
- Persists until browser data is explicitly cleared by the user (e.g., through browser settings like "Clear browsing data") or programmatically via JavaScript (localStorage.removeItem('key') or `localStorage.clear
|
||||
198
docs/04_FILE_SYSTEM_ACCESS.md
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
# Feature: File Reading Utility
|
||||
|
||||
ReactiveNotes provides a convenient `readFile` utility that allows your components to interactively select and read files from your Obsidian vault, or to read a specific file by its path.
|
||||
|
||||
This utility is essential for components that need to process data from various file types, such as text files, Markdown notes, CSV data, JSON configurations, and more.
|
||||
|
||||
## How to Use
|
||||
|
||||
The `readFile` function is available in the scope of your React components. You can call it using `await` as it returns a Promise.
|
||||
|
||||
**Basic Syntax:**
|
||||
|
||||
```javascript
|
||||
async function loadAndProcessFile() {
|
||||
const fileData = await readFile(path, extensions);
|
||||
|
||||
if (fileData) {
|
||||
// Process fileData.content, fileData.name, etc.
|
||||
console.log(`Successfully read: ${fileData.name}`);
|
||||
console.log(`Content snippet: ${fileData.content.substring(0, 100)}`);
|
||||
} else {
|
||||
// Handle cancellation or file not found/read error
|
||||
console.log('File selection was cancelled or file could not be read.');
|
||||
}
|
||||
}
|
||||
```
|
||||
## Parameters
|
||||
|
||||
The `readFile` function accepts two optional parameters:
|
||||
|
||||
1. **`path`** (optional):
|
||||
|
||||
- Type: `string | null`
|
||||
|
||||
- Default: `null`
|
||||
|
||||
- Description: If you provide a string representing the full path to a specific file within your vault (e.g., `'data/mydata.csv'` or `'Templates/My Template.md'`), the utility will attempt to read that file directly without showing the interactive file selection modal.
|
||||
|
||||
- If `null` or not provided, the utility will open a suggestion modal allowing the user to search and select a file from the vault.
|
||||
|
||||
2. **`extensions`** (optional):
|
||||
|
||||
- Type: `string[]`
|
||||
|
||||
- Default: `['txt',` 'md', 'json', 'csv']
|
||||
|
||||
- Description: An array of file extensions (without the leading dot) to filter the files shown in the suggestion modal. This helps users quickly find relevant files. This parameter is ignored if a direct `path` is provided.
|
||||
|
||||
|
||||
## Return Value
|
||||
|
||||
The `readFile` function returns a `Promise` that resolves to either:
|
||||
|
||||
- **An object** containing file details if a file is successfully selected and read:
|
||||
|
||||
- `path`: (string) The full path to the file in the vault (e.g., `folder/My File.md`).
|
||||
|
||||
- `name`: (string) The base name of the file without the extension (e.g., `My File`).
|
||||
|
||||
- `extension`: (string) The file extension without the leading dot (e.g., `md`).
|
||||
|
||||
- `content`: (string) The raw text content of the file.
|
||||
|
||||
- **`null`**: If:
|
||||
|
||||
- The user cancels the file selection modal (e.g., by pressing Escape or closing it).
|
||||
|
||||
- A direct `path` is provided but the file is not found or cannot be read.
|
||||
|
||||
- An unexpected error occurs during the file reading process.
|
||||
|
||||
|
||||
Your component should always check if the return value is `null` to handle these cases gracefully.
|
||||
## Use Cases
|
||||
|
||||
The `readFile` utility is versatile and can be used for:
|
||||
|
||||
- Loading datasets for charts and visualizations (CSV, JSON).
|
||||
|
||||
- Importing configurations or settings for components.
|
||||
|
||||
- Displaying content from other notes or text files.
|
||||
|
||||
- Building simple knowledge base explorers.
|
||||
|
||||
- Creating components that process or analyze text from files.
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Interactive File Selection (Default Behavior)
|
||||
|
||||
This will open a modal for the user to choose a file. By default, it filters for `.txt`, `.md`, `.json`, and `.csv` files.
|
||||
|
||||
```jsx
|
||||
const MyDataProcessor = () => {
|
||||
const [fileContent, setFileContent] = React.useState('');
|
||||
const [fileName, setFileName] = React.useState('');
|
||||
|
||||
const handleLoadFile = async () => {
|
||||
const fileData = await readFile(); // Uses default extensions
|
||||
if (fileData) {
|
||||
setFileName(fileData.name);
|
||||
setFileContent(fileData.content);
|
||||
// For CSV, you might parse it here:
|
||||
// if (fileData.extension === 'csv') {
|
||||
// const parsedData = Papa.parse(fileData.content, { header: true });
|
||||
// console.log(parsedData.data);
|
||||
// }
|
||||
} else {
|
||||
new Notice('File selection cancelled.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleLoadFile}>Load File</button>
|
||||
{fileName && <h4>Displaying: {fileName}</h4>}
|
||||
{fileContent && <pre style={{maxHeight: '200px', overflowY: 'auto'}}>{fileContent}</pre>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default MyDataProcessor;
|
||||
```
|
||||
|
||||
### 2. Interactive Selection with Custom Extension Filters
|
||||
|
||||
This will open a modal filtering for only Markdown (`.md`) and text (`.txt`) files.
|
||||
|
||||
```
|
||||
const loadMarkdownOrTextFile = async () => {
|
||||
const fileData = await readFile(null, ['md', 'txt']);
|
||||
if (fileData) {
|
||||
console.log(`Content of ${fileData.path}:`, fileData.content);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Direct File Access by Path
|
||||
|
||||
This will attempt to read `path/to/my/config.json` directly without showing a modal.
|
||||
|
||||
```
|
||||
const loadSpecificConfig = async () => {
|
||||
const filePath = 'configs/settings.json'; // Relative to vault root
|
||||
const fileData = await readFile(filePath);
|
||||
|
||||
if (fileData && fileData.extension === 'json') {
|
||||
try {
|
||||
const config = JSON.parse(fileData.content);
|
||||
console.log('Configuration loaded:', config);
|
||||
// Use the config object
|
||||
} catch (e) {
|
||||
new Notice(`Error parsing JSON from ${filePath}: ${e.message}`);
|
||||
console.error("JSON parsing error:", e);
|
||||
}
|
||||
} else if (fileData) {
|
||||
new Notice(`Expected a JSON file but got .${fileData.extension}`);
|
||||
} else {
|
||||
new Notice(`Could not read file: ${filePath}. Ensure it exists.`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Reading CSV Data for Charts
|
||||
|
||||
```
|
||||
// (Assuming you have a chart component that takes data)
|
||||
const CSVChartLoader = () => {
|
||||
const [chartData, setChartData] = React.useState(null);
|
||||
|
||||
const loadCSV = async () => {
|
||||
const fileData = await readFile(null, ['csv']);
|
||||
if (fileData && fileData.extension === 'csv') {
|
||||
// Assuming PapaParse is available in scope
|
||||
const parsed = Papa.parse(fileData.content, { header: true, dynamicTyping: true });
|
||||
if (parsed.errors.length > 0) {
|
||||
new Notice("Errors found while parsing CSV: " + parsed.errors[0].message);
|
||||
return;
|
||||
}
|
||||
setChartData(parsed.data);
|
||||
new Notice(`Loaded ${parsed.data.length} rows from ${fileData.name}`);
|
||||
} else if (fileData) {
|
||||
new Notice(`Selected file was not a CSV: ${fileData.name}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={loadCSV}>Load CSV for Chart</button>
|
||||
{/* {chartData && <MyChartComponent data={chartData} />} */}
|
||||
{chartData && <pre>Data Loaded: {JSON.stringify(chartData.slice(0,2))}...</pre>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default CSVChartLoader;
|
||||
```
|
||||
|
||||
By providing both interactive selection and direct path access, `readFile` offers flexibility for various component needs within ReactiveNotes.
|
||||
1
docs/EXAMPLES/README.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**(To be Populated)**
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"id": "reactive-notes",
|
||||
"name": "Reactive Notes",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.5",
|
||||
"minAppVersion": "1.4.0",
|
||||
"description": "Transform your Obsidian vault into a reactive computational environment. Create dynamic React components directly in your notes.",
|
||||
"description": "Transform your vault into a reactive computational environment. Create dynamic React components directly in your notes.",
|
||||
"author": "Elias Noesis",
|
||||
"authorUrl": "https://github.com/Prodigist",
|
||||
"isDesktopOnly": false,
|
||||
"fundingUrl": {
|
||||
"Buy Me A Coffee": "https://www.buymeacoffee.com/prodigist"
|
||||
}
|
||||
}
|
||||
}
|
||||
10782
outStyles.css
Normal file
416
package-lock.json
generated
|
|
@ -1,13 +1,12 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"name": "reactive-notes",
|
||||
"version": "1.0.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"name": "reactive-notes",
|
||||
"version": "1.0.5",
|
||||
"dependencies": {
|
||||
"@codemirror/language": "^6.10.6",
|
||||
"@emotion/css": "^11.13.5",
|
||||
|
|
@ -17,9 +16,11 @@
|
|||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3": "^7.9.0",
|
||||
"d3-force": "^3.0.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"force-graph": "^1.49.5",
|
||||
"lightweight-charts": "^4.2.1",
|
||||
"lucide-react": "^0.462.0",
|
||||
"postcss": "^8.4.49",
|
||||
|
|
@ -40,6 +41,7 @@
|
|||
"@babel/preset-react": "^7.25.9",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@babel/standalone": "^7.26.2",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^18.3.12",
|
||||
|
|
@ -1848,27 +1850,156 @@
|
|||
"@types/tern": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
|
||||
"integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/d3-axis": "*",
|
||||
"@types/d3-brush": "*",
|
||||
"@types/d3-chord": "*",
|
||||
"@types/d3-color": "*",
|
||||
"@types/d3-contour": "*",
|
||||
"@types/d3-delaunay": "*",
|
||||
"@types/d3-dispatch": "*",
|
||||
"@types/d3-drag": "*",
|
||||
"@types/d3-dsv": "*",
|
||||
"@types/d3-ease": "*",
|
||||
"@types/d3-fetch": "*",
|
||||
"@types/d3-force": "*",
|
||||
"@types/d3-format": "*",
|
||||
"@types/d3-geo": "*",
|
||||
"@types/d3-hierarchy": "*",
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-path": "*",
|
||||
"@types/d3-polygon": "*",
|
||||
"@types/d3-quadtree": "*",
|
||||
"@types/d3-random": "*",
|
||||
"@types/d3-scale": "*",
|
||||
"@types/d3-scale-chromatic": "*",
|
||||
"@types/d3-selection": "*",
|
||||
"@types/d3-shape": "*",
|
||||
"@types/d3-time": "*",
|
||||
"@types/d3-time-format": "*",
|
||||
"@types/d3-timer": "*",
|
||||
"@types/d3-transition": "*",
|
||||
"@types/d3-zoom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
|
||||
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
|
||||
},
|
||||
"node_modules/@types/d3-axis": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz",
|
||||
"integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-brush": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz",
|
||||
"integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-chord": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz",
|
||||
"integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-color": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
|
||||
},
|
||||
"node_modules/@types/d3-contour": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz",
|
||||
"integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-array": "*",
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-dispatch": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz",
|
||||
"integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-drag": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-dsv": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz",
|
||||
"integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-ease": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
|
||||
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
|
||||
},
|
||||
"node_modules/@types/d3-fetch": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz",
|
||||
"integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-dsv": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-force": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz",
|
||||
"integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-format": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz",
|
||||
"integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-geo": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz",
|
||||
"integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/geojson": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-hierarchy": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz",
|
||||
"integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-interpolate": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
|
||||
|
|
@ -1882,6 +2013,24 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
|
||||
"integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ=="
|
||||
},
|
||||
"node_modules/@types/d3-polygon": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz",
|
||||
"integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-quadtree": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz",
|
||||
"integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-random": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz",
|
||||
"integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
|
||||
|
|
@ -1890,6 +2039,18 @@
|
|||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-scale-chromatic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
|
||||
"integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-selection": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
|
||||
|
|
@ -1903,17 +2064,48 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
|
||||
},
|
||||
"node_modules/@types/d3-time-format": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz",
|
||||
"integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||
},
|
||||
"node_modules/@types/d3-transition": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-zoom": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/d3-interpolate": "*",
|
||||
"@types/d3-selection": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
|
|
@ -2870,6 +3062,46 @@
|
|||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
||||
},
|
||||
"node_modules/d3": {
|
||||
"version": "7.9.0",
|
||||
"resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz",
|
||||
"integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==",
|
||||
"dependencies": {
|
||||
"d3-array": "3",
|
||||
"d3-axis": "3",
|
||||
"d3-brush": "3",
|
||||
"d3-chord": "3",
|
||||
"d3-color": "3",
|
||||
"d3-contour": "4",
|
||||
"d3-delaunay": "6",
|
||||
"d3-dispatch": "3",
|
||||
"d3-drag": "3",
|
||||
"d3-dsv": "3",
|
||||
"d3-ease": "3",
|
||||
"d3-fetch": "3",
|
||||
"d3-force": "3",
|
||||
"d3-format": "3",
|
||||
"d3-geo": "3",
|
||||
"d3-hierarchy": "3",
|
||||
"d3-interpolate": "3",
|
||||
"d3-path": "3",
|
||||
"d3-polygon": "3",
|
||||
"d3-quadtree": "3",
|
||||
"d3-random": "3",
|
||||
"d3-scale": "4",
|
||||
"d3-scale-chromatic": "3",
|
||||
"d3-selection": "3",
|
||||
"d3-shape": "3",
|
||||
"d3-time": "3",
|
||||
"d3-time-format": "4",
|
||||
"d3-timer": "3",
|
||||
"d3-transition": "3",
|
||||
"d3-zoom": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
|
|
@ -2881,11 +3113,45 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-axis": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz",
|
||||
"integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-binarytree": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz",
|
||||
"integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw=="
|
||||
},
|
||||
"node_modules/d3-brush": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz",
|
||||
"integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==",
|
||||
"dependencies": {
|
||||
"d3-dispatch": "1 - 3",
|
||||
"d3-drag": "2 - 3",
|
||||
"d3-interpolate": "1 - 3",
|
||||
"d3-selection": "3",
|
||||
"d3-transition": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-chord": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz",
|
||||
"integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==",
|
||||
"dependencies": {
|
||||
"d3-path": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
|
||||
|
|
@ -2894,6 +3160,28 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-contour": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz",
|
||||
"integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==",
|
||||
"dependencies": {
|
||||
"d3-array": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
|
||||
"dependencies": {
|
||||
"delaunator": "5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dispatch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz",
|
||||
|
|
@ -2914,6 +3202,38 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dsv": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz",
|
||||
"integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==",
|
||||
"dependencies": {
|
||||
"commander": "7",
|
||||
"iconv-lite": "0.6",
|
||||
"rw": "1"
|
||||
},
|
||||
"bin": {
|
||||
"csv2json": "bin/dsv2json.js",
|
||||
"csv2tsv": "bin/dsv2dsv.js",
|
||||
"dsv2dsv": "bin/dsv2dsv.js",
|
||||
"dsv2json": "bin/dsv2json.js",
|
||||
"json2csv": "bin/json2dsv.js",
|
||||
"json2dsv": "bin/json2dsv.js",
|
||||
"json2tsv": "bin/json2dsv.js",
|
||||
"tsv2csv": "bin/dsv2dsv.js",
|
||||
"tsv2json": "bin/dsv2json.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-dsv/node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-ease": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
|
||||
|
|
@ -2922,6 +3242,17 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-fetch": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz",
|
||||
"integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==",
|
||||
"dependencies": {
|
||||
"d3-dsv": "1 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-force": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz",
|
||||
|
|
@ -2958,6 +3289,25 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-geo": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz",
|
||||
"integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==",
|
||||
"dependencies": {
|
||||
"d3-array": "2.5.0 - 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-hierarchy": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz",
|
||||
"integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-interpolate": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
|
||||
|
|
@ -2982,6 +3332,14 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-polygon": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz",
|
||||
"integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-quadtree": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz",
|
||||
|
|
@ -2990,6 +3348,14 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-random": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz",
|
||||
"integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-scale": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
|
||||
|
|
@ -3170,6 +3536,14 @@
|
|||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
|
||||
"integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ=="
|
||||
},
|
||||
"node_modules/delaunator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
|
||||
"integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
|
|
@ -3721,9 +4095,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/force-graph": {
|
||||
"version": "1.49.0",
|
||||
"resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.49.0.tgz",
|
||||
"integrity": "sha512-S8ODRE6eVtHtkIPCRu9Zj03uL/l8EpwKIZnIzLZO6aiZIMQLI8JguEeT3uCozT9kB2nLXem0xCiA7Pnk38Yy7g==",
|
||||
"version": "1.49.5",
|
||||
"resolved": "https://registry.npmjs.org/force-graph/-/force-graph-1.49.5.tgz",
|
||||
"integrity": "sha512-mCTLxsaOPfp4Jq4FND8sHTpa8aZDLNXgkwAN98IDZ8Ve3nralz0gNsmE4Nx6NFm48olJ0gzCQYYLJrrYDqifew==",
|
||||
"dependencies": {
|
||||
"@tweenjs/tween.js": "18 - 25",
|
||||
"accessor-fn": "1",
|
||||
|
|
@ -3982,6 +4356,17 @@
|
|||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
|
|
@ -5378,6 +5763,11 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
|
|
@ -5400,6 +5790,16 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "reactive-notes",
|
||||
"version": "1.0.0",
|
||||
"description": "Transform your Obsidian vault into a reactive computational environment with React components.",
|
||||
"version": "1.0.5",
|
||||
"description": "Transform your vault into a reactive computational environment with React components.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
"@babel/preset-react": "^7.25.9",
|
||||
"@babel/preset-typescript": "^7.26.0",
|
||||
"@babel/standalone": "^7.26.2",
|
||||
"@types/d3": "^7.4.3",
|
||||
"@types/d3-force": "^3.0.10",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "^18.3.12",
|
||||
|
|
@ -40,9 +41,11 @@
|
|||
"autoprefixer": "^10.4.20",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"d3": "^7.9.0",
|
||||
"d3-force": "^3.0.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"force-graph": "^1.49.5",
|
||||
"lightweight-charts": "^4.2.1",
|
||||
"lucide-react": "^0.462.0",
|
||||
"postcss": "^8.4.49",
|
||||
|
|
|
|||
|
|
@ -1,22 +1,36 @@
|
|||
// src/config/componentRegistry.ts
|
||||
// src/config/componentRegistry.tsx
|
||||
import React from 'react';
|
||||
import {
|
||||
PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend,
|
||||
ComposedChart, BarChart, XAxis, YAxis, Bar, Line, Area,
|
||||
ReferenceLine, LineChart
|
||||
ReferenceLine, LineChart, CartesianGrid, Treemap,
|
||||
ScatterChart, Scatter, // For plotting points
|
||||
RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis, // For radar charts
|
||||
AreaChart, // Dedicated area chart
|
||||
RadialBarChart, RadialBar, // For radial/circular progress
|
||||
} from 'recharts';
|
||||
import { createChart } from 'lightweight-charts';
|
||||
import { forceSimulation, forceLink, forceManyBody, forceCenter } from 'd3-force';
|
||||
import {
|
||||
Upload, Activity, AlertCircle, TrendingUp, TrendingDown,
|
||||
// Add other icons as needed
|
||||
Upload, Activity, AlertCircle, TrendingUp, TrendingDown, Home, BarChart2, BarChart3, Code, Settings, Moon, Sun, ChevronLeft, ChevronRight, Heart, ExternalLink,
|
||||
Play, Image as ImageIcon, Check, X, Plus, Minus, ArrowRightCircle, ArrowLeftCircle, ArrowUpCircle, ArrowDownCircle,
|
||||
ChevronUp, ChevronDown, ArrowRight, ArrowLeft, ArrowUp, ArrowDown, PlusCircle, MinusCircle, XCircle, CheckCircle,
|
||||
Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, Circle, Clock, Calendar,
|
||||
} from 'lucide-react';
|
||||
import * as LucideIcons from 'lucide-react';
|
||||
import {
|
||||
Card, CardHeader, CardTitle, CardContent,
|
||||
Card, CardHeader, CardTitle, CardContent, CardFooter,
|
||||
Tabs, TabsContent, TabsList, TabsTrigger,
|
||||
Switch,
|
||||
Switch, CardDescription,
|
||||
} from 'src/components/ui/';
|
||||
|
||||
//Data Processing Libraries
|
||||
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)
|
||||
export const ReactUtils = {
|
||||
|
|
@ -46,12 +60,20 @@ export const Components = {
|
|||
Area,
|
||||
ReferenceLine,
|
||||
LineChart,
|
||||
CartesianGrid,
|
||||
Treemap, ScatterChart, Scatter,
|
||||
|
||||
RadarChart, Radar, PolarGrid, PolarAngleAxis, PolarRadiusAxis,
|
||||
AreaChart,
|
||||
RadialBarChart, RadialBar,
|
||||
|
||||
// UI Components
|
||||
Card,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardDescription,
|
||||
Tabs,
|
||||
TabsContent,
|
||||
TabsList,
|
||||
|
|
@ -59,13 +81,35 @@ export const Components = {
|
|||
Switch,
|
||||
|
||||
// Icons
|
||||
|
||||
Upload,
|
||||
Activity,
|
||||
AlertCircle,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Home,
|
||||
BarChart3,
|
||||
Code,
|
||||
Settings,
|
||||
Moon,
|
||||
Sun,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Heart,
|
||||
ExternalLink, BarChart2,
|
||||
Play, ImageIcon, Check, X, Plus, Minus, ArrowRightCircle, ArrowLeftCircle, ArrowUpCircle, ArrowDownCircle,
|
||||
ChevronUp, ChevronDown, ArrowRight, ArrowLeft, ArrowUp, ArrowDown, PlusCircle, MinusCircle, XCircle, CheckCircle,
|
||||
Info, Search, Trash, Edit, Save, Share, Copy, Pause, Undo, Redo, ZoomIn, ZoomOut, PlayCircle, PauseCircle, Circle, Clock, Calendar,
|
||||
LucideIcons,
|
||||
//data processing utilities
|
||||
dateFns: { format, addDays, differenceInDays, parseISO },
|
||||
csv: { parse, stringify },
|
||||
math: mathjs,
|
||||
XLSX,
|
||||
Papa,
|
||||
} as const;
|
||||
|
||||
|
||||
// Utilities that aren't React components
|
||||
export const Utilities = {
|
||||
createChart,
|
||||
|
|
@ -73,7 +117,6 @@ export const Utilities = {
|
|||
forceLink,
|
||||
forceManyBody,
|
||||
forceCenter,
|
||||
THREE: (window as any).THREE || require('three'), // Use global THREE if available
|
||||
} as const;
|
||||
|
||||
|
||||
|
|
@ -87,7 +130,7 @@ export const ComponentRegistry = {
|
|||
|
||||
|
||||
|
||||
// Optional: Type guard for checking if a component exists
|
||||
// Optional Type guard for checking if a component exists
|
||||
export const hasComponent = (name: string): name is keyof typeof ComponentRegistry => {
|
||||
return name in ComponentRegistry;
|
||||
};
|
||||
|
|
@ -123,7 +166,7 @@ export function getComponent<K extends keyof ComponentsOnly>(name: K): Component
|
|||
return Components[name];
|
||||
}
|
||||
|
||||
// Optional: Type-safe hook usage
|
||||
// Optional Type-safe hook usage
|
||||
type Hook<T> = (...args: any[]) => T;
|
||||
export const getHook = <T>(name: keyof typeof ReactUtils): Hook<T> | undefined => {
|
||||
const hook = ReactUtils[name];
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// src/components/ui/index.ts
|
||||
export { Card, CardHeader, CardTitle, CardContent } from './card';
|
||||
export { Card, CardHeader, CardTitle, CardContent, CardFooter, CardDescription } from './card';
|
||||
export { Tabs, TabsContent, TabsList, TabsTrigger } from './tabs';
|
||||
export { Switch } from './switch';
|
||||
// Add other component exports as needed
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
// src/core/storage.tsx
|
||||
import { MarketDataService, MarketData } from '../services/marketDataService';
|
||||
import { Plugin, TFile,parseYaml,stringifyYaml } from 'obsidian';
|
||||
import { Plugin, TFile, App, parseYaml,stringifyYaml, Notice } from 'obsidian';
|
||||
export class StorageManager {
|
||||
private plugin: Plugin;
|
||||
private cache: Map<string, any>;
|
||||
|
||||
private app: App; // Replace with the actual type of your app if available
|
||||
constructor(plugin: Plugin) {
|
||||
this.plugin = plugin;
|
||||
this.cache = new Map();
|
||||
this.app = plugin.app;
|
||||
}
|
||||
|
||||
private getCacheKey(noteFile: TFile, key: string): string {
|
||||
|
|
@ -18,29 +18,6 @@ export class StorageManager {
|
|||
const metadata = this.plugin.app.metadataCache.getFileCache(noteFile)?.frontmatter;
|
||||
return metadata?.react_data || {};
|
||||
}
|
||||
async getMarketData(symbol: string, interval: string, range: string, noteFile: TFile): Promise<MarketData[]> {
|
||||
const cacheKey = `market-data-${symbol}-${interval}-${range}`;
|
||||
|
||||
// Get from frontmatter
|
||||
const frontmatter = await this.getFrontmatter(noteFile);
|
||||
const cached = frontmatter[cacheKey];
|
||||
|
||||
// Check if we have cached data and if it's still fresh (24 hours)
|
||||
if (cached && Date.now() - cached.timestamp < 24 * 60 * 60 * 1000) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
// Fetch new data
|
||||
const data = (await MarketDataService.fetchHistoricalData(symbol)).data;
|
||||
|
||||
// Cache the data using existing set method
|
||||
await this.set(cacheKey, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
}, noteFile);
|
||||
|
||||
return data;
|
||||
}
|
||||
async get<T>(key: string, defaultValue: T, noteFile: TFile): Promise<T> {
|
||||
const cacheKey = this.getCacheKey(noteFile, key);
|
||||
|
||||
|
|
@ -62,44 +39,20 @@ export class StorageManager {
|
|||
const cacheKey = this.getCacheKey(noteFile, key);
|
||||
this.cache.set(cacheKey, value);
|
||||
|
||||
const content = await this.plugin.app.vault.read(noteFile);
|
||||
const frontmatter = await this.getFrontmatter(noteFile);
|
||||
|
||||
// Prepare new frontmatter
|
||||
const newFrontmatter = {
|
||||
...frontmatter,
|
||||
[key]: value
|
||||
};
|
||||
|
||||
// Update the file with new frontmatter
|
||||
const updatedContent = this.updateFrontmatter(content, newFrontmatter);
|
||||
await this.plugin.app.vault.modify(noteFile, updatedContent);
|
||||
}
|
||||
|
||||
private updateFrontmatter(content: string, newData: any): string {
|
||||
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
||||
const frontmatterMatch = content.match(frontmatterRegex);
|
||||
|
||||
if (frontmatterMatch) {
|
||||
// Update existing frontmatter
|
||||
let frontmatter;
|
||||
try {
|
||||
frontmatter = parseYaml(frontmatterMatch[1]);
|
||||
} catch (e) {
|
||||
frontmatter = {};
|
||||
}
|
||||
|
||||
frontmatter.react_data = {
|
||||
...frontmatter.react_data,
|
||||
...newData
|
||||
};
|
||||
|
||||
const newFrontmatter = stringifyYaml(frontmatter);
|
||||
return content.replace(frontmatterRegex, `---\n${newFrontmatter}---`);
|
||||
} else {
|
||||
// Create new frontmatter
|
||||
const newFrontmatter = stringifyYaml({ react_data: newData });
|
||||
return `---\n${newFrontmatter}---\n\n${content}`;
|
||||
try {
|
||||
await this.app.fileManager.processFrontMatter(noteFile, (frontmatter) => {
|
||||
// Initialize react_data if it doesn't exist
|
||||
if (!frontmatter.react_data) {
|
||||
frontmatter.react_data = {};
|
||||
}
|
||||
|
||||
// Update the specific key
|
||||
frontmatter.react_data[key] = value;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to save data for key "${key}":`, error);
|
||||
new Notice(`Failed to save component state: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
213
src/hooks/mathJax.tsx
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
import { e } from "mathjs";
|
||||
import React from "react";
|
||||
export function useMathJax(children: React.ReactNode) {
|
||||
// const containerRef = React.useRef(null);
|
||||
const containerRef = React.useRef<HTMLDivElement>(null);
|
||||
React.useEffect(() => {
|
||||
if (!containerRef.current || !window.MathJax) {
|
||||
if (!window.MathJax) {
|
||||
console.warn("MathJax not available when RenderWrapper mounted.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const container = containerRef.current;
|
||||
let mathJaxProcessed = false; // Flag to ensure typesetPromise runs only after processing
|
||||
interface MathMatch {
|
||||
type: 'math';
|
||||
matched: string;
|
||||
displayMath?: string;
|
||||
inlineMath?: string;
|
||||
escapedMath?: string;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
|
||||
interface CodeMatch {
|
||||
type: 'code';
|
||||
matched: string;
|
||||
delimiter: string;
|
||||
content: string;
|
||||
startIndex: number;
|
||||
endIndex: number;
|
||||
}
|
||||
|
||||
type Match = MathMatch | CodeMatch;
|
||||
const processSingleTextNode = (textNode: Text): DocumentFragment | null => {
|
||||
let currentText = textNode.nodeValue || '';
|
||||
if (!currentText.includes('$')) { // No dollar signs at all, nothing to do
|
||||
return null;
|
||||
}
|
||||
// Find all matches of both patterns
|
||||
const allMatches: Match[] = [];
|
||||
const fragment = document.createDocumentFragment();
|
||||
let lastIndex = 0;
|
||||
|
||||
// Regex to find $$...$$ (unescaped) or $...$ (unescaped) or escaped \$ or \$\$
|
||||
const mathRegex = /(?<!\\)\$\$(.*?)(?<!\\)\$\$|(?<!\\)\$(?!\s)(.*?)(?<!\s)(?<!\\)\$|\\(\$\$|\$)/gs;
|
||||
|
||||
let mathMatch;
|
||||
while ((mathMatch = mathRegex.exec(currentText)) !== null) {
|
||||
allMatches.push({
|
||||
type: 'math',
|
||||
matched: mathMatch[0],
|
||||
displayMath: mathMatch[1],
|
||||
inlineMath: mathMatch[2],
|
||||
escapedMath: mathMatch[3],
|
||||
startIndex: mathMatch.index,
|
||||
endIndex: mathMatch.index + mathMatch[0].length
|
||||
});
|
||||
}
|
||||
|
||||
const codeBlockRegex = /(`+)((?:(?!\1).)*?)\1/gs;
|
||||
let codeMatch;
|
||||
while ((codeMatch = codeBlockRegex.exec(currentText)) !== null) {
|
||||
allMatches.push({
|
||||
type: 'code',
|
||||
matched: codeMatch[0],
|
||||
delimiter: codeMatch[1],
|
||||
content: codeMatch[2],
|
||||
startIndex: codeMatch.index,
|
||||
endIndex: codeMatch.index + codeMatch[0].length
|
||||
});
|
||||
}
|
||||
// Sort all matches by start index
|
||||
allMatches.sort((a, b) => a.startIndex - b.startIndex);
|
||||
|
||||
// Track active matches (open but not yet closed)
|
||||
const activeMatches: Match[] = [];
|
||||
|
||||
// Process text with matches
|
||||
for (let i = 0; i < allMatches.length; i++) {
|
||||
const current = allMatches[i];
|
||||
|
||||
// Add text before this match
|
||||
if (current.startIndex > lastIndex) {
|
||||
fragment.appendChild(document.createTextNode(
|
||||
currentText.substring(lastIndex, current.startIndex)
|
||||
));
|
||||
}
|
||||
|
||||
// Check if this match is inside any active match
|
||||
const isInsideCode = activeMatches.some(m =>
|
||||
m.type === 'code' && current.startIndex > m.startIndex && current.endIndex <= m.endIndex
|
||||
);
|
||||
|
||||
if (isInsideCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process based on type
|
||||
if (current.type === 'code') {
|
||||
fragment.appendChild(document.createTextNode(current.matched));
|
||||
activeMatches.push(current);
|
||||
|
||||
// Remove from active once processed
|
||||
setTimeout(() => {
|
||||
const index = activeMatches.indexOf(current);
|
||||
if (index > -1) activeMatches.splice(index, 1);
|
||||
}, 0);
|
||||
} else if (current.type === 'math') {
|
||||
if (current.displayMath !== undefined) {
|
||||
try {
|
||||
const mathNode = window.MathJax.tex2chtml(current.displayMath, { display: true });
|
||||
fragment.appendChild(mathNode);
|
||||
} catch (e) {
|
||||
console.error("MathJax display math error:", e);
|
||||
fragment.appendChild(document.createTextNode(current.matched));
|
||||
}
|
||||
} else if (current.inlineMath !== undefined) {
|
||||
try {
|
||||
const mathNode = window.MathJax.tex2chtml(current.inlineMath, { display: false });
|
||||
fragment.appendChild(mathNode);
|
||||
} catch (e) {
|
||||
console.error("MathJax inline math error:", e);
|
||||
fragment.appendChild(document.createTextNode(current.matched));
|
||||
}
|
||||
} else if (current.escapedMath !== undefined) {
|
||||
fragment.appendChild(document.createTextNode(current.escapedMath));
|
||||
}
|
||||
}
|
||||
|
||||
lastIndex = current.endIndex;
|
||||
}
|
||||
|
||||
// Add remaining text
|
||||
if (lastIndex < currentText.length) {
|
||||
fragment.appendChild(document.createTextNode(
|
||||
currentText.substring(lastIndex)
|
||||
));
|
||||
}
|
||||
|
||||
return fragment;
|
||||
|
||||
};
|
||||
|
||||
const processMathInElement = (element: HTMLElement) => {
|
||||
const textNodesToReplace: { originalNode: Text, newFragment: DocumentFragment }[] = [];
|
||||
|
||||
const walker = document.createTreeWalker(
|
||||
element,
|
||||
NodeFilter.SHOW_TEXT,
|
||||
null
|
||||
);
|
||||
|
||||
let currentNode;
|
||||
while ((currentNode = walker.nextNode())) {
|
||||
if (currentNode instanceof Text) {
|
||||
const newContentFragment = processSingleTextNode(currentNode);
|
||||
if (newContentFragment) {
|
||||
textNodesToReplace.push({ originalNode: currentNode, newFragment: newContentFragment });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform DOM replacements after identifying all nodes to avoid issues with the walker
|
||||
textNodesToReplace.forEach(({ originalNode, newFragment }) => {
|
||||
originalNode.parentNode?.replaceChild(newFragment, originalNode);
|
||||
});
|
||||
};
|
||||
|
||||
const typesetMath = async () => {
|
||||
if (!window.MathJax || !containerRef.current) return;
|
||||
|
||||
try {
|
||||
// Ensure MathJax startup is complete (important for initial loads)
|
||||
if (window.MathJax.startup && typeof window.MathJax.startup.promise === 'object') {
|
||||
await window.MathJax.startup.promise;
|
||||
} else if (typeof window.MathJax.startup?.promise === 'function' ) { // older MathJax might have it as a function
|
||||
await window.MathJax.startup.promise();
|
||||
}
|
||||
|
||||
|
||||
processMathInElement(container);
|
||||
|
||||
// Only call typesetPromise if we actually processed some math and if the container still exists
|
||||
if (mathJaxProcessed && containerRef.current && typeof window.MathJax.typesetPromise === 'function') {
|
||||
await window.MathJax.typesetPromise([containerRef.current]);
|
||||
console.log("MathJax typesetting complete for:", containerRef.current);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error during MathJax processing or typesetting:", e);
|
||||
}
|
||||
};
|
||||
|
||||
typesetMath();
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
try {
|
||||
// Clear MathJax's knowledge of this container when the component unmounts
|
||||
// or if the children prop changes, leading to this effect re-running.
|
||||
if (window.MathJax && containerRef.current && typeof window.MathJax.typesetClear === 'function') {
|
||||
window.MathJax.typesetClear([containerRef.current]);
|
||||
console.log("MathJax state cleared for:", containerRef.current);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("MathJax cleanup error:", e);
|
||||
}
|
||||
};
|
||||
}, [children]); // Re-run if children prop changes, implying new content to process.
|
||||
return containerRef;
|
||||
}
|
||||
export default useMathJax;
|
||||
11052
styles.css
|
|
@ -1,3 +1,6 @@
|
|||
{
|
||||
"1.0.0": "1.4.0"
|
||||
}
|
||||
"1.0.2": "1.4.0",
|
||||
"1.0.3": "1.4.0",
|
||||
"1.0.4": "1.4.0",
|
||||
"1.0.5": "1.4.0"
|
||||
}
|
||||