diff --git a/README.md b/README.md index e69de29..37b8c15 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,138 @@ +[Image Description] + +# ExcaliDash + +A beautiful, self hosted dashboard and organizer for [Excalidraw](https://github.com/excalidraw/excalidraw) with live collaboration. +![](dashboard.png) + +[Features](#Features) + +[Installation](#Installation) + +[Development](#Development) + +[Credits](#Credits) + +# Features + +## Persistent storage for all your drawings + +![](dashboardLight.png) + +## Real time collaboration + +![](collabDemo.gif) + +## Search your drawings + +![](searchPage.png) + +## Drag and drop drawings into collections + +![](collectionsPage.png) + +## Export/import your drawings and databases for backup + +![](settingsPage.png) + +# Installation + +## Dockerhub (recommended) + +[Install Docker](https://docs.docker.com/desktop/) + +```bash +# Download docker-compose.prod.yml +curl -OL https://raw.githubusercontent.com/ZimengXiong/ExcaliDash/refs/heads/main/docker-compose.prod.yml + +# Pull images +docker compose -f docker-compose.prod.yml pull + +# Run container +docker compose -f docker-compose.prod.yml up -d +``` + +## Docker build + +[Install Docker](https://docs.docker.com/desktop/) + +```bash +# Clone the repository (recommended) +git clone git@github.com:ZimengXiong/ExcaliDash.git + +# or, clone with HTTPS +# git clone https://github.com/ZimengXiong/ExcaliDash.git + +docker compose build +docker compose up -d + +# Access the frontend at localhost:6767 +``` + +# Development + +## Clone the repository + +```bash +# Clone the repository (recommended) +git clone git@github.com:ZimengXiong/ExcaliDash.git + +# or, clone with HTTPS +# git clone https://github.com/ZimengXiong/ExcaliDash.git +``` + +## Frontend + +```bash +cd ExcaliDash/frontend +npm install + +# Copy environment file and customize if needed +cp .env.example .env + +npm run dev +``` + +## Backend + +```bash +cd ExcaliDash/backend +npm install + +# Copy environment file and customize if needed +cp .env.example .env + +# Generate Prisma client and setup database +npx prisma generate +npx prisma db push + +npm run dev +``` + +## Structure + +``` +ExcaliDash/ +├── backend/ # Node.js + Express + Prisma +│ ├── src/ +│ │ └── index.ts # Main server file +│ ├── prisma/ +│ │ ├── schema.prisma # Database schema +│ │ └── dev.db # SQLite database +│ └── package.json +├── frontend/ # React + TypeScript + Vite +│ ├── src/ +│ │ ├── components/ # React components +│ │ ├── pages/ # Page components +│ │ ├── hooks/ # Custom hooks +│ │ └── api/ # API client +│ └── package.json +└── README.md +``` + +# Credits + +- Example designs from: + - https://github.com/Prakash-sa/system-design-ultimatum/tree/main + - https://github.com/kitsteam/excalidraw-examples/tree/main +- [Excalidraw](https://github.com/ZimengXiong/ExcaliDash) diff --git a/collabDemo.gif b/collabDemo.gif new file mode 100644 index 0000000..84b258e Binary files /dev/null and b/collabDemo.gif differ diff --git a/collectionsPage.png b/collectionsPage.png new file mode 100644 index 0000000..0a0ca11 Binary files /dev/null and b/collectionsPage.png differ diff --git a/dashboard.png b/dashboard.png new file mode 100644 index 0000000..b55644d Binary files /dev/null and b/dashboard.png differ diff --git a/dashboardLight.png b/dashboardLight.png new file mode 100644 index 0000000..bd8680e Binary files /dev/null and b/dashboardLight.png differ diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 7a9ff2f..f75c275 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { Sidebar } from './Sidebar'; import type { Collection } from '../types'; @@ -23,10 +23,51 @@ export const Layout: React.FC = ({ onDeleteCollection, onDrop }) => { + const [sidebarWidth, setSidebarWidth] = useState(260); + const [isResizing, setIsResizing] = useState(false); + const sidebarRef = useRef(null); + const startXRef = useRef(0); + const startWidthRef = useRef(0); + + // Handle mouse down on resize handle + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + setIsResizing(true); + startXRef.current = e.clientX; + startWidthRef.current = sidebarWidth; + + const handleMouseMove = (e: MouseEvent) => { + const diff = e.clientX - startXRef.current; + const newWidth = Math.max(200, Math.min(600, startWidthRef.current + diff)); + setSidebarWidth(newWidth); + }; + + const handleMouseUp = () => { + setIsResizing(false); + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + }; + + // Cleanup event listeners on unmount + useEffect(() => { + return () => { + document.removeEventListener('mousemove', () => {}); + document.removeEventListener('mouseup', () => {}); + }; + }, []); + return (
-