diff --git a/.gitignore b/.gitignore index 8b5e6ce..b328fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,8 @@ -# node_modules/ -# dist/ -# .env -# .env.local -# .DS_Store -# *.log -# coverage/ -# .next/ -# -------------------------- -# +node_modules/ +dist/ +.env +.env.local +.DS_Store +*.log +coverage/ +.next/ diff --git a/Affirmations.tsx b/Affirmations.tsx deleted file mode 100644 index 2863142..0000000 --- a/Affirmations.tsx +++ /dev/null @@ -1,45 +0,0 @@ - -import React, { useState, useEffect } from 'react'; - -const MESSAGES = [ - "You are a masterpiece.", - "Bring your magic.", - "Imagine without fear.", - "Write your story.", - "It's all within you.", - "Reinvent yourself.", - "No limits.", - "Become you.", - "You are enough.", - "Dream big." -]; - -const Affirmations: React.FC = () => { - const [index, setIndex] = useState(0); - const [fade, setFade] = useState(true); - - useEffect(() => { - const interval = setInterval(() => { - setFade(false); - setTimeout(() => { - setIndex((prev) => (prev + 1) % MESSAGES.length); - setFade(true); - }, 1000); - }, 15000); - return () => clearInterval(interval); - }, []); - - return ( -
-
-

- {MESSAGES[index]} -

-
-
- ); -}; - -export default Affirmations; diff --git a/App.tsx b/App.tsx index a2fbe3b..beb1704 100644 --- a/App.tsx +++ b/App.tsx @@ -1,377 +1,267 @@ -import React, { - useState, - useEffect, - useCallback, - useMemo, - useRef, -} from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { - IndicatorStyle, - WindowState, - Workspace, - UserPreferences, - TabData, - ColorTheme, - FocusTimerState, + Pin, TodoItem, Priority, - SoundscapeType, + UserPreferences, + FocusTimerState, } from "./types"; -import StatusBar from "./StatusBar"; -import Desktop from "./Desktop"; -import Onboarding from "./Onboarding"; +import Sidebar from "./Sidebar"; +import TopBar from "./TopBar"; +import PinCard from "./PinCard"; +import PinDetail from "./PinDetail"; +import CommandPalette from "./CommandPalette"; import PreferencesModal from "./PreferencesModal"; -import WorkspaceManager from "./WorkspaceManager"; -import SmartAssistant from "./SmartAssistant"; -import GhostSearch from "./GhostSearch"; -import EchoAssistant from "./EchoAssistant"; +import Onboarding from "./Onboarding"; import { GoogleGenAI, Type } from "@google/genai"; import { soundscapeEngine } from "./soundscapeEngine"; - -const THEME_ACCENTS: Record = { - nebula: "#3b82f6", - sunrise: "#f97316", - ocean: "#06b6d4", - emerald: "#10b981", -}; +import { Plus } from "lucide-react"; const DEFAULT_PREFERENCES: UserPreferences = { - launchAtLogin: false, - showMenuBarIcon: true, - indicatorStyle: IndicatorStyle.COLORED_GLOW, - accentColor: "#3b82f6", + accentColor: "#6366f1", theme: "dark", - minOpacity: 0.4, - zenMode: false, - ghostingEnabled: true, - colorTheme: "nebula", soundscape: "none", - snapZonesEnabled: true, - shortcuts: { - pin: "p", - transparency: "t", - focus: "f", - workspace: "w", - zen: "z", - search: "k", - }, }; -const INITIAL_WINDOWS: WindowState[] = [ +const INITIAL_PINS: Pin[] = [ { - id: "w1", + id: "p1", title: "Research Project", - type: "browser", - x: 350, - y: 100, - width: 600, - height: 450, + type: "tabs", + content: "Deep dive into the architecture of modern LLMs.", + category: "AI Research", isPinned: true, - opacity: 1, + isArchived: false, focusCount: 8, - content: "Deep dive into the architecture of modern LLMs.", + createdAt: Date.now(), tabs: [ { id: "t1", - title: "Gemini Technical Paper", - url: "deepmind.google/gemini", + title: "Example Domain", + url: "https://example.com", isActive: true, - category: "AI Research", - lastAccessed: Date.now(), }, { id: "t2", - title: "React Performance Tips", - url: "react.dev/learn", + title: "First Website Ever", + url: "https://info.cern.ch", isActive: false, - category: "Dev Docs", - lastAccessed: Date.now() - 10000, }, { id: "t3", - title: "Window Management UX", - url: "nngroup.com", + title: "W3C", + url: "https://www.w3.org", isActive: false, - category: "Design", - lastAccessed: Date.now() - 20000, }, ], }, { - id: "w2", + id: "p2", title: "Focus Space", - type: "notes", - x: 800, - y: 400, - width: 300, - height: 300, - isPinned: false, - opacity: 1, - focusCount: 3, + type: "note", content: "Goal: Finish the multi-tab architecture by end of day.", - }, - { - id: "w3", - title: "Creative Canvas", - type: "canvas", - x: 100, - y: 180, - width: 320, - height: 380, isPinned: false, - opacity: 1, - isClosed: true, - focusCount: 1, - content: "Generative mood board.", - canvasData: { - prompt: - "A futuristic digital workspace with floating holographic screens in Nebula colors", - imageUrl: "", - isGenerating: false, - }, + isArchived: false, + focusCount: 3, + createdAt: Date.now(), }, ]; +type View = "today" | "pins" | "vault"; + const App: React.FC = () => { const [isOnboarding, setIsOnboarding] = useState(true); const [preferences, setPreferences] = useState(DEFAULT_PREFERENCES); - const [windows, setWindows] = useState(INITIAL_WINDOWS); + const [pins, setPins] = useState(INITIAL_PINS); const [todos, setTodos] = useState([]); - const [archivedTabs, setArchivedTabs] = useState([]); - const [activeWindowId, setActiveWindowId] = useState(null); - const [isPreferencesOpen, setIsPreferencesOpen] = useState(false); - const [isWorkspaceManagerOpen, setIsWorkspaceManagerOpen] = useState(false); - const [isAssistantOpen, setIsAssistantOpen] = useState(false); - const [isEchoOpen, setIsEchoOpen] = useState(false); + const [view, setView] = useState("today"); + const [openPinId, setOpenPinId] = useState(null); const [isSearchOpen, setIsSearchOpen] = useState(false); - const [workspaces, setWorkspaces] = useState([]); - const [isMobile, setIsMobile] = useState(false); - const [globalWallpaper, setGlobalWallpaper] = useState(null); + const [isPrefsOpen, setIsPrefsOpen] = useState(false); + const [isStacking, setIsStacking] = useState(false); const [focusTimer, setFocusTimer] = useState({ isActive: false, timeLeft: 25 * 60, + duration: 25 * 60, mode: "focus", }); - const [soundscapeVolume, setSoundscapeVolume] = useState(0.5); - - // Soundscape audio controller useEffect(() => { - if (preferences.soundscape && preferences.soundscape !== "none") { + if (preferences.soundscape !== "none") { soundscapeEngine.start(preferences.soundscape); } else { soundscapeEngine.stop(); } - return () => { - soundscapeEngine.stop(); - }; + return () => soundscapeEngine.stop(); }, [preferences.soundscape]); - // Sync volume changes useEffect(() => { - soundscapeEngine.setVolume(soundscapeVolume); - }, [soundscapeVolume]); - - // Timer Tick - useEffect(() => { - let interval: any; - if (focusTimer.isActive && focusTimer.timeLeft > 0) { - interval = setInterval(() => { - setFocusTimer((prev) => ({ ...prev, timeLeft: prev.timeLeft - 1 })); - }, 1000); - } else if (focusTimer.timeLeft === 0) { - setFocusTimer((prev) => ({ ...prev, isActive: false })); - } - return () => clearInterval(interval); + if (!focusTimer.isActive || focusTimer.timeLeft <= 0) return; + const id = setInterval(() => { + setFocusTimer((prev) => + prev.timeLeft <= 1 + ? { ...prev, timeLeft: 0, isActive: false } + : { ...prev, timeLeft: prev.timeLeft - 1 }, + ); + }, 1000); + return () => clearInterval(id); }, [focusTimer.isActive, focusTimer.timeLeft]); - // Handle Responsiveness useEffect(() => { - const checkMobile = () => setIsMobile(window.innerWidth < 768); - checkMobile(); - window.addEventListener("resize", checkMobile); - return () => window.removeEventListener("resize", checkMobile); + const handler = (e: KeyboardEvent) => { + if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { + e.preventDefault(); + setIsSearchOpen(true); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); }, []); const toggleFocusTimer = () => setFocusTimer((prev) => ({ ...prev, isActive: !prev.isActive })); - const updateWindowPosition = useCallback( - (id: string, x: number, y: number) => { - if (isMobile) return; - setWindows((prev) => - prev.map((w) => { - if (w.id !== id) return w; - if (w.preSnapDimensions) { - const pre = w.preSnapDimensions; - return { - ...w, - x: x + (w.width - pre.width) / 2, - y, - width: pre.width, - height: pre.height, - preSnapDimensions: undefined, - }; - } - return { ...w, x, y }; - }), - ); - }, - [isMobile], - ); - - const handleSnapWindow = useCallback( - (id: string, snapType: "left" | "right" | "top" | "bottom" | "none") => { - if (isMobile) return; - setWindows((prev) => - prev.map((w) => { - if (w.id !== id) return w; + const togglePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isPinned: !p.isPinned } : p)), + ); + }, []); - const width = window.innerWidth; - const height = window.innerHeight; - const statusBarHeight = 32; // pt-8 is 32px - const desktopHeight = height - statusBarHeight; + const archivePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isArchived: true } : p)), + ); + }, []); - if (snapType === "none") return w; + const restorePin = useCallback((id: string) => { + setPins((prev) => + prev.map((p) => (p.id === id ? { ...p, isArchived: false } : p)), + ); + }, []); - const preSnapDimensions = w.preSnapDimensions || { - x: w.x, - y: w.y, - width: w.width, - height: w.height, - }; + const openPin = useCallback((id: string) => { + setOpenPinId(id); + setPins((prev) => + prev.map((p) => + p.id === id ? { ...p, focusCount: p.focusCount + 1 } : p, + ), + ); + }, []); - if (snapType === "left") { - return { - ...w, - x: 0, - y: statusBarHeight, - width: width / 2, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "right") { - return { - ...w, - x: width / 2, - y: statusBarHeight, - width: width / 2, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "top") { - return { - ...w, - x: 0, - y: statusBarHeight, - width: width, - height: desktopHeight, - isPinned: true, - preSnapDimensions, - }; - } else if (snapType === "bottom") { - return { - ...w, - x: 0, - y: statusBarHeight + desktopHeight / 2, - width: width, - height: desktopHeight / 2, - isPinned: true, - preSnapDimensions, - }; - } - return w; - }), - ); - }, - [isMobile], - ); + const updatePinContent = useCallback((id: string, content: string) => { + setPins((prev) => prev.map((p) => (p.id === id ? { ...p, content } : p))); + }, []); - const togglePin = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isPinned: !w.isPinned } : w)), + const switchTab = useCallback((pinId: string, tabId: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + return { + ...p, + tabs: p.tabs.map((t) => ({ ...t, isActive: t.id === tabId })), + }; + }), ); }, []); - const handleGenerateCanvas = useCallback( - async (id: string, prompt: string) => { - setWindows((prev) => - prev.map((w) => - w.id === id - ? { - ...w, - canvasData: { ...w.canvasData, isGenerating: true, prompt }, - } - : w, - ), - ); + const navigateTab = useCallback((pinId: string, tabId: string, url: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + return { + ...p, + tabs: p.tabs.map((t) => + t.id === tabId + ? { ...t, url, title: t.title || url, lastAccessed: Date.now() } + : t, + ), + }; + }), + ); + }, []); - try { - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const response = await ai.models.generateContent({ - model: "gemini-2.5-flash-image", - contents: { parts: [{ text: prompt }] }, - config: { imageConfig: { aspectRatio: "1:1" } }, - }); + const addTab = useCallback((pinId: string, url: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId) return p; + const newTab = { + id: `tab-${Date.now()}`, + title: url.replace(/^https?:\/\//, ""), + url, + isActive: true, + lastAccessed: Date.now(), + }; + return { + ...p, + tabs: [...(p.tabs || []).map((t) => ({ ...t, isActive: false })), newTab], + }; + }), + ); + }, []); - let imageUrl = ""; - for (const part of response.candidates?.[0]?.content?.parts || []) { - if (part.inlineData) { - imageUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; - break; - } + const closeTab = useCallback((pinId: string, tabId: string) => { + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; + const filtered = p.tabs.filter((t) => t.id !== tabId); + if (filtered.length > 0 && !filtered.some((t) => t.isActive)) { + filtered[0].isActive = true; } + return { ...p, tabs: filtered }; + }), + ); + }, []); - setWindows((prev) => - prev.map((w) => - w.id === id - ? { - ...w, - canvasData: { - ...w.canvasData, - isGenerating: false, - imageUrl, - prompt, - }, - } - : w, - ), - ); - } catch (e) { - console.error("Canvas Generation Failed:", e); - setWindows((prev) => - prev.map((w) => - w.id === id - ? { ...w, canvasData: { ...w.canvasData, isGenerating: false } } - : w, - ), - ); - } - }, - [], - ); + const addIntention = useCallback((text: string, priority: Priority = "medium") => { + setTodos((prev) => [ + { id: `t-${Date.now()}`, text, priority, completed: false, createdAt: Date.now() }, + ...prev, + ]); + }, []); - const handleApplyWallpaper = useCallback((imageUrl: string) => { - setGlobalWallpaper(imageUrl); + const addNotePin = useCallback(() => { + const newPin: Pin = { + id: `p-${Date.now()}`, + title: "New Note", + type: "note", + content: "", + isPinned: false, + isArchived: false, + focusCount: 0, + createdAt: Date.now(), + }; + setPins((prev) => [newPin, ...prev]); + setOpenPinId(newPin.id); }, []); - const handleSmartStack = useCallback( - async (windowId: string) => { - const targetWindow = windows.find((w) => w.id === windowId); - if (!targetWindow || !targetWindow.tabs || targetWindow.tabs.length < 2) - return; + const addBrowserPin = useCallback(() => { + const newPin: Pin = { + id: `p-${Date.now()}`, + title: "New Tab Group", + type: "tabs", + content: "", + isPinned: false, + isArchived: false, + focusCount: 0, + createdAt: Date.now(), + tabs: [ + { id: `tab-${Date.now()}`, title: "New Tab", url: "", isActive: true }, + ], + }; + setPins((prev) => [newPin, ...prev]); + setOpenPinId(newPin.id); + }, []); + const handleSmartStack = useCallback( + async (pinId: string) => { + const pin = pins.find((p) => p.id === pinId); + if (!pin?.tabs || pin.tabs.length < 2) return; + setIsStacking(true); try { const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const tabData = targetWindow.tabs.map((t) => ({ - id: t.id, - title: t.title, - })); - + const tabData = pin.tabs.map((t) => ({ id: t.id, title: t.title })); const response = await ai.models.generateContent({ model: "gemini-3-flash-preview", contents: `Categorize these browser tabs into logical groups. Return a JSON array of objects with "id" and "category" (max 2 words). Tabs: ${JSON.stringify(tabData)}`, @@ -390,15 +280,13 @@ const App: React.FC = () => { }, }, }); - const categories = JSON.parse(response.text || "[]"); - - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; + setPins((prev) => + prev.map((p) => { + if (p.id !== pinId || !p.tabs) return p; return { - ...w, - tabs: w.tabs.map((t) => { + ...p, + tabs: p.tabs.map((t) => { const cat = categories.find((c: any) => c.id === t.id); return cat ? { ...t, category: cat.category } : t; }), @@ -406,406 +294,150 @@ const App: React.FC = () => { }), ); } catch (e) { - console.error("Smart Stack Failed:", e); - } - }, - [windows], - ); - - const handleSwitchTab = useCallback((windowId: string, tabId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - return { - ...w, - tabs: w.tabs.map((t) => ({ - ...t, - isActive: t.id === tabId, - lastAccessed: t.id === tabId ? Date.now() : t.lastAccessed, - })), - }; - }), - ); - }, []); - - const handleUpdateTabCategory = useCallback( - (windowId: string, tabId: string, category: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - return { - ...w, - tabs: w.tabs.map((t) => (t.id === tabId ? { ...t, category } : t)), - }; - }), - ); - }, - [], - ); - - const handleAddTab = useCallback((windowId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId) return w; - const newTab: TabData = { - id: `tab-${Date.now()}`, - title: "New Surface", - url: "pinpoint.internal/new", - isActive: true, - lastAccessed: Date.now(), - }; - return { - ...w, - tabs: [ - ...(w.tabs || []).map((t) => ({ ...t, isActive: false })), - newTab, - ], - }; - }), - ); - }, []); - - const handleCloseTab = useCallback((windowId: string, tabId: string) => { - setWindows((prev) => - prev.map((w) => { - if (w.id !== windowId || !w.tabs) return w; - const tabToArchive = w.tabs.find((t) => t.id === tabId); - if (tabToArchive) { - setArchivedTabs((prevArchive) => [ - tabToArchive, - ...prevArchive.slice(0, 19), - ]); - } - const filteredTabs = w.tabs.filter((t) => t.id !== tabId); - if (filteredTabs.length > 0 && !filteredTabs.some((t) => t.isActive)) { - filteredTabs[0].isActive = true; - } - return { ...w, tabs: filteredTabs }; - }), - ); - }, []); - - const handleCloseWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isClosed: true } : w)), - ); - }, []); - - const handleRestoreWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, isClosed: false } : w)), - ); - }, []); - - const handleToggleCollapseWindow = useCallback((id: string) => { - setWindows((prev) => - prev.map((w) => - w.id === id ? { ...w, isCollapsed: !w.isCollapsed } : w, - ), - ); - }, []); - - const handleSetActiveWindowId = useCallback((id: string | null) => { - setActiveWindowId(id); - if (id) { - setWindows((prev) => - prev.map((w) => - w.id === id ? { ...w, focusCount: (w.focusCount || 0) + 1 } : w, - ), - ); - } - }, []); - - const handleDesktopTidy = useCallback(() => { - setWindows((prev) => { - const visibleWindows = prev.filter((w) => !w.isClosed); - if (visibleWindows.length === 0) return prev; - - // Sort by focusCount (usage frequency) descending - const sorted = [...visibleWindows].sort((a, b) => { - const countA = a.focusCount || 0; - const countB = b.focusCount || 0; - if (countB !== countA) return countB - countA; - return a.id.localeCompare(b.id); - }); - - const screenWidth = window.innerWidth || 1200; - const screenHeight = window.innerHeight || 800; - - const centerWidth = Math.max(500, Math.min(680, screenWidth * 0.55)); - const centerHeight = Math.max(380, Math.min(500, screenHeight * 0.6)); - const centerX = (screenWidth - centerWidth) / 2; - const centerY = (screenHeight - centerHeight) / 2 + 10; - - const mostActive = sorted[0]; - const others = sorted.slice(1); - - return prev.map((w) => { - if (w.isClosed) return w; - - if (w.id === mostActive.id) { - return { - ...w, - x: centerX, - y: centerY, - width: centerWidth, - height: centerHeight, - isPinned: true, - isCollapsed: false, - }; - } - - const index = others.findIndex((o) => o.id === w.id); - if (index === -1) return w; - - const isLeft = index % 2 === 0; - const sideIndex = Math.floor(index / 2); - const totalOnSide = Math.ceil(others.length / 2); - - const sideWidth = Math.max( - 280, - Math.min(320, (screenWidth - centerWidth) / 2 - 30), - ); - const sideX = isLeft - ? Math.max(20, (centerX - sideWidth) / 2) - : Math.min( - screenWidth - sideWidth - 20, - centerX + - centerWidth + - (screenWidth - (centerX + centerWidth) - sideWidth) / 2, - ); - - const availableHeight = screenHeight - 120; - const sideHeight = Math.min(260, availableHeight / totalOnSide - 20); - const startY = 70; - const sideY = startY + sideIndex * (sideHeight + 15); - - return { - ...w, - x: sideX, - y: sideY, - width: sideWidth, - height: sideHeight, - isPinned: false, - isCollapsed: false, - }; - }); - }); - - setWindows((prev) => { - const visibleWindows = prev.filter((w) => !w.isClosed); - if (visibleWindows.length > 0) { - const sorted = [...visibleWindows].sort((a, b) => { - const countA = a.focusCount || 0; - const countB = b.focusCount || 0; - if (countB !== countA) return countB - countA; - return a.id.localeCompare(b.id); - }); - setActiveWindowId(sorted[0].id); + console.error("Smart Stack failed:", e); + } finally { + setIsStacking(false); } - return prev; - }); - }, []); - - const handleRestoreTab = useCallback( - (tab: TabData) => { - // Restore to currently active window or first browser window - setWindows((prev) => { - let targetId = - activeWindowId || prev.find((w) => w.type === "browser")?.id; - if (!targetId) return prev; - - return prev.map((w) => { - if (w.id !== targetId) return w; - return { - ...w, - tabs: [ - ...(w.tabs || []).map((t) => ({ ...t, isActive: false })), - { ...tab, isActive: true, lastAccessed: Date.now() }, - ], - }; - }); - }); - setArchivedTabs((prev) => prev.filter((t) => t.id !== tab.id)); - }, - [activeWindowId], - ); - - const addIntention = useCallback( - (text: string, priority: Priority = "medium") => { - setTodos((prev) => [ - { - id: `t-${Date.now()}`, - text, - priority, - completed: false, - createdAt: Date.now(), - }, - ...prev, - ]); }, - [], + [pins], ); if (isOnboarding) { - return ( - setIsOnboarding(false)} - preferences={preferences} - setPreferences={setPreferences} - /> - ); + return setIsOnboarding(false)} />; } + const visiblePins = + view === "vault" + ? pins.filter((p) => p.isArchived) + : view === "pins" + ? pins.filter((p) => !p.isArchived) + : pins.filter((p) => !p.isArchived && p.isPinned); + + const openPinObj = pins.find((p) => p.id === openPinId) || null; + return (
- w.isPinned).length} - focusMode={preferences.zenMode} - currentTheme={preferences.colorTheme} - theme={preferences.theme} - onToggleTheme={() => - setPreferences((p) => ({ - ...p, - theme: p.theme === "dark" ? "light" : "dark", - })) - } - accentColor={preferences.accentColor} - onAccentChange={(color) => - setPreferences((p) => ({ ...p, accentColor: color })) - } - focusTimer={focusTimer} - archivedTabs={archivedTabs} - onRestoreTab={handleRestoreTab} - onToggleFocusTimer={toggleFocusTimer} - onThemeChange={(theme) => - setPreferences((p) => ({ ...p, colorTheme: theme })) - } - onOpenPrefs={() => setIsPreferencesOpen(true)} - onOpenWorkspaces={() => setIsWorkspaceManagerOpen(true)} - onOpenAssistant={() => setIsAssistantOpen(true)} - onOpenSearch={() => setIsSearchOpen(true)} - onOpenEcho={() => setIsEchoOpen(true)} - isMobile={isMobile} - soundscape={preferences.soundscape} - onSoundscapeChange={(sound) => - setPreferences((p) => ({ ...p, soundscape: sound })) - } - soundscapeVolume={soundscapeVolume} - onSoundscapeVolumeChange={(vol) => setSoundscapeVolume(vol)} - closedWindows={windows.filter((w) => w.isClosed)} - onRestoreWindow={handleRestoreWindow} - /> - - - setWindows((prev) => - prev.map((w) => (w.id === id ? { ...w, opacity } : w)), - ) - } - onDetachTab={handleCloseTab} - onSwitchTab={handleSwitchTab} - onUpdateTabCategory={handleUpdateTabCategory} - onAddTab={handleAddTab} - onCloseTab={handleCloseTab} - onGenerateGlance={() => {}} - onGenerateCanvas={handleGenerateCanvas} - onApplyWallpaper={handleApplyWallpaper} - onSmartStack={handleSmartStack} + onAddTodo={addIntention} onToggleTodo={(id) => setTodos((prev) => - prev.map((t) => - t.id === id ? { ...t, completed: !t.completed } : t, - ), + prev.map((t) => (t.id === id ? { ...t, completed: !t.completed } : t)), ) } - onAddTodo={addIntention} - onDeleteTodo={(id) => - setTodos((prev) => prev.filter((t) => t.id !== id)) - } - onGroupWindows={() => {}} - onRemoveFromFolder={() => {}} - isMobile={isMobile} - globalWallpaper={globalWallpaper} - onCloseWindow={handleCloseWindow} - onSnapWindow={handleSnapWindow} - onToggleCollapseWindow={handleToggleCollapseWindow} + onDeleteTodo={(id) => setTodos((prev) => prev.filter((t) => t.id !== id))} /> - {isEchoOpen && ( - setIsEchoOpen(false)} - commands={{ - setTimer: (mins) => - setFocusTimer({ - mode: "focus", - timeLeft: mins * 60, - isActive: true, - }), - setTheme: (theme) => - setPreferences((p) => ({ - ...p, - colorTheme: theme as ColorTheme, - })), - pinWindow: (id) => togglePin(id), - addIntention: (text) => addIntention(text), - toggleZen: () => - setPreferences((p) => ({ ...p, zenMode: !p.zenMode })), - desktopTidy: handleDesktopTidy, - }} +
+ + setPreferences((p) => ({ + ...p, + theme: p.theme === "dark" ? "light" : "dark", + })) + } + onOpenSearch={() => setIsSearchOpen(true)} + onOpenPrefs={() => setIsPrefsOpen(true)} + focusTimer={focusTimer} + onToggleFocusTimer={toggleFocusTimer} /> - )} - {isAssistantOpen && ( - {}} - onCategorize={() => {}} - onSyncTabs={() => {}} - onCleanDesktop={handleDesktopTidy} - onClose={() => setIsAssistantOpen(false)} - isMobile={isMobile} +
+
+

+ {view === "today" ? "Pinned for today" : view} +

+ {view !== "vault" && ( +
+ + +
+ )} +
+ + {visiblePins.length === 0 ? ( +

+ {view === "vault" ? "Vault is empty." : "Nothing here yet."} +

+ ) : ( +
+ {visiblePins.map((pin) => + view === "vault" ? ( +
+

{pin.title}

+ +
+ ) : ( + + ), + )} +
+ )} +
+
+ + {openPinObj && ( + setOpenPinId(null)} + onUpdateContent={updatePinContent} + onSmartStack={handleSmartStack} + isStacking={isStacking} + onSwitchTab={switchTab} + onAddTab={addTab} + onCloseTab={closeTab} + onNavigateTab={navigateTab} /> )} - {isSearchOpen && ( - { - handleSetActiveWindowId(id); + openPin(id); setIsSearchOpen(false); }} onClose={() => setIsSearchOpen(false)} /> )} - {isPreferencesOpen && ( + {isPrefsOpen && ( setIsPreferencesOpen(false)} - isMobile={isMobile} - /> - )} - {isWorkspaceManagerOpen && ( - {}} - onRestore={() => {}} - onClose={() => setIsWorkspaceManagerOpen(false)} + onClose={() => setIsPrefsOpen(false)} /> )}
diff --git a/ColorStylePicker.tsx b/ColorStylePicker.tsx deleted file mode 100644 index f20cd09..0000000 --- a/ColorStylePicker.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from "react"; -import { ACCENT_PRESETS } from "./types"; -import { Palette } from "lucide-react"; - -interface Props { - accentColor: string; - onChange: (color: string) => void; - isMobile?: boolean; -} - -const ColorStylePicker: React.FC = ({ - accentColor, - onChange, - isMobile, -}) => { - const [isOpen, setIsOpen] = useState(false); - - return ( -
- - - {isOpen && ( -
-
-

- Accent Color -

- -
- -
- {ACCENT_PRESETS.map((preset) => ( - - ))} -
- - onChange(e.target.value)} - className="mt-3 w-full h-8 rounded-lg bg-transparent border border-white/10 cursor-pointer" - title="Custom color" - /> -
- )} -
- ); -}; - -export default ColorStylePicker; diff --git a/CommandPalette.tsx b/CommandPalette.tsx new file mode 100644 index 0000000..8f0e4a8 --- /dev/null +++ b/CommandPalette.tsx @@ -0,0 +1,97 @@ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { Pin } from "./types"; +import { Search, Link2, StickyNote, Layers } from "lucide-react"; + +interface Props { + pins: Pin[]; + onSelect: (id: string) => void; + onClose: () => void; +} + +const TYPE_ICON: Record> = { + link: Link2, + note: StickyNote, + tabs: Layers, +}; + +const CommandPalette: React.FC = ({ pins, onSelect, onClose }) => { + const [query, setQuery] = useState(""); + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.focus(); + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [onClose]); + + const results = useMemo(() => { + const q = query.trim().toLowerCase(); + const visible = pins.filter((p) => !p.isArchived); + if (!q) return visible.slice(0, 8); + return visible.filter( + (p) => + p.title.toLowerCase().includes(q) || + p.content.toLowerCase().includes(q) || + p.category?.toLowerCase().includes(q) || + p.tabs?.some((t) => t.title.toLowerCase().includes(q)), + ); + }, [pins, query]); + + return ( +
+
e.stopPropagation()} + className="w-full max-w-lg bg-surface border border-app rounded-xl shadow-xl overflow-hidden" + > +
+ + setQuery(e.target.value)} + placeholder="Search pins..." + className="flex-1 bg-transparent outline-none text-sm placeholder:text-app-tertiary" + /> + + esc + +
+
+ {results.length === 0 && ( +

+ No results +

+ )} + {results.map((p) => { + const Icon = TYPE_ICON[p.type]; + return ( + + ); + })} +
+
+
+ ); +}; + +export default CommandPalette; diff --git a/Darkmodetoggle.tsx b/Darkmodetoggle.tsx deleted file mode 100644 index 926ba29..0000000 --- a/Darkmodetoggle.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useDarkMode, DarkModeToggle } from "./useDarkMode"; - -function Navbar() { - const { isDark, toggle } = useDarkMode(); - return ; -} diff --git a/Desktop.tsx b/Desktop.tsx deleted file mode 100644 index 599bfb0..0000000 --- a/Desktop.tsx +++ /dev/null @@ -1,249 +0,0 @@ - -import React, { useMemo, useState } from 'react'; -import { WindowState, UserPreferences, ColorTheme, TodoItem, Priority } from './types'; -import Window from './Window'; -import Affirmations from './Affirmations'; -import FocusTaskBoard from './FocusTaskBoard'; -import DesktopFolder from './DesktopFolder'; - -interface Props { - windows: WindowState[]; - todos: TodoItem[]; - activeWindowId: string | null; - focusMode: boolean; - preferences: UserPreferences; - focusTimerActive: boolean; - setActiveWindowId: (id: string | null) => void; - updateWindowPosition: (id: string, x: number, y: number) => void; - togglePin: (id: string) => void; - updateWindowOpacity: (id: string, opacity: number) => void; - onDetachTab: (windowId: string, tabId: string) => void; - onSwitchTab?: (windowId: string, tabId: string) => void; - onUpdateTabCategory?: (windowId: string, tabId: string, category: string) => void; - onAddTab?: (windowId: string) => void; - onCloseTab?: (windowId: string, tabId: string) => void; - onGenerateGlance: (id: string) => void; - onGenerateCanvas?: (id: string, prompt: string) => void; - onApplyWallpaper?: (imageUrl: string) => void; - onSmartStack?: (id: string) => void; - onToggleTodo: (id: string) => void; - onAddTodo: (text: string, priority: Priority) => void; - onDeleteTodo: (id: string) => void; - onGroupWindows: (sourceId: string, targetId: string) => void; - onRemoveFromFolder: (id: string) => void; - isMobile: boolean; - globalWallpaper: string | null; - onCloseWindow?: (id: string) => void; - onSnapWindow?: (id: string, snapType: 'left' | 'right' | 'top' | 'bottom' | 'none') => void; - onToggleCollapseWindow?: (id: string) => void; -} - -const Desktop: React.FC = ({ - windows, - todos, - activeWindowId, - focusMode, - preferences, - focusTimerActive, - setActiveWindowId, - updateWindowPosition, - togglePin, - updateWindowOpacity, - onDetachTab, - onSwitchTab, - onUpdateTabCategory, - onAddTab, - onCloseTab, - onGenerateGlance, - onGenerateCanvas, - onApplyWallpaper, - onSmartStack, - onToggleTodo, - onAddTodo, - onDeleteTodo, - onGroupWindows, - onRemoveFromFolder, - isMobile, - globalWallpaper, - onCloseWindow, - onSnapWindow, - onToggleCollapseWindow -}) => { - const [draggedWindowId, setDraggedWindowId] = useState(null); - - const desktopItems = useMemo(() => { - return windows.filter(w => !w.parentId && !w.isClosed); - }, [windows]); - - const activeDraggedWindow = useMemo(() => { - return windows.find(w => w.id === draggedWindowId); - }, [windows, draggedWindowId]); - - const activeSnapZone = useMemo(() => { - if (!activeDraggedWindow || isMobile || preferences.snapZonesEnabled === false) return 'none'; - - const threshold = 60; - const width = window.innerWidth; - const height = window.innerHeight; - - if (activeDraggedWindow.x < threshold) { - return 'left'; - } - if (activeDraggedWindow.x + activeDraggedWindow.width > width - threshold) { - return 'right'; - } - if (activeDraggedWindow.y < threshold + 32) { - return 'top'; - } - if (activeDraggedWindow.y + activeDraggedWindow.height > height - threshold) { - return 'bottom'; - } - return 'none'; - }, [activeDraggedWindow, isMobile]); - - const handleDragEnd = (id: string) => { - if (activeSnapZone !== 'none' && onSnapWindow) { - onSnapWindow(id, activeSnapZone); - } - setDraggedWindowId(null); - }; - - const sortedItems = [...desktopItems].sort((a, b) => { - if (a.isPinned && !b.isPinned) return 1; - if (!a.isPinned && b.isPinned) return -1; - if (a.id === activeWindowId) return 1; - if (b.id === activeWindowId) return -1; - return 0; - }); - - const themeColors = useMemo(() => { - const t = preferences.colorTheme; - const colors: Record = { - nebula: ['#3b82f6', '#9333ea', '#6366f1', '#ec4899'], - sunrise: ['#f97316', '#ef4444', '#f59e0b', '#db2777'], - ocean: ['#06b6d4', '#0891b2', '#10b981', '#3b82f6'], - emerald: ['#10b981', '#059669', '#84cc16', '#06b6d4'], - }; - return colors[t]; - }, [preferences.colorTheme]); - - return ( -
setActiveWindowId(null)} - style={{ - '--accent-1': themeColors[0], - '--accent-2': themeColors[1], - '--accent-3': themeColors[2], - } as React.CSSProperties} - > - {/* Dynamic Background Layer */} - {globalWallpaper ? ( -
- ) : ( - <> -
-
- - )} - - {!isMobile && } - -
- {!focusMode && ( -
- -
- )} - -
- {sortedItems.map(item => ( - item.type === 'folder' ? ( - w.parentId === item.id)} - isActive={activeWindowId === item.id} - onActivate={() => setActiveWindowId(item.id)} - onMove={updateWindowPosition} - onOpenChild={(id) => { - onRemoveFromFolder(id); - setActiveWindowId(id); - }} - /> - ) : ( - setActiveWindowId(item.id)} - onMove={(x, y) => updateWindowPosition(item.id, x, y)} - onTogglePin={(timer) => togglePin(item.id)} - onUpdateOpacity={(o) => updateWindowOpacity(item.id, o)} - onDetachTab={(tabId) => onDetachTab(item.id, tabId)} - onSwitchTab={(tabId) => onSwitchTab?.(item.id, tabId)} - onUpdateTabCategory={(tabId, cat) => onUpdateTabCategory?.(item.id, tabId, cat)} - onAddTab={() => onAddTab?.(item.id)} - onCloseTab={(tabId) => onCloseTab?.(item.id, tabId)} - onGenerateGlance={() => onGenerateGlance(item.id)} - onGenerateCanvas={onGenerateCanvas} - onApplyWallpaper={onApplyWallpaper} - onSmartStack={onSmartStack} - onDroppedOn={(targetId) => onGroupWindows(item.id, targetId)} - isMobile={isMobile} - onClose={() => onCloseWindow?.(item.id)} - onToggleCollapse={() => onToggleCollapseWindow?.(item.id)} - onDragStart={() => setDraggedWindowId(item.id)} - onDragEnd={() => handleDragEnd(item.id)} - /> - ) - ))} -
-
- - {/* Snap Zone Visual Indicators */} - {draggedWindowId && activeSnapZone !== 'none' && ( -
-
-
-
- - {activeSnapZone === 'left' ? '⬅️' : activeSnapZone === 'right' ? '➡️' : activeSnapZone === 'top' ? '🔼' : '🔽'} - - - {activeSnapZone === 'top' ? 'Maximize' : `Snap ${activeSnapZone}`} - -
-
-
- )} -
- ); -}; - -export default Desktop; diff --git a/DesktopFolder.tsx b/DesktopFolder.tsx deleted file mode 100644 index 86a108c..0000000 --- a/DesktopFolder.tsx +++ /dev/null @@ -1,114 +0,0 @@ - -import React, { useState, useEffect } from 'react'; -import { WindowState } from './types'; - -interface Props { - folder: WindowState; - childrenWindows: WindowState[]; - isActive: boolean; - onActivate: () => void; - onMove: (id: string, x: number, y: number) => void; - onOpenChild: (id: string) => void; -} - -const DesktopFolder: React.FC = ({ folder, childrenWindows, isActive, onActivate, onMove, onOpenChild }) => { - const [isDragging, setIsDragging] = useState(false); - const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); - const [isExpanded, setIsExpanded] = useState(false); - - const handleMouseDown = (e: React.MouseEvent) => { - e.stopPropagation(); - onActivate(); - setIsDragging(true); - setDragOffset({ - x: e.clientX - folder.x, - y: e.clientY - folder.y, - }); - }; - - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - if (isDragging) onMove(folder.id, e.clientX - dragOffset.x, e.clientY - dragOffset.y); - }; - const handleMouseUp = () => setIsDragging(false); - if (isDragging) { - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - } - return () => { - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - }, [isDragging, dragOffset, onMove, folder.id]); - - return ( -
setIsExpanded(!isExpanded)} - > - {/* Folder Icon Stack Preview */} -
-
-
-
- 📁 -
-
- - - {folder.title} - - -
- {childrenWindows.length} Items -
- - {/* Expanded Grid View */} - {isExpanded && ( -
{ e.stopPropagation(); setIsExpanded(false); }} - > -
e.stopPropagation()} - > - {childrenWindows.map(win => ( -
onOpenChild(win.id)} - className="glass rounded-3xl p-6 aspect-square flex flex-col items-center justify-center space-y-4 hover:bg-white/10 transition-all cursor-pointer group/item hover:scale-105 active:scale-95 shadow-2xl" - > -
- {win.type === 'browser' ? '🌐' : win.type === 'code' ? '💻' : '📄'} -
-
-

{win.title}

-

{win.type}

-
-
- ))} -
- -
- )} -
- ); -}; - -export default DesktopFolder; diff --git a/EchoAssistant.tsx b/EchoAssistant.tsx deleted file mode 100644 index 7a6c8a7..0000000 --- a/EchoAssistant.tsx +++ /dev/null @@ -1,256 +0,0 @@ - -import React, { useState, useEffect, useRef } from 'react'; -import { GoogleGenAI, LiveServerMessage, Modality, Type, FunctionDeclaration } from '@google/genai'; -import { WindowState } from './types'; - -interface Props { - windows: WindowState[]; - onClose: () => void; - commands: { - setTimer: (mins: number) => void; - setTheme: (theme: string) => void; - pinWindow: (id: string) => void; - addIntention: (text: string) => void; - toggleZen: () => void; - desktopTidy: () => void; - }; -} - -const EchoAssistant: React.FC = ({ windows, onClose, commands }) => { - const [isActive, setIsActive] = useState(false); - const [transcription, setTranscription] = useState([]); - const [audioLevel, setAudioLevel] = useState(0); - const [isConnecting, setIsConnecting] = useState(false); - - const audioCtxRef = useRef(null); - const outputCtxRef = useRef(null); - const nextStartTimeRef = useRef(0); - const sourcesRef = useRef>(new Set()); - const sessionRef = useRef(null); - - // Tools for Voice Control - const controlTools: FunctionDeclaration[] = [ - { - name: 'set_focus_timer', - parameters: { - type: Type.OBJECT, - properties: { minutes: { type: Type.NUMBER, description: 'Minutes for the focus timer' } }, - required: ['minutes'], - } - }, - { - name: 'change_theme', - parameters: { - type: Type.OBJECT, - properties: { theme: { type: Type.STRING, description: 'One of: nebula, sunrise, ocean, emerald' } }, - required: ['theme'], - } - }, - { - name: 'pin_window', - parameters: { - type: Type.OBJECT, - properties: { window_id: { type: Type.STRING, description: 'The ID of the window to pin' } }, - required: ['window_id'], - } - }, - { - name: 'add_intention', - parameters: { - type: Type.OBJECT, - properties: { text: { type: Type.STRING, description: 'Description of the new task' } }, - required: ['text'], - } - }, - { - name: 'toggle_zen_mode', - parameters: { type: Type.OBJECT, properties: {} } - }, - { - name: 'desktop_tidy', - parameters: { type: Type.OBJECT, properties: {} } - } - ]; - - const startSession = async () => { - setIsConnecting(true); - // Create a new GoogleGenAI instance right before making an API call to ensure it always uses the most up-to-date API key - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - - audioCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 16000 }); - outputCtxRef.current = new (window.AudioContext || (window as any).webkitAudioContext)({ sampleRate: 24000 }); - - const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); - - const sessionPromise = ai.live.connect({ - model: 'gemini-2.5-flash-native-audio-preview-12-2025', - callbacks: { - onopen: () => { - setIsActive(true); - setIsConnecting(false); - const source = audioCtxRef.current!.createMediaStreamSource(stream); - const processor = audioCtxRef.current!.createScriptProcessor(4096, 1, 1); - - processor.onaudioprocess = (e) => { - const inputData = e.inputBuffer.getChannelData(0); - - // Calculate Audio Level for Visualizer - let sum = 0; - for(let i=0; i session.sendRealtimeInput({ media: pcmBlob })); - }; - - source.connect(processor); - processor.connect(audioCtxRef.current!.destination); - }, - onmessage: async (msg: LiveServerMessage) => { - if (msg.serverContent?.outputTranscription) { - const text = msg.serverContent.outputTranscription.text; - setTranscription(prev => [...prev.slice(-4), `Echo: ${text}`]); - } - - if (msg.toolCall) { - for (const fc of msg.toolCall.functionCalls) { - let result = "ok"; - // Fixed: Added explicit type casting for function arguments to fix TS 'unknown' errors (lines 112-115) - if (fc.name === 'set_focus_timer') commands.setTimer(fc.args.minutes as number); - if (fc.name === 'change_theme') commands.setTheme(fc.args.theme as string); - if (fc.name === 'pin_window') commands.pinWindow(fc.args.window_id as string); - if (fc.name === 'add_intention') commands.addIntention(fc.args.text as string); - if (fc.name === 'toggle_zen_mode') commands.toggleZen(); - if (fc.name === 'desktop_tidy') commands.desktopTidy(); - - sessionPromise.then(s => s.sendToolResponse({ - functionResponses: { id: fc.id, name: fc.name, response: { result } } - })); - } - } - - const audioBase64 = msg.serverContent?.modelTurn?.parts[0]?.inlineData?.data; - if (audioBase64) { - // Use a running timestamp to track the end of the audio playback queue for gapless playback - nextStartTimeRef.current = Math.max(nextStartTimeRef.current, outputCtxRef.current!.currentTime); - const buffer = await decodeAudioData(decode(audioBase64), outputCtxRef.current!, 24000, 1); - const source = outputCtxRef.current!.createBufferSource(); - source.buffer = buffer; - source.connect(outputCtxRef.current!.destination); - source.start(nextStartTimeRef.current); - nextStartTimeRef.current += buffer.duration; - sourcesRef.current.add(source); - } - }, - onerror: (e) => console.error("Echo Error:", e), - onclose: () => { - setIsActive(false); - setIsConnecting(false); - }, - }, - config: { - responseModalities: [Modality.AUDIO], - outputAudioTranscription: {}, - tools: [{ functionDeclarations: controlTools }], - systemInstruction: `You are Echo, the PinPoint Pro workspace controller. - You help users manage their screen using tools. - Current workspace windows: ${windows.map(w => `${w.id}: ${w.title}`).join(', ')}. - Be concise and helpful.` - } - }); - - sessionRef.current = await sessionPromise; - }; - - const stopSession = () => { - sessionRef.current?.close(); - audioCtxRef.current?.close(); - outputCtxRef.current?.close(); - setIsActive(false); - }; - - // Helper Functions - Implementing manual decode/encode as required by guidelines - function decode(b64: string) { - const bin = atob(b64); - const bytes = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i); - return bytes; - } - - async function decodeAudioData(data: Uint8Array, ctx: AudioContext, rate: number, chans: number) { - const data16 = new Int16Array(data.buffer); - const count = data16.length / chans; - const buffer = ctx.createBuffer(chans, count, rate); - for (let c = 0; c < chans; c++) { - const cData = buffer.getChannelData(c); - for (let i = 0; i < count; i++) cData[i] = data16[i * chans + c] / 32768.0; - } - return buffer; - } - - function createBlob(data: Float32Array) { - const int16 = new Int16Array(data.length); - for (let i = 0; i < data.length; i++) int16[i] = data[i] * 32768; - return { data: encode(new Uint8Array(int16.buffer)), mimeType: 'audio/pcm;rate=16000' }; - } - - function encode(bytes: Uint8Array) { - let bin = ''; - for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); - return btoa(bin); - } - - return ( -
-
-
-
- Echo Live - {isActive &&
} -
- -
- - {/* Visualizer */} -
- {[...Array(12)].map((_, i) => ( -
- ))} -
- -
- {transcription.map((t, i) => ( -

{t}

- ))} - {transcription.length === 0 && !isConnecting && ( -

"Echo, pin my research window..."

- )} - {isConnecting &&

Initializing Neural Link...

} -
- - - -

- {isActive ? 'Echo is listening...' : 'Tap to start voice control'} -

-
-
- ); -}; - -export default EchoAssistant; diff --git a/FocusTaskBoard.tsx b/FocusTaskBoard.tsx deleted file mode 100644 index c242a8b..0000000 --- a/FocusTaskBoard.tsx +++ /dev/null @@ -1,104 +0,0 @@ - -import React, { useState } from 'react'; -import { TodoItem, Priority } from './types'; - -interface Props { - todos: TodoItem[]; - onToggleTodo: (id: string) => void; - onAddTodo: (text: string, priority: Priority) => void; - onDeleteTodo: (id: string) => void; - isMobile?: boolean; -} - -const PRIORITY_COLORS: Record = { - critical: '#ef4444', - high: '#f97316', - medium: '#3b82f6', - low: '#64748b', -}; - -const FocusTaskBoard: React.FC = ({ todos, onToggleTodo, onAddTodo, onDeleteTodo, isMobile }) => { - const [newText, setNewText] = useState(''); - const [newPriority, setNewPriority] = useState('medium'); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (newText.trim()) { - onAddTodo(newText, newPriority); - setNewText(''); - } - }; - - const sortedTodos = [...todos].sort((a, b) => { - const weights: Record = { critical: 4, high: 3, medium: 2, low: 1 }; - if (a.completed !== b.completed) return a.completed ? 1 : -1; - return weights[b.priority] - weights[a.priority]; - }); - - return ( -
-
-

Intentions

-
-
- -
- setNewText(e.target.value)} - placeholder="Add intention..." - className="w-full bg-transparent border-b border-white/10 py-2 text-xs focus:outline-none focus:border-blue-500/50 transition-all placeholder:text-slate-600 font-medium" - /> -
- {(['critical', 'high', 'medium', 'low'] as Priority[]).map((p) => ( -
-
- -
- {sortedTodos.map(todo => { - const isCritical = todo.priority === 'critical' && !todo.completed; - - return ( -
onToggleTodo(todo.id)} - className={`relative group cursor-pointer transition-all duration-500 p-2 rounded-lg hover:bg-white/5 ${todo.completed ? 'opacity-20' : 'opacity-100'}`} - > -
-
-
-

- {todo.text} -

-
- -
-
- ); - })} - {sortedTodos.length === 0 && ( -

No tasks active.

- )} -
-
- ); -}; - -export default FocusTaskBoard; diff --git a/GhostSearch.tsx b/GhostSearch.tsx deleted file mode 100644 index 48798f2..0000000 --- a/GhostSearch.tsx +++ /dev/null @@ -1,112 +0,0 @@ - -import React, { useState, useEffect, useRef } from 'react'; -import { WindowState } from './types'; -import { GoogleGenAI } from "@google/genai"; - -interface Props { - windows: WindowState[]; - onSelect: (windowId: string) => void; - onClose: () => void; -} - -const GhostSearch: React.FC = ({ windows, onSelect, onClose }) => { - const [query, setQuery] = useState(''); - const [results, setResults] = useState<{ id: string, title: string, relevance: string }[]>([]); - const [loading, setLoading] = useState(false); - const inputRef = useRef(null); - - useEffect(() => { - inputRef.current?.focus(); - }, []); - - const handleSearch = async (e: React.FormEvent) => { - e.preventDefault(); - if (!query.trim()) return; - - setLoading(true); - try { - // Corrected GoogleGenAI initialization to follow guidelines - const ai = new GoogleGenAI({ apiKey: process.env.API_KEY }); - const context = windows.map(w => `ID: ${w.id}, Title: ${w.title}, Content: ${w.content}`).join('\n---\n'); - - const response = await ai.models.generateContent({ - model: "gemini-3-flash-preview", - contents: `I have several open windows with this content: - ${context} - - The user is searching for: "${query}" - - Analyze which windows are most relevant. Return a valid JSON array of objects with "id" and "relevance" (a 5-word summary of why it's relevant). Only return the JSON.`, - config: { responseMimeType: "application/json" } - }); - - const parsed = JSON.parse(response.text || '[]'); - const resultsWithTitles = parsed.map((res: any) => ({ - ...res, - title: windows.find(w => w.id === res.id)?.title || 'Unknown Window' - })); - setResults(resultsWithTitles); - } catch (e) { - console.error("Search failed:", e); - } finally { - setLoading(false); - } - }; - - return ( -
-
e.stopPropagation()} - > -
-
- 🔍 - setQuery(e.target.value)} - placeholder="Search across your workspace..." - className="flex-1 bg-transparent border-none outline-none text-lg font-medium placeholder:text-slate-600" - /> - {loading &&
} -
- - -
- {results.length > 0 ? ( - results.map(res => ( - - )) - ) : query && !loading ? ( -
- Press Enter to ask AI to find it... -
- ) : ( -
- Ghost Search active -
- )} -
- -
- Powered by Gemini Contextual Engine - ESC to cancel -
-
-
- ); -}; - -export default GhostSearch; diff --git a/Onboarding.tsx b/Onboarding.tsx index 007845a..5b1f692 100644 --- a/Onboarding.tsx +++ b/Onboarding.tsx @@ -1,119 +1,34 @@ - -import React, { useState } from 'react'; -import { IndicatorStyle, UserPreferences } from './types'; -import { Pin } from 'lucide-react'; +import React from "react"; +import { Pin } from "lucide-react"; interface Props { onComplete: () => void; - preferences: UserPreferences; - setPreferences: (p: UserPreferences) => void; } -const Onboarding: React.FC = ({ onComplete, preferences, setPreferences }) => { - const [step, setStep] = useState(1); - - const next = () => setStep(s => s + 1); - - const renderStep = () => { - switch (step) { - case 1: - return ( -
-
- Open Source -
- v1.0.0-beta -
-
- -
-

Welcome to PinPoint Pro

-

Pin anything, stay focused. Elevate your productivity with context-aware window management.

- -
- ); - case 2: - return ( -
-

Quick Setup

-
-
-
-

Use suggested shortcuts

-

Optimized for Apple keyboards and macOS power users.

- -
-
- Pin Surface -
- ⌘ - + - P -
-
-
- Ghost Search -
- ⌘ - + - K -
-
-
-
-
-

Customize my shortcuts

-

Fine-tune keys to match your existing muscle memory.

- -
- ⌥ - ⇧ - ⌃ - Key -
-
-
-
- ); - case 3: - return ( -
-

You're all set!

-

- PinPoint is now your new digital surface. Drag windows into folders to organize, or use the AI Assistant to clean up. -

-
- -

Proudly Open Source • MIT License

-
-
- ); - default: - return null; - } - }; - +const Onboarding: React.FC = ({ onComplete }) => { return ( -
-
-
- {renderStep()} +
+
+
+ +
+ + Open Source · v1.0.0 + +

+ Welcome to PinPoint Pro +

+

+ Pin what matters, set your intentions, and stay focused — without + the clutter. +

+ +
); }; diff --git a/PinCard.tsx b/PinCard.tsx new file mode 100644 index 0000000..25bb04e --- /dev/null +++ b/PinCard.tsx @@ -0,0 +1,96 @@ +import React from "react"; +import { Pin } from "./types"; +import { Link2, StickyNote, Layers, Pin as PinIcon, Archive } from "lucide-react"; + +interface Props { + pin: Pin; + onOpen: (id: string) => void; + onTogglePin: (id: string) => void; + onArchive: (id: string) => void; +} + +const TYPE_ICON: Record> = { + link: Link2, + note: StickyNote, + tabs: Layers, +}; + +const PinCard: React.FC = ({ pin, onOpen, onTogglePin, onArchive }) => { + const Icon = TYPE_ICON[pin.type]; + + return ( +
onOpen(pin.id)} + className="group relative flex flex-col gap-3 p-4 rounded-xl border border-app bg-surface hover:border-app-strong hover:shadow-sm transition-all cursor-pointer" + > +
+
+ +
+
+ + +
+
+ +
+

+ {pin.title} +

+

+ {pin.type === "tabs" + ? `${pin.tabs?.length || 0} tabs` + : pin.content} +

+
+ + {pin.type === "tabs" && pin.tabs && pin.tabs.length > 0 && ( +
+ {pin.tabs.slice(0, 3).map((t) => ( + + {t.title} + + ))} +
+ )} + +
+ {pin.category ? ( + + {pin.category} + + ) : ( + + )} + {pin.focusCount > 0 && ( + + {pin.focusCount} focus{pin.focusCount === 1 ? "" : "es"} + + )} +
+
+ ); +}; + +export default PinCard; diff --git a/PinDetail.tsx b/PinDetail.tsx new file mode 100644 index 0000000..f7f9ecc --- /dev/null +++ b/PinDetail.tsx @@ -0,0 +1,199 @@ +import React, { useState } from "react"; +import { Pin } from "./types"; +import { X, Sparkles, Plus, ArrowLeft, ArrowRight, RotateCw } from "lucide-react"; + +interface Props { + pin: Pin; + onClose: () => void; + onUpdateContent: (id: string, content: string) => void; + onSmartStack: (id: string) => void; + isStacking: boolean; + onSwitchTab: (pinId: string, tabId: string) => void; + onAddTab: (pinId: string, url: string) => void; + onCloseTab: (pinId: string, tabId: string) => void; + onNavigateTab: (pinId: string, tabId: string, url: string) => void; +} + +const isElectron = + typeof window !== "undefined" && !!(window as any).pinpoint?.isElectron; + +const normalizeUrl = (raw: string) => { + const trimmed = raw.trim(); + if (!trimmed) return ""; + if (/^https?:\/\//i.test(trimmed)) return trimmed; + return `https://${trimmed}`; +}; + +const PinDetail: React.FC = ({ + pin, + onClose, + onUpdateContent, + onSmartStack, + isStacking, + onSwitchTab, + onAddTab, + onCloseTab, + onNavigateTab, +}) => { + const activeTab = pin.tabs?.find((t) => t.isActive) || pin.tabs?.[0]; + const [addressValue, setAddressValue] = useState(activeTab?.url || ""); + const [newTabUrl, setNewTabUrl] = useState(""); + const [reloadKey, setReloadKey] = useState(0); + + React.useEffect(() => { + setAddressValue(activeTab?.url || ""); + }, [activeTab?.id, activeTab?.url]); + + const isBrowser = pin.type === "tabs"; + + const submitAddress = (e: React.FormEvent) => { + e.preventDefault(); + if (!activeTab) return; + onNavigateTab(pin.id, activeTab.id, normalizeUrl(addressValue)); + }; + + const submitNewTab = (e: React.FormEvent) => { + e.preventDefault(); + if (!newTabUrl.trim()) return; + onAddTab(pin.id, normalizeUrl(newTabUrl)); + setNewTabUrl(""); + }; + + return ( +
+
e.stopPropagation()} + className={`w-full flex flex-col bg-surface border border-app rounded-2xl shadow-xl overflow-hidden ${ + isBrowser ? "max-w-4xl h-[85vh]" : "max-w-lg max-h-[80vh]" + }`} + > +
+

+ {pin.title} +

+
+ {isBrowser && ( + + )} + +
+
+ + {isBrowser && pin.tabs ? ( + <> +
+ {pin.tabs.map((t) => ( +
onSwitchTab(pin.id, t.id)} + className={`group flex items-center gap-2 px-3 py-2 rounded-t-lg text-xs cursor-pointer max-w-[160px] shrink-0 ${ + t.isActive + ? "bg-surface text-app-primary font-medium" + : "text-app-tertiary hover:bg-app-muted-hover" + }`} + > + {t.title || t.url || "New Tab"} + +
+ ))} +
+ setNewTabUrl(e.target.value)} + placeholder="+ new tab url" + className="text-xs bg-transparent outline-none placeholder:text-app-tertiary w-28 py-2" + /> +
+
+ +
+ + + + setAddressValue(e.target.value)} + placeholder="Enter a URL..." + className="flex-1 text-xs bg-app-muted rounded-md px-2.5 py-1.5 outline-none" + /> + + +
+ {activeTab?.url ? ( + isElectron ? ( + // @ts-ignore -- webview is an Electron-only element + + ) : ( +