File size: 8,491 Bytes
3d7d9b5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import { Plus, FolderOpen, Layout, Layers, Settings, Trash2, Minus, Square, X } from 'lucide-react';
import { useAppStore } from '../store';
import { useRef, useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { getCurrentWindow } from '@tauri-apps/api/window';

const appWindow = getCurrentWindow();

interface ProjectEntry { id: string; title: string; element_count: number; saved_at: number; }

export const StarterHub = () => {
  const { setCurrentScreen, setIsSettingsOpen, setImages, setTextNotes, setAnnotations, setPalettes, setZoom, setPan, setActiveProjectId, setBoardTitle } = useAppStore();
  const [projects, setProjects] = useState<ProjectEntry[]>([]);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const loadProjects = () => invoke<ProjectEntry[]>('projects_list').then(setProjects).catch(() => {});
  useEffect(() => { loadProjects(); }, []);

  const handleOpenFile = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = (ev) => {
      try {
        const d = JSON.parse(ev.target?.result as string);
        if (d.images) setImages(d.images);
        if (d.textNotes) setTextNotes(d.textNotes);
        if (d.annotations) setAnnotations(d.annotations);
        if (d.palettes) setPalettes(d.palettes);
        if (d.zoom) setZoom(d.zoom);
        if (d.pan) setPan(d.pan);
        if (d.title) setBoardTitle(d.title);
        setCurrentScreen('board');
      } catch {}
    };
    reader.readAsText(file);
    if (e.target) e.target.value = '';
  };

  const handleNewBoard = async () => {
    try {
      const entry = await invoke<any>('project_create', { title: null });
      setActiveProjectId(entry.id);
      setBoardTitle(entry.title);
    } catch {}
    setImages([]); setTextNotes([]); setAnnotations([]); setPalettes([]);
    setZoom(1); setPan({ x: 0, y: 0 });
    setCurrentScreen('board');
  };

  const handleOpenProject = async (project: ProjectEntry) => {
    try {
      const json = await invoke<string>('project_load', { id: project.id });
      const d = JSON.parse(json);
      setImages(d.images || []); setTextNotes(d.textNotes || []); setAnnotations(d.annotations || []); setPalettes(d.palettes || []);
      setZoom(d.zoom || 1); setPan(d.pan || { x: 0, y: 0 });
      setBoardTitle(d.title || project.title);
      setActiveProjectId(project.id);
    } catch {
      setImages([]); setTextNotes([]); setAnnotations([]); setPalettes([]); setZoom(1); setPan({ x: 0, y: 0 });
      setBoardTitle(project.title); setActiveProjectId(project.id);
    }
    setCurrentScreen('board');
  };

  const handleDeleteProject = async (id: string, e: React.MouseEvent) => {
    e.stopPropagation();
    try { await invoke('project_delete', { id }); } catch {}
    loadProjects();
  };

  const timeAgo = (ts: number) => {
    const diff = Math.floor(Date.now() / 1000) - ts;
    if (diff < 60) return 'Just now';
    if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
    if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`;
    if (diff < 604800) return `${Math.floor(diff / 86400)}d ago`;
    return new Date(ts * 1000).toLocaleDateString();
  };

  return (
    <div className="w-screen h-screen bg-[#0A0A0B] flex flex-col font-sans text-white select-none overflow-hidden">

      {/* Top bar */}

      <div className="w-full h-12 flex items-center justify-between px-5 bg-[#0A0A0B] shrink-0 z-10 border-b border-white/[0.04]" data-tauri-drag-region>

        {/* Left: Logo */}

        <div className="flex items-center gap-2.5">

          <div className="w-[22px] h-[22px] bg-white rounded-[5px] flex items-center justify-center text-black">

            <Layout size={11} />

          </div>

          <span className="font-semibold text-[12px] text-white/80 tracking-tight">Refstudio</span>

        </div>



        {/* Right: Settings + Window controls */}

        <div className="flex items-center gap-1">

          <button onClick={() => setIsSettingsOpen(true)} className="w-7 h-7 rounded-md flex items-center justify-center text-white/30 hover:text-white/70 hover:bg-white/5 transition-colors" title="Settings">

            <Settings size={14} />

          </button>

          <div className="w-px h-4 bg-white/[0.06] mx-1" />

          <button onClick={() => appWindow.minimize()} className="w-7 h-7 rounded-md flex items-center justify-center text-white/30 hover:text-white/70 hover:bg-white/5 transition-colors">

            <Minus size={14} />

          </button>

          <button onClick={() => appWindow.toggleMaximize()} className="w-7 h-7 rounded-md flex items-center justify-center text-white/30 hover:text-white/70 hover:bg-white/5 transition-colors">

            <Square size={11} />

          </button>

          <button onClick={() => appWindow.close()} className="w-7 h-7 rounded-md flex items-center justify-center text-white/30 hover:text-[#FF453A] hover:bg-[#FF453A]/10 transition-colors">

            <X size={14} />

          </button>

        </div>

      </div>



      {/* Content */}

      <div className="flex-1 flex flex-col items-center pt-14 pb-12 px-8 overflow-y-auto">

        {/* Action buttons */}

        <div className="flex gap-3 mb-10 w-full max-w-md">

          <button onClick={handleNewBoard} className="flex-1 h-[72px] rounded-xl bg-[#141415] border border-white/[0.06] hover:border-[#0A84FF]/30 flex items-center gap-3 px-5 transition-all group">

            <div className="w-8 h-8 rounded-full bg-white/[0.04] group-hover:bg-[#0A84FF] flex items-center justify-center text-white/50 group-hover:text-white transition-colors shrink-0">

              <Plus size={15} />

            </div>

            <span className="text-[13px] text-white/70 font-medium">New Board</span>

          </button>

          <button onClick={() => fileInputRef.current?.click()} className="flex-1 h-[72px] rounded-xl bg-[#141415] border border-white/[0.06] hover:border-white/15 flex items-center gap-3 px-5 transition-all group">

            <div className="w-8 h-8 rounded-full bg-white/[0.04] group-hover:bg-white group-hover:text-black flex items-center justify-center text-white/50 transition-colors shrink-0">

              <FolderOpen size={15} />

            </div>

            <span className="text-[13px] text-white/70 font-medium">Open File</span>

          </button>

          <input type="file" accept=".json" className="hidden" ref={fileInputRef} onChange={handleOpenFile} />

        </div>



        {/* Recent projects */}

        <div className="w-full max-w-md">

          <div className="flex items-center justify-between mb-3 px-1">

            <span className="text-[11px] font-medium text-white/25 uppercase tracking-wider">Recent</span>

          </div>



          {projects.length === 0 ? (

            <div className="flex flex-col items-center justify-center py-16 text-white/20">

              <Layers size={22} className="mb-2.5 opacity-50" />

              <p className="text-[12px] font-medium">No projects yet</p>

            </div>

          ) : (

            <div className="flex flex-col gap-1">

              {projects.map(p => (

                <button key={p.id} onClick={() => handleOpenProject(p)} className="group w-full flex items-center gap-3 px-3.5 py-3 rounded-lg hover:bg-white/[0.03] transition-colors text-left">

                  <div className="w-8 h-8 rounded-lg bg-white/[0.04] flex items-center justify-center text-white/20 shrink-0">

                    <Layers size={13} />

                  </div>

                  <div className="flex-1 min-w-0">

                    <div className="text-[13px] font-medium text-white/80 truncate">{p.title}</div>

                    <div className="text-[10px] text-white/25 mt-0.5">{p.element_count} elements · {timeAgo(p.saved_at)}</div>

                  </div>

                  <button onClick={(e) => handleDeleteProject(p.id, e)} className="w-6 h-6 rounded flex items-center justify-center text-white/10 hover:text-[#FF453A] hover:bg-[#FF453A]/10 opacity-0 group-hover:opacity-100 transition-all shrink-0">

                    <Trash2 size={12} />

                  </button>

                </button>

              ))}

            </div>

          )}

        </div>

      </div>

    </div>
  );
};