holon shape integration testing
This commit is contained in:
parent
7e3cca656e
commit
2480d16e70
|
|
@ -0,0 +1,122 @@
|
||||||
|
# Holon Shape for Canvas Website
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Holon shape is a new tool that allows users to interact with Holon data objects through a visual interface. It provides functionality to input a Holon ID and perform put/get operations on JSON tasklists.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Holon ID Input**: Enter a Holon ID (e.g., -4962820663) to connect to a specific holon
|
||||||
|
- **Tasklist Management**: Load, view, and manage tasklists from the holon
|
||||||
|
- **Task Operations**: Add new tasks to existing or new tasklists
|
||||||
|
- **Task Completion**: Toggle task completion status
|
||||||
|
- **Real-time Updates**: Changes are immediately reflected in the holon data
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. **Adding the Holon Shape**:
|
||||||
|
- Select the Holon tool from the toolbar (star icon) or context menu
|
||||||
|
- Use keyboard shortcut `Alt+H` to quickly select the Holon tool
|
||||||
|
- Click and drag on the canvas to create a new Holon shape
|
||||||
|
|
||||||
|
2. **Connecting to a Holon**:
|
||||||
|
- Enter a Holon ID in the input field
|
||||||
|
- Click "Load Tasklists" to fetch existing tasklists
|
||||||
|
|
||||||
|
3. **Managing Tasks**:
|
||||||
|
- Enter a tasklist name and task description
|
||||||
|
- Click "Add" to create a new task
|
||||||
|
- Check/uncheck tasks to mark them as complete/incomplete
|
||||||
|
|
||||||
|
## Technical Implementation
|
||||||
|
|
||||||
|
### Files Created/Modified
|
||||||
|
|
||||||
|
- `src/shapes/HolonShapeUtil.tsx` - Main shape utility with UI component and sync support
|
||||||
|
- `src/tools/HolonShapeTool.ts` - Tool definition for the Holon shape
|
||||||
|
- `src/routes/Board.tsx` - Updated to include Holon shape and tool
|
||||||
|
- `src/ui/overrides.tsx` - Added Holon tool definition with keyboard shortcut (Alt+H)
|
||||||
|
- `src/ui/CustomContextMenu.tsx` - Added Holon tool to context menu
|
||||||
|
- `src/ui/CustomToolbar.tsx` - Added Holon tool to toolbar
|
||||||
|
- `worker/TldrawDurableObject.ts` - Added Holon shape to worker schema for multiplayer sync
|
||||||
|
|
||||||
|
### HoloSphere Class
|
||||||
|
|
||||||
|
The `HoloSphere` class provides the interface for interacting with Holon data:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class HoloSphere {
|
||||||
|
async getAll(holonId: string, dataType: string): Promise<any[]>
|
||||||
|
async put(holonId: string, dataType: string, data: any): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
The shape currently uses these API endpoints:
|
||||||
|
- `GET https://api.holons.io/holons/{holonId}/tasklists` - Fetch tasklists
|
||||||
|
- `PUT https://api.holons.io/holons/{holonId}/tasklists` - Update tasklists
|
||||||
|
|
||||||
|
## Data Structure
|
||||||
|
|
||||||
|
Tasklists are stored as JSON arrays with the following structure:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Tasklist {
|
||||||
|
name: string
|
||||||
|
tasks: Task[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: number
|
||||||
|
text: string
|
||||||
|
completed: boolean
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with Holons i/o
|
||||||
|
|
||||||
|
This shape integrates with the Holons i/o ecosystem, allowing users to:
|
||||||
|
|
||||||
|
- Connect to their personal me-holon and we-holons
|
||||||
|
- Manage tasks across different holons
|
||||||
|
- Collaborate on shared tasklists
|
||||||
|
- Track task completion and progress
|
||||||
|
|
||||||
|
## Sync Support
|
||||||
|
|
||||||
|
The Holon shape is fully integrated with TL-Draw's sync system:
|
||||||
|
|
||||||
|
- **Multiplayer Support**: All state changes are synchronized across multiple users
|
||||||
|
- **Real-time Updates**: Task additions, completions, and Holon ID changes sync immediately
|
||||||
|
- **State Persistence**: Shape state is preserved in the room's persistent storage
|
||||||
|
- **Conflict Resolution**: Uses TL-Draw's built-in conflict resolution for concurrent edits
|
||||||
|
- **Worker Schema**: Properly registered in the worker schema for multiplayer sessions
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- Support for other Holon data types (offers, requests, expenses, etc.)
|
||||||
|
- Real-time synchronization with other users
|
||||||
|
- Integration with the Holons Bot commands
|
||||||
|
- Support for nested tasklists and subtasks
|
||||||
|
- Export/import functionality for tasklists
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example Holon ID from the Holons i/o system
|
||||||
|
const holonId = "-4962820663"
|
||||||
|
|
||||||
|
// The shape will automatically handle:
|
||||||
|
// - Fetching existing tasklists
|
||||||
|
// - Adding new tasks
|
||||||
|
// - Updating task completion status
|
||||||
|
// - Persisting changes to the holon
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The shape currently uses simulated API endpoints
|
||||||
|
- Error handling is implemented for network failures
|
||||||
|
- The UI is responsive and follows the existing design patterns
|
||||||
|
- All data operations are logged to the console for debugging
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"cherry-markdown": "^0.8.57",
|
"cherry-markdown": "^0.8.57",
|
||||||
"cloudflare-workers-unfurl": "^0.0.7",
|
"cloudflare-workers-unfurl": "^0.0.7",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"holosphere": "^1.1.17",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"itty-router": "^5.0.17",
|
"itty-router": "^5.0.17",
|
||||||
"jotai": "^2.6.0",
|
"jotai": "^2.6.0",
|
||||||
|
|
@ -1847,6 +1848,48 @@
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@peculiar/asn1-schema": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-umbembjIWOrPSOzEGG5vxFLkeM8kzIhLkgigtsOrfLKnuzxWxejAcUX+q/SoZCdemlODOcr5WiYa7+dIEzBXZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"asn1js": "^3.0.6",
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/json-schema": {
|
||||||
|
"version": "1.1.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz",
|
||||||
|
"integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@peculiar/webcrypto": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
|
"@peculiar/json-schema": "^1.1.12",
|
||||||
|
"pvtsutils": "^1.3.5",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"webcrypto-core": "^1.8.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
"node_modules/@radix-ui/number": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||||
|
|
@ -4286,6 +4329,21 @@
|
||||||
"printable-characters": "^1.0.42"
|
"printable-characters": "^1.0.42"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asn1js": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pvtsutils": "^1.3.6",
|
||||||
|
"pvutils": "^1.1.3",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/async-listen": {
|
"node_modules/async-listen": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/async-listen/-/async-listen-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/async-listen/-/async-listen-1.2.0.tgz",
|
||||||
|
|
@ -6317,6 +6375,22 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/fastq": {
|
"node_modules/fastq": {
|
||||||
"version": "1.18.0",
|
"version": "1.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
|
||||||
|
|
@ -6626,6 +6700,53 @@
|
||||||
"node": ">=6.0"
|
"node": ">=6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/gun": {
|
||||||
|
"version": "0.2020.1241",
|
||||||
|
"resolved": "https://registry.npmjs.org/gun/-/gun-0.2020.1241.tgz",
|
||||||
|
"integrity": "sha512-rmGqLuJj4fAuZ/0lddCvXHbENPkEnBOBYpq+kXHrwQ5RdNtQ5p0Io99lD1qUXMFmtwNacQ/iqo3VTmjmMyAYZg==",
|
||||||
|
"license": "(Zlib OR MIT OR Apache-2.0)",
|
||||||
|
"dependencies": {
|
||||||
|
"ws": "^7.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.4"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@peculiar/webcrypto": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gun/node_modules/ws": {
|
||||||
|
"version": "7.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||||
|
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/h3-js": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/h3-js/-/h3-js-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-HYiUrq5qTRFqMuQu3jEHqxXLk1zsSJiby9Lja/k42wHjabZG7tN9rOuzT/PEFf+Wa7rsnHLMHRWIu0mgcJ0ewQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4",
|
||||||
|
"npm": ">=3",
|
||||||
|
"yarn": ">=1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hamt_plus": {
|
"node_modules/hamt_plus": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
|
||||||
|
|
@ -6936,6 +7057,49 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/holosphere": {
|
||||||
|
"version": "1.1.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/holosphere/-/holosphere-1.1.17.tgz",
|
||||||
|
"integrity": "sha512-tQsP9lFoOnU1KDqU2s+TZTj3P5GCzN8DXtl2VXSIg1DY+8SoflqdI7nFAfW8JrgqIBvRTKKq38Zw22gdrhGZnA==",
|
||||||
|
"license": "GPL-3.0-or-later",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.12.0",
|
||||||
|
"gun": "^0.2020.1240",
|
||||||
|
"h3-js": "^4.1.0",
|
||||||
|
"openai": "^4.85.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"gun": "^0.2020.1240",
|
||||||
|
"h3-js": "^4.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"openai": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/holosphere/node_modules/ajv": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/holosphere/node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/hotkeys-js": {
|
"node_modules/hotkeys-js": {
|
||||||
"version": "3.13.9",
|
"version": "3.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.9.tgz",
|
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.9.tgz",
|
||||||
|
|
@ -8938,9 +9102,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openai": {
|
"node_modules/openai": {
|
||||||
"version": "4.79.3",
|
"version": "4.104.0",
|
||||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.79.3.tgz",
|
"resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz",
|
||||||
"integrity": "sha512-0yAnr6oxXAyVrYwLC1jA0KboyU7DjEmrfTXQX+jSpE+P4i72AI/Lxx5pvR3r9i5X7G33835lL+ZrnQ+MDvyuUg==",
|
"integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
|
|
@ -9226,6 +9390,26 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pvtsutils": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
|
||||||
|
"integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pvutils": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/querystringify": {
|
"node_modules/querystringify": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||||
|
|
@ -11328,6 +11512,20 @@
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/webcrypto-core": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@peculiar/asn1-schema": "^2.3.13",
|
||||||
|
"@peculiar/json-schema": "^1.1.12",
|
||||||
|
"asn1js": "^3.0.5",
|
||||||
|
"pvtsutils": "^1.3.5",
|
||||||
|
"tslib": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
"cherry-markdown": "^0.8.57",
|
"cherry-markdown": "^0.8.57",
|
||||||
"cloudflare-workers-unfurl": "^0.0.7",
|
"cloudflare-workers-unfurl": "^0.0.7",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"holosphere": "^1.1.17",
|
||||||
"html2canvas": "^1.4.1",
|
"html2canvas": "^1.4.1",
|
||||||
"itty-router": "^5.0.17",
|
"itty-router": "^5.0.17",
|
||||||
"jotai": "^2.6.0",
|
"jotai": "^2.6.0",
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ import { SlideShape } from "@/shapes/SlideShapeUtil"
|
||||||
import { makeRealSettings, applySettingsMigrations } from "@/lib/settings"
|
import { makeRealSettings, applySettingsMigrations } from "@/lib/settings"
|
||||||
import { PromptShapeTool } from "@/tools/PromptShapeTool"
|
import { PromptShapeTool } from "@/tools/PromptShapeTool"
|
||||||
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
||||||
|
import { HolonShapeTool } from "@/tools/HolonShapeTool"
|
||||||
|
import { HolonShape } from "@/shapes/HolonShapeUtil"
|
||||||
import { llm } from "@/utils/llmUtils"
|
import { llm } from "@/utils/llmUtils"
|
||||||
import {
|
import {
|
||||||
lockElement,
|
lockElement,
|
||||||
|
|
@ -49,6 +51,7 @@ const customShapeUtils = [
|
||||||
MycrozineTemplateShape,
|
MycrozineTemplateShape,
|
||||||
MarkdownShape,
|
MarkdownShape,
|
||||||
PromptShape,
|
PromptShape,
|
||||||
|
HolonShape,
|
||||||
]
|
]
|
||||||
const customTools = [
|
const customTools = [
|
||||||
ChatBoxTool,
|
ChatBoxTool,
|
||||||
|
|
@ -58,6 +61,7 @@ const customTools = [
|
||||||
MycrozineTemplateTool,
|
MycrozineTemplateTool,
|
||||||
MarkdownTool,
|
MarkdownTool,
|
||||||
PromptShapeTool,
|
PromptShapeTool,
|
||||||
|
HolonShapeTool,
|
||||||
]
|
]
|
||||||
|
|
||||||
export function Board() {
|
export function Board() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,756 @@
|
||||||
|
import {
|
||||||
|
BaseBoxShapeUtil,
|
||||||
|
HTMLContainer,
|
||||||
|
TLBaseShape,
|
||||||
|
} from "tldraw"
|
||||||
|
import React, { useState, useCallback } from "react"
|
||||||
|
import HoloSphere from "holosphere"
|
||||||
|
|
||||||
|
// Initialize HoloSphere
|
||||||
|
const holosphere = new HoloSphere('holons')
|
||||||
|
|
||||||
|
type IHolon = TLBaseShape<
|
||||||
|
"Holon",
|
||||||
|
{
|
||||||
|
w: number
|
||||||
|
h: number
|
||||||
|
holonId: string | null
|
||||||
|
showAdvancedMenu?: boolean
|
||||||
|
loadedContent?: {
|
||||||
|
tasks?: any[] | { error: string }
|
||||||
|
lists?: any[] | { error: string }
|
||||||
|
users?: any[] | { error: string }
|
||||||
|
events?: any[] | { error: string }
|
||||||
|
proposals?: any[] | { error: string }
|
||||||
|
offers?: any[] | { error: string }
|
||||||
|
requests?: any[] | { error: string }
|
||||||
|
balance?: any | { error: string }
|
||||||
|
quests?: any[] | { error: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
export class HolonShape extends BaseBoxShapeUtil<IHolon> {
|
||||||
|
static override type = "Holon" as const
|
||||||
|
|
||||||
|
getDefaultProps(): IHolon["props"] {
|
||||||
|
return {
|
||||||
|
w: 400,
|
||||||
|
h: 300,
|
||||||
|
holonId: null,
|
||||||
|
showAdvancedMenu: false,
|
||||||
|
loadedContent: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component(shape: IHolon) {
|
||||||
|
const isSelected = this.editor.getSelectedShapeIds().includes(shape.id)
|
||||||
|
|
||||||
|
const [inputHolonId, setInputHolonId] = useState(shape.props.holonId || "")
|
||||||
|
const [error, setError] = useState("")
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(
|
||||||
|
(e: React.FormEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!inputHolonId.trim()) {
|
||||||
|
setError("Please enter a Holon ID")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic validation - Holon IDs are typically numbers
|
||||||
|
const isValidHolonId = /^-?\d+$/.test(inputHolonId.trim())
|
||||||
|
if (!isValidHolonId) {
|
||||||
|
setError("Invalid Holon ID format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.updateShape<IHolon>({
|
||||||
|
id: shape.id,
|
||||||
|
type: "Holon",
|
||||||
|
props: { ...shape.props, holonId: inputHolonId.trim() },
|
||||||
|
})
|
||||||
|
setError("")
|
||||||
|
},
|
||||||
|
[inputHolonId],
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggleAdvancedMenu = useCallback(() => {
|
||||||
|
// Debounce to prevent rapid sync updates
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
this.editor.updateShape<IHolon>({
|
||||||
|
id: shape.id,
|
||||||
|
type: "Holon",
|
||||||
|
props: {
|
||||||
|
...shape.props,
|
||||||
|
showAdvancedMenu: !shape.props.showAdvancedMenu
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (syncError) {
|
||||||
|
console.error(`❌ Toggle menu failed (sync error):`, syncError)
|
||||||
|
}
|
||||||
|
}, 100) // 100ms debounce
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId)
|
||||||
|
}, [shape.props.showAdvancedMenu])
|
||||||
|
|
||||||
|
const loadContent = useCallback(async (contentType: string) => {
|
||||||
|
if (!shape.props.holonId) return
|
||||||
|
|
||||||
|
console.log(`🔄 Starting to load ${contentType} for Holon ${shape.props.holonId}`)
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`📡 Calling holosphere.getAll('${shape.props.holonId}','${contentType}')`)
|
||||||
|
|
||||||
|
// Wrap HoloSphere API call in a timeout to prevent blocking sync
|
||||||
|
const data = await Promise.race([
|
||||||
|
holosphere.getAll(`'${shape.props.holonId}'`,`'${contentType}'`),
|
||||||
|
new Promise((_, reject) =>
|
||||||
|
setTimeout(() => reject(new Error('HoloSphere API timeout')), 10000)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
|
console.log(`🔍 Data(users):`, await holosphere.getAll('-1002848305066','status'))
|
||||||
|
console.log(`🔍 Data(tasks):`, await holosphere.getAll('-1002848305066','tasks'))
|
||||||
|
|
||||||
|
console.log(`✅ Received data for ${contentType}:`, data)
|
||||||
|
console.log(`📊 Data type: ${typeof data}, isArray: ${Array.isArray(data)}`)
|
||||||
|
console.log(`📏 Data length: ${Array.isArray(data) ? data.length : 'N/A'}`)
|
||||||
|
|
||||||
|
// Test with different Holon ID to see if it's a data issue
|
||||||
|
console.log(`🧪 Testing with different Holon ID:`, await holosphere.getAll('-1002848305066', 'status'))
|
||||||
|
|
||||||
|
// Test with different content type to see if it's a content type issue
|
||||||
|
console.log(`🧪 Testing with different content type:`, await holosphere.getAll(shape.props.holonId, 'tasks'))
|
||||||
|
|
||||||
|
// Log the HoloSphere instance to see its configuration
|
||||||
|
console.log(`🔧 HoloSphere instance:`, holosphere)
|
||||||
|
console.log(`🔧 HoloSphere methods:`, Object.getOwnPropertyNames(Object.getPrototypeOf(holosphere)))
|
||||||
|
|
||||||
|
// Wrap shape update in try-catch to prevent sync interference
|
||||||
|
try {
|
||||||
|
this.editor.updateShape<IHolon>({
|
||||||
|
id: shape.id,
|
||||||
|
type: "Holon",
|
||||||
|
props: {
|
||||||
|
...shape.props,
|
||||||
|
loadedContent: {
|
||||||
|
...shape.props.loadedContent,
|
||||||
|
[contentType]: data
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
console.log(`💾 Updated shape with ${contentType} data`)
|
||||||
|
} catch (syncError) {
|
||||||
|
console.error(`❌ Shape update failed (sync error):`, syncError)
|
||||||
|
// Don't re-throw sync errors to prevent blocking
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Failed to load ${contentType} from Holon ${shape.props.holonId}:`, error)
|
||||||
|
console.error(`🔍 Error details:`, {
|
||||||
|
message: (error as Error).message,
|
||||||
|
stack: (error as Error).stack,
|
||||||
|
name: (error as Error).name
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wrap error state update in try-catch
|
||||||
|
try {
|
||||||
|
this.editor.updateShape<IHolon>({
|
||||||
|
id: shape.id,
|
||||||
|
type: "Holon",
|
||||||
|
props: {
|
||||||
|
...shape.props,
|
||||||
|
loadedContent: {
|
||||||
|
...shape.props.loadedContent,
|
||||||
|
[contentType]: { error: `Failed to load ${contentType}: ${(error as Error).message}` }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (syncError) {
|
||||||
|
console.error(`❌ Error state update failed (sync error):`, syncError)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
console.log(`🏁 Finished loading ${contentType}`)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}, [shape.props.holonId, shape.props.loadedContent])
|
||||||
|
|
||||||
|
const contentStyle = {
|
||||||
|
pointerEvents: isSelected ? "none" as const : "all" as const,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
border: "1px solid #D3D3D3",
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
overflow: "hidden",
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperStyle = {
|
||||||
|
position: 'relative' as const,
|
||||||
|
width: `${shape.props.w}px`,
|
||||||
|
height: `${shape.props.h}px`,
|
||||||
|
backgroundColor: "#F0F0F0",
|
||||||
|
borderRadius: "4px",
|
||||||
|
overflow: "hidden",
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonStyle = {
|
||||||
|
border: "none",
|
||||||
|
background: "#007bff",
|
||||||
|
color: "white",
|
||||||
|
padding: "6px 12px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "11px",
|
||||||
|
margin: "2px",
|
||||||
|
pointerEvents: "all" as const,
|
||||||
|
whiteSpace: "nowrap" as const,
|
||||||
|
transition: "background-color 0.2s",
|
||||||
|
}
|
||||||
|
|
||||||
|
const secondaryButtonStyle = {
|
||||||
|
...buttonStyle,
|
||||||
|
background: "#6c757d",
|
||||||
|
fontSize: "10px",
|
||||||
|
padding: "4px 8px",
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleButtonStyle = {
|
||||||
|
...buttonStyle,
|
||||||
|
background: "#28a745",
|
||||||
|
fontSize: "10px",
|
||||||
|
padding: "4px 8px",
|
||||||
|
}
|
||||||
|
|
||||||
|
// For empty state (no holonId set)
|
||||||
|
if (!shape.props.holonId) {
|
||||||
|
return (
|
||||||
|
<HTMLContainer>
|
||||||
|
<div style={wrapperStyle}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...contentStyle,
|
||||||
|
cursor: 'text',
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const input = e.currentTarget.querySelector('input')
|
||||||
|
input?.focus()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
padding: "10px",
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: "center", marginBottom: "10px" }}>
|
||||||
|
<h3 style={{ margin: "0 0 8px 0", color: "#495057", fontSize: "16px" }}>
|
||||||
|
🌟 Holon Task Manager
|
||||||
|
</h3>
|
||||||
|
<p style={{ margin: "0", color: "#6c757d", fontSize: "12px" }}>
|
||||||
|
Enter a Holon ID to get started
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={inputHolonId}
|
||||||
|
onChange={(e) => setInputHolonId(e.target.value)}
|
||||||
|
placeholder="Enter Holon ID (e.g., -4962820663)"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "15px",
|
||||||
|
border: "1px solid #ccc",
|
||||||
|
borderRadius: "4px",
|
||||||
|
fontSize: "16px",
|
||||||
|
touchAction: 'none',
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
handleSubmit(e)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
e.currentTarget.focus()
|
||||||
|
}}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
{error && (
|
||||||
|
<div style={{ color: "red", marginTop: "10px", fontSize: "12px" }}>{error}</div>
|
||||||
|
)}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HTMLContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For loaded state (holonId is set)
|
||||||
|
return (
|
||||||
|
<HTMLContainer>
|
||||||
|
<div style={wrapperStyle}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...contentStyle,
|
||||||
|
flexDirection: "column",
|
||||||
|
padding: "12px",
|
||||||
|
alignItems: "stretch",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Header */}
|
||||||
|
<div style={{ marginBottom: "12px", textAlign: "center" }}>
|
||||||
|
<h3 style={{ margin: "0 0 4px 0", color: "#495057", fontSize: "14px" }}>
|
||||||
|
🌟 Holon Task Manager
|
||||||
|
</h3>
|
||||||
|
<p style={{ margin: "0", color: "#6c757d", fontSize: "10px" }}>
|
||||||
|
ID: {shape.props.holonId}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Basic Menu */}
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontSize: "10px", fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>
|
||||||
|
Quick Actions:
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap", gap: "2px" }}>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("tasks")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={buttonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
📋 Tasks
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("lists")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={buttonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
📝 Lists
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("status")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={buttonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
👥 Users
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("events")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={buttonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
📅 Events
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Advanced Menu Toggle */}
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<button
|
||||||
|
onClick={toggleAdvancedMenu}
|
||||||
|
style={toggleButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
{shape.props.showAdvancedMenu ? "🔽 Hide Advanced" : "🔼 Show Advanced"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Advanced Menu */}
|
||||||
|
{shape.props.showAdvancedMenu && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontSize: "10px", fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>
|
||||||
|
Advanced Features:
|
||||||
|
</div>
|
||||||
|
<div style={{ display: "flex", flexWrap: "wrap", gap: "2px" }}>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("proposals")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={secondaryButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
📋 Proposals
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("offers")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={secondaryButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
🎁 Offers
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("requests")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={secondaryButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
🙏 Requests
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("balance")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={secondaryButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
💰 Balance
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => loadContent("quests")}
|
||||||
|
disabled={isLoading}
|
||||||
|
style={secondaryButtonStyle}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
🎯 Quests
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Loading Indicator */}
|
||||||
|
{isLoading && (
|
||||||
|
<div style={{ textAlign: "center", padding: "8px", color: "#6c757d", fontSize: "10px" }}>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Content Display */}
|
||||||
|
<div style={{ flex: 1, overflow: "auto", fontSize: "10px" }}>
|
||||||
|
{shape.props.loadedContent && Object.keys(shape.props.loadedContent).length > 0 && (
|
||||||
|
<div>
|
||||||
|
{shape.props.loadedContent.tasks && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>📋 Tasks:</div>
|
||||||
|
{'error' in shape.props.loadedContent.tasks ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.tasks.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.tasks) ? (
|
||||||
|
shape.props.loadedContent.tasks.map((task: any) => (
|
||||||
|
<div key={task.id || task._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: task.completed ? "#d4edda" : "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
{task.completed ? "✅" : "⭕"} {task.text || task.title} {task.assigned && `(${task.assigned})`}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No tasks found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.lists && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>📝 Lists:</div>
|
||||||
|
{'error' in shape.props.loadedContent.lists ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.lists.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.lists) ? (
|
||||||
|
shape.props.loadedContent.lists.map((list: any) => (
|
||||||
|
<div key={list.id || list._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
📋 {list.name || list.title}: {list.items ? list.items.join(", ") : "No items"}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No lists found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.users && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>👥 Users:</div>
|
||||||
|
{'error' in shape.props.loadedContent.users ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.users.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.users) ? (
|
||||||
|
shape.props.loadedContent.users.map((user: any) => (
|
||||||
|
<div key={user.id || user._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
👤 {user.name || user.username} ({user.role || "Member"}) - {user.status || "Active"}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No users found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.events && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>📅 Events:</div>
|
||||||
|
{'error' in shape.props.loadedContent.events ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.events.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.events) ? (
|
||||||
|
shape.props.loadedContent.events.map((event: any) => (
|
||||||
|
<div key={event.id || event._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
📅 {event.title || event.name} - {event.date || event.startDate} {event.time && event.time}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No events found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.proposals && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>📋 Proposals:</div>
|
||||||
|
{'error' in shape.props.loadedContent.proposals ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.proposals.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.proposals) ? (
|
||||||
|
shape.props.loadedContent.proposals.map((proposal: any) => (
|
||||||
|
<div key={proposal.id || proposal._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
📋 {proposal.title || proposal.name} - {proposal.status || "Active"} {proposal.votes && `(${proposal.votes} votes)`}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No proposals found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.offers && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>🎁 Offers:</div>
|
||||||
|
{'error' in shape.props.loadedContent.offers ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.offers.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.offers) ? (
|
||||||
|
shape.props.loadedContent.offers.map((offer: any) => (
|
||||||
|
<div key={offer.id || offer._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
🎁 {offer.title || offer.name} by {offer.offered_by || offer.user}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No offers found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.requests && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>🙏 Requests:</div>
|
||||||
|
{'error' in shape.props.loadedContent.requests ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.requests.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.requests) ? (
|
||||||
|
shape.props.loadedContent.requests.map((request: any) => (
|
||||||
|
<div key={request.id || request._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
🙏 {request.title || request.name} by {request.requested_by || request.user}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No requests found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.balance && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>💰 Balance:</div>
|
||||||
|
{'error' in shape.props.loadedContent.balance ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.balance.error}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
💰 {shape.props.loadedContent.balance.total || shape.props.loadedContent.balance.amount} {shape.props.loadedContent.balance.currency || "EUR"} {shape.props.loadedContent.balance.transactions && `(${shape.props.loadedContent.balance.transactions} transactions)`}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shape.props.loadedContent.quests && (
|
||||||
|
<div style={{ marginBottom: "8px" }}>
|
||||||
|
<div style={{ fontWeight: "bold", marginBottom: "4px", color: "#495057" }}>🎯 Quests:</div>
|
||||||
|
{'error' in shape.props.loadedContent.quests ? (
|
||||||
|
<div style={{ color: "#dc3545", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
❌ {shape.props.loadedContent.quests.error}
|
||||||
|
</div>
|
||||||
|
) : Array.isArray(shape.props.loadedContent.quests) ? (
|
||||||
|
shape.props.loadedContent.quests.map((quest: any) => (
|
||||||
|
<div key={quest.id || quest._id} style={{
|
||||||
|
padding: "2px 4px",
|
||||||
|
backgroundColor: "#f8f9fa",
|
||||||
|
marginBottom: "2px",
|
||||||
|
borderRadius: "2px",
|
||||||
|
fontSize: "9px"
|
||||||
|
}}>
|
||||||
|
🎯 {quest.title || quest.name} - {quest.description} - {quest.status || "Active"}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div style={{ color: "#6c757d", fontSize: "9px", padding: "2px 4px" }}>
|
||||||
|
No quests found
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* External Link */}
|
||||||
|
<div style={{ textAlign: "center", marginTop: "8px" }}>
|
||||||
|
<a
|
||||||
|
href={`https://dashboard.holons.io/${shape.props.holonId}/`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{
|
||||||
|
color: "#1976d2",
|
||||||
|
textDecoration: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "10px",
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onPointerUp={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
Open in Holons.io →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HTMLContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override indicator(shape: IHolon) {
|
||||||
|
return (
|
||||||
|
<rect
|
||||||
|
width={shape.props.w}
|
||||||
|
height={shape.props.h}
|
||||||
|
fill="none"
|
||||||
|
stroke="dashed"
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray={8}
|
||||||
|
strokeDashoffset={4}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle sync for Holon shape updates
|
||||||
|
onBeforeUpdate = (_prev: IHolon, next: IHolon) => {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle creation with proper sync
|
||||||
|
onBeforeCreate = (shape: IHolon) => {
|
||||||
|
return shape
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointer down for input focus
|
||||||
|
onPointerDown = (shape: IHolon) => {
|
||||||
|
if (!shape.props.holonId) {
|
||||||
|
const input = document.querySelector('input[placeholder*="Holon ID"]') as HTMLInputElement
|
||||||
|
input?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle double click for interaction
|
||||||
|
override onDoubleClick = (shape: IHolon) => {
|
||||||
|
// If no holonId is set, focus the input field
|
||||||
|
if (!shape.props.holonId) {
|
||||||
|
const input = document.querySelector('input[placeholder*="Holon ID"]') as HTMLInputElement
|
||||||
|
input?.focus()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For existing holons, open in new tab
|
||||||
|
window.open(`https://dashboard.holons.io/${shape.props.holonId}/`, '_blank', 'noopener,noreferrer')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { BaseBoxShapeTool } from 'tldraw'
|
||||||
|
|
||||||
|
export class HolonShapeTool extends BaseBoxShapeTool {
|
||||||
|
static override id = 'Holon'
|
||||||
|
static override initial = 'idle'
|
||||||
|
override shapeType = 'Holon'
|
||||||
|
}
|
||||||
|
|
@ -111,6 +111,7 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
||||||
<TldrawUiMenuItem {...tools.Markdown} disabled={hasSelection} />
|
<TldrawUiMenuItem {...tools.Markdown} disabled={hasSelection} />
|
||||||
<TldrawUiMenuItem {...tools.MycrozineTemplate} disabled={hasSelection} />
|
<TldrawUiMenuItem {...tools.MycrozineTemplate} disabled={hasSelection} />
|
||||||
<TldrawUiMenuItem {...tools.Prompt} disabled={hasSelection} />
|
<TldrawUiMenuItem {...tools.Prompt} disabled={hasSelection} />
|
||||||
|
<TldrawUiMenuItem {...tools.Holon} disabled={hasSelection} />
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -168,6 +168,14 @@ export function CustomToolbar() {
|
||||||
isSelected={tools["Prompt"].id === editor.getCurrentToolId()}
|
isSelected={tools["Prompt"].id === editor.getCurrentToolId()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{tools["Holon"] && (
|
||||||
|
<TldrawUiMenuItem
|
||||||
|
{...tools["Holon"]}
|
||||||
|
icon="star"
|
||||||
|
label="Holon"
|
||||||
|
isSelected={tools["Holon"].id === editor.getCurrentToolId()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DefaultToolbar>
|
</DefaultToolbar>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,15 @@ export const overrides: TLUiOverrides = {
|
||||||
readonlyOk: true,
|
readonlyOk: true,
|
||||||
onSelect: () => editor.setCurrentTool("Prompt"),
|
onSelect: () => editor.setCurrentTool("Prompt"),
|
||||||
},
|
},
|
||||||
|
Holon: {
|
||||||
|
id: "Holon",
|
||||||
|
icon: "star",
|
||||||
|
label: "Holon",
|
||||||
|
type: "Holon",
|
||||||
|
kbd: "alt+h",
|
||||||
|
readonlyOk: true,
|
||||||
|
onSelect: () => editor.setCurrentTool("Holon"),
|
||||||
|
},
|
||||||
hand: {
|
hand: {
|
||||||
...tools.hand,
|
...tools.hand,
|
||||||
onDoubleClick: (info: any) => {
|
onDoubleClick: (info: any) => {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import { MarkdownShape } from "@/shapes/MarkdownShapeUtil"
|
||||||
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
|
import { MycrozineTemplateShape } from "@/shapes/MycrozineTemplateShapeUtil"
|
||||||
import { SlideShape } from "@/shapes/SlideShapeUtil"
|
import { SlideShape } from "@/shapes/SlideShapeUtil"
|
||||||
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
import { PromptShape } from "@/shapes/PromptShapeUtil"
|
||||||
|
import { HolonShape } from "@/shapes/HolonShapeUtil"
|
||||||
|
|
||||||
// add custom shapes and bindings here if needed:
|
// add custom shapes and bindings here if needed:
|
||||||
export const customSchema = createTLSchema({
|
export const customSchema = createTLSchema({
|
||||||
|
|
@ -52,6 +53,10 @@ export const customSchema = createTLSchema({
|
||||||
props: PromptShape.props,
|
props: PromptShape.props,
|
||||||
migrations: PromptShape.migrations,
|
migrations: PromptShape.migrations,
|
||||||
},
|
},
|
||||||
|
Holon: {
|
||||||
|
props: HolonShape.props,
|
||||||
|
migrations: HolonShape.migrations,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
bindings: defaultBindingSchemas,
|
bindings: defaultBindingSchemas,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue