@gallop.software/studio 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/layout.tsx +2 -2
- package/app/page.tsx +1 -1
- package/dist/components/StudioUI.d.mts +15 -0
- package/dist/components/StudioUI.d.ts +15 -0
- package/dist/components/StudioUI.js +6572 -0
- package/dist/components/StudioUI.js.map +1 -0
- package/dist/components/StudioUI.mjs +6572 -0
- package/dist/components/StudioUI.mjs.map +1 -0
- package/package.json +6 -2
- package/src/components/AddNewModal.tsx +0 -402
- package/src/components/ErrorModal.tsx +0 -89
- package/src/components/R2SetupModal.tsx +0 -400
- package/src/components/StudioBreadcrumb.tsx +0 -115
- package/src/components/StudioButton.tsx +0 -200
- package/src/components/StudioContext.tsx +0 -219
- package/src/components/StudioDetailView.tsx +0 -714
- package/src/components/StudioFileGrid.tsx +0 -704
- package/src/components/StudioFileList.tsx +0 -743
- package/src/components/StudioFolderPicker.tsx +0 -342
- package/src/components/StudioModal.tsx +0 -473
- package/src/components/StudioPreview.tsx +0 -399
- package/src/components/StudioSettings.tsx +0 -536
- package/src/components/StudioToolbar.tsx +0 -1448
- package/src/components/StudioUI.tsx +0 -731
- package/src/components/styles/common.ts +0 -236
- package/src/components/tokens.ts +0 -78
- package/src/components/useStudioActions.tsx +0 -497
- package/src/config/index.ts +0 -7
- package/src/config/workspace.ts +0 -52
- package/src/handlers/favicon.ts +0 -152
- package/src/handlers/files.ts +0 -784
- package/src/handlers/images.ts +0 -949
- package/src/handlers/import.ts +0 -190
- package/src/handlers/index.ts +0 -168
- package/src/handlers/list.ts +0 -627
- package/src/handlers/scan.ts +0 -311
- package/src/handlers/utils/cdn.ts +0 -234
- package/src/handlers/utils/files.ts +0 -64
- package/src/handlers/utils/index.ts +0 -4
- package/src/handlers/utils/meta.ts +0 -102
- package/src/handlers/utils/thumbnails.ts +0 -98
- package/src/hooks/useFileList.ts +0 -143
- package/src/index.tsx +0 -36
- package/src/lib/api.ts +0 -176
- package/src/types.ts +0 -119
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
/** @jsxImportSource @emotion/react */
|
|
2
|
-
'use client'
|
|
3
|
-
|
|
4
|
-
import { useState, useEffect } from 'react'
|
|
5
|
-
import { css, keyframes } from '@emotion/react'
|
|
6
|
-
import { colors, fontSize, fontStack, baseReset } from './tokens'
|
|
7
|
-
|
|
8
|
-
const fadeIn = keyframes`
|
|
9
|
-
from { opacity: 0; }
|
|
10
|
-
to { opacity: 1; }
|
|
11
|
-
`
|
|
12
|
-
|
|
13
|
-
const slideIn = keyframes`
|
|
14
|
-
from {
|
|
15
|
-
opacity: 0;
|
|
16
|
-
transform: translateY(-8px) scale(0.98);
|
|
17
|
-
}
|
|
18
|
-
to {
|
|
19
|
-
opacity: 1;
|
|
20
|
-
transform: translateY(0) scale(1);
|
|
21
|
-
}
|
|
22
|
-
`
|
|
23
|
-
|
|
24
|
-
interface Folder {
|
|
25
|
-
path: string
|
|
26
|
-
name: string
|
|
27
|
-
depth: number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const styles = {
|
|
31
|
-
overlay: css`
|
|
32
|
-
position: fixed;
|
|
33
|
-
inset: 0;
|
|
34
|
-
background-color: rgba(26, 31, 54, 0.4);
|
|
35
|
-
backdrop-filter: blur(4px);
|
|
36
|
-
display: flex;
|
|
37
|
-
align-items: center;
|
|
38
|
-
justify-content: center;
|
|
39
|
-
z-index: 10000;
|
|
40
|
-
animation: ${fadeIn} 0.15s ease-out;
|
|
41
|
-
font-family: ${fontStack};
|
|
42
|
-
`,
|
|
43
|
-
modal: css`
|
|
44
|
-
${baseReset}
|
|
45
|
-
background-color: ${colors.surface};
|
|
46
|
-
border-radius: 12px;
|
|
47
|
-
box-shadow: 0 30px 60px -12px rgba(50, 50, 93, 0.25), 0 18px 36px -18px rgba(0, 0, 0, 0.3);
|
|
48
|
-
max-width: 480px;
|
|
49
|
-
width: 90%;
|
|
50
|
-
max-height: 80vh;
|
|
51
|
-
display: flex;
|
|
52
|
-
flex-direction: column;
|
|
53
|
-
animation: ${slideIn} 0.2s ease-out;
|
|
54
|
-
overflow: hidden;
|
|
55
|
-
`,
|
|
56
|
-
header: css`
|
|
57
|
-
padding: 24px 24px 0;
|
|
58
|
-
`,
|
|
59
|
-
title: css`
|
|
60
|
-
font-size: ${fontSize.lg};
|
|
61
|
-
font-weight: 600;
|
|
62
|
-
color: ${colors.text};
|
|
63
|
-
margin: 0;
|
|
64
|
-
letter-spacing: -0.02em;
|
|
65
|
-
`,
|
|
66
|
-
body: css`
|
|
67
|
-
padding: 12px 24px 24px;
|
|
68
|
-
flex: 1;
|
|
69
|
-
overflow-y: auto;
|
|
70
|
-
min-height: 200px;
|
|
71
|
-
max-height: 400px;
|
|
72
|
-
`,
|
|
73
|
-
message: css`
|
|
74
|
-
font-size: ${fontSize.base};
|
|
75
|
-
color: ${colors.textSecondary};
|
|
76
|
-
margin: 0 0 16px;
|
|
77
|
-
line-height: 1.6;
|
|
78
|
-
`,
|
|
79
|
-
folderList: css`
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
gap: 0;
|
|
83
|
-
`,
|
|
84
|
-
folderItem: css`
|
|
85
|
-
display: flex;
|
|
86
|
-
align-items: center;
|
|
87
|
-
gap: 8px;
|
|
88
|
-
padding: 8px 12px;
|
|
89
|
-
border-radius: 6px;
|
|
90
|
-
cursor: pointer;
|
|
91
|
-
transition: all 0.15s ease;
|
|
92
|
-
border: 1px solid transparent;
|
|
93
|
-
position: relative;
|
|
94
|
-
|
|
95
|
-
&:hover {
|
|
96
|
-
background-color: ${colors.surfaceHover};
|
|
97
|
-
}
|
|
98
|
-
`,
|
|
99
|
-
treeIndent: css`
|
|
100
|
-
display: flex;
|
|
101
|
-
align-items: center;
|
|
102
|
-
height: 100%;
|
|
103
|
-
`,
|
|
104
|
-
treeLine: css`
|
|
105
|
-
width: 16px;
|
|
106
|
-
height: 100%;
|
|
107
|
-
position: relative;
|
|
108
|
-
flex-shrink: 0;
|
|
109
|
-
|
|
110
|
-
&::before {
|
|
111
|
-
content: '';
|
|
112
|
-
position: absolute;
|
|
113
|
-
left: 8px;
|
|
114
|
-
top: 0;
|
|
115
|
-
bottom: 50%;
|
|
116
|
-
width: 0;
|
|
117
|
-
border-left: 1px dashed ${colors.textSecondary};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
&::after {
|
|
121
|
-
content: '';
|
|
122
|
-
position: absolute;
|
|
123
|
-
left: 8px;
|
|
124
|
-
top: 50%;
|
|
125
|
-
width: 8px;
|
|
126
|
-
height: 0;
|
|
127
|
-
border-top: 1px dashed ${colors.textSecondary};
|
|
128
|
-
}
|
|
129
|
-
`,
|
|
130
|
-
folderItemSelected: css`
|
|
131
|
-
background-color: ${colors.primaryLight};
|
|
132
|
-
border-color: ${colors.primary};
|
|
133
|
-
|
|
134
|
-
&:hover {
|
|
135
|
-
background-color: ${colors.primaryLight};
|
|
136
|
-
}
|
|
137
|
-
`,
|
|
138
|
-
folderItemDisabled: css`
|
|
139
|
-
opacity: 0.5;
|
|
140
|
-
cursor: not-allowed;
|
|
141
|
-
|
|
142
|
-
&:hover {
|
|
143
|
-
background-color: transparent;
|
|
144
|
-
}
|
|
145
|
-
`,
|
|
146
|
-
folderIcon: css`
|
|
147
|
-
width: 20px;
|
|
148
|
-
height: 20px;
|
|
149
|
-
color: #f9935e;
|
|
150
|
-
flex-shrink: 0;
|
|
151
|
-
`,
|
|
152
|
-
folderName: css`
|
|
153
|
-
font-size: ${fontSize.base};
|
|
154
|
-
color: ${colors.text};
|
|
155
|
-
flex: 1;
|
|
156
|
-
`,
|
|
157
|
-
footer: css`
|
|
158
|
-
display: flex;
|
|
159
|
-
justify-content: flex-end;
|
|
160
|
-
gap: 12px;
|
|
161
|
-
padding: 16px 24px;
|
|
162
|
-
border-top: 1px solid ${colors.border};
|
|
163
|
-
background-color: ${colors.background};
|
|
164
|
-
`,
|
|
165
|
-
btn: css`
|
|
166
|
-
padding: 10px 18px;
|
|
167
|
-
font-size: ${fontSize.base};
|
|
168
|
-
font-weight: 500;
|
|
169
|
-
border-radius: 6px;
|
|
170
|
-
cursor: pointer;
|
|
171
|
-
transition: all 0.15s ease;
|
|
172
|
-
letter-spacing: -0.01em;
|
|
173
|
-
`,
|
|
174
|
-
btnCancel: css`
|
|
175
|
-
background-color: ${colors.surface};
|
|
176
|
-
border: 1px solid ${colors.border};
|
|
177
|
-
color: ${colors.text};
|
|
178
|
-
|
|
179
|
-
&:hover {
|
|
180
|
-
background-color: ${colors.surfaceHover};
|
|
181
|
-
border-color: ${colors.borderHover};
|
|
182
|
-
}
|
|
183
|
-
`,
|
|
184
|
-
btnConfirm: css`
|
|
185
|
-
background-color: ${colors.primary};
|
|
186
|
-
border: 1px solid ${colors.primary};
|
|
187
|
-
color: white;
|
|
188
|
-
|
|
189
|
-
&:hover:not(:disabled) {
|
|
190
|
-
background-color: ${colors.primaryHover};
|
|
191
|
-
border-color: ${colors.primaryHover};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
&:disabled {
|
|
195
|
-
opacity: 0.5;
|
|
196
|
-
cursor: not-allowed;
|
|
197
|
-
}
|
|
198
|
-
`,
|
|
199
|
-
loading: css`
|
|
200
|
-
display: flex;
|
|
201
|
-
align-items: center;
|
|
202
|
-
justify-content: center;
|
|
203
|
-
padding: 40px;
|
|
204
|
-
color: ${colors.textSecondary};
|
|
205
|
-
`,
|
|
206
|
-
spinner: css`
|
|
207
|
-
width: 24px;
|
|
208
|
-
height: 24px;
|
|
209
|
-
border-radius: 50%;
|
|
210
|
-
border: 3px solid ${colors.border};
|
|
211
|
-
border-top-color: ${colors.primary};
|
|
212
|
-
animation: spin 0.8s linear infinite;
|
|
213
|
-
|
|
214
|
-
@keyframes spin {
|
|
215
|
-
to { transform: rotate(360deg); }
|
|
216
|
-
}
|
|
217
|
-
`,
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
interface StudioFolderPickerProps {
|
|
221
|
-
selectedItems: Set<string>
|
|
222
|
-
currentPath: string
|
|
223
|
-
onMove: (destination: string) => void
|
|
224
|
-
onCancel: () => void
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export function StudioFolderPicker({ selectedItems, currentPath, onMove, onCancel }: StudioFolderPickerProps) {
|
|
228
|
-
const [folders, setFolders] = useState<Folder[]>([])
|
|
229
|
-
const [loading, setLoading] = useState(true)
|
|
230
|
-
const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
|
|
231
|
-
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
async function loadFolders() {
|
|
234
|
-
try {
|
|
235
|
-
const response = await fetch('/api/studio/list-folders')
|
|
236
|
-
if (response.ok) {
|
|
237
|
-
const data = await response.json()
|
|
238
|
-
console.log('Loaded folders:', data)
|
|
239
|
-
setFolders(data.folders || [])
|
|
240
|
-
} else {
|
|
241
|
-
console.error('Failed to load folders:', response.status, await response.text())
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error('Failed to load folders:', error)
|
|
245
|
-
} finally {
|
|
246
|
-
setLoading(false)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
loadFolders()
|
|
250
|
-
}, [])
|
|
251
|
-
|
|
252
|
-
// Filter out folders that are being moved (can't move to themselves or their children)
|
|
253
|
-
// Mark current folder as disabled but still show it
|
|
254
|
-
const selectedPaths = Array.from(selectedItems)
|
|
255
|
-
const availableFolders = folders.filter(folder => {
|
|
256
|
-
// Can't move a folder into itself or its children
|
|
257
|
-
return !selectedPaths.some(selected =>
|
|
258
|
-
folder.path === selected ||
|
|
259
|
-
folder.path.startsWith(selected + '/')
|
|
260
|
-
)
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
// Check if a folder is the current location (disabled)
|
|
264
|
-
const isCurrentFolder = (folderPath: string) => folderPath === currentPath
|
|
265
|
-
|
|
266
|
-
const handleConfirm = () => {
|
|
267
|
-
if (selectedFolder) {
|
|
268
|
-
onMove(selectedFolder)
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<div css={styles.overlay} onClick={onCancel}>
|
|
274
|
-
<div css={styles.modal} onClick={(e) => e.stopPropagation()}>
|
|
275
|
-
<div css={styles.header}>
|
|
276
|
-
<h3 css={styles.title}>Move Items</h3>
|
|
277
|
-
</div>
|
|
278
|
-
<div css={styles.body}>
|
|
279
|
-
<p css={styles.message}>
|
|
280
|
-
Select a destination folder for {selectedItems.size} item{selectedItems.size !== 1 ? 's' : ''}:
|
|
281
|
-
</p>
|
|
282
|
-
|
|
283
|
-
{loading ? (
|
|
284
|
-
<div css={styles.loading}>
|
|
285
|
-
<div css={styles.spinner} />
|
|
286
|
-
</div>
|
|
287
|
-
) : availableFolders.length === 0 ? (
|
|
288
|
-
<div css={styles.loading}>
|
|
289
|
-
No available folders to move to.
|
|
290
|
-
</div>
|
|
291
|
-
) : (
|
|
292
|
-
<div css={styles.folderList}>
|
|
293
|
-
{availableFolders.map((folder) => {
|
|
294
|
-
const disabled = isCurrentFolder(folder.path)
|
|
295
|
-
return (
|
|
296
|
-
<div
|
|
297
|
-
key={folder.path}
|
|
298
|
-
css={[
|
|
299
|
-
styles.folderItem,
|
|
300
|
-
selectedFolder === folder.path && styles.folderItemSelected,
|
|
301
|
-
disabled && styles.folderItemDisabled
|
|
302
|
-
]}
|
|
303
|
-
style={{ paddingLeft: 12 }}
|
|
304
|
-
onClick={() => !disabled && setSelectedFolder(folder.path)}
|
|
305
|
-
>
|
|
306
|
-
{/* Render tree indent lines */}
|
|
307
|
-
{folder.depth > 0 && (
|
|
308
|
-
<div css={styles.treeIndent}>
|
|
309
|
-
{Array.from({ length: folder.depth }).map((_, i) => (
|
|
310
|
-
<div key={i} css={styles.treeLine} />
|
|
311
|
-
))}
|
|
312
|
-
</div>
|
|
313
|
-
)}
|
|
314
|
-
<svg css={styles.folderIcon} fill="currentColor" viewBox="0 0 24 24">
|
|
315
|
-
<path d="M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" />
|
|
316
|
-
</svg>
|
|
317
|
-
<span css={styles.folderName}>
|
|
318
|
-
{folder.name}
|
|
319
|
-
{disabled && ' (current)'}
|
|
320
|
-
</span>
|
|
321
|
-
</div>
|
|
322
|
-
)
|
|
323
|
-
})}
|
|
324
|
-
</div>
|
|
325
|
-
)}
|
|
326
|
-
</div>
|
|
327
|
-
<div css={styles.footer}>
|
|
328
|
-
<button css={[styles.btn, styles.btnCancel]} onClick={onCancel}>
|
|
329
|
-
Cancel
|
|
330
|
-
</button>
|
|
331
|
-
<button
|
|
332
|
-
css={[styles.btn, styles.btnConfirm]}
|
|
333
|
-
onClick={handleConfirm}
|
|
334
|
-
disabled={!selectedFolder}
|
|
335
|
-
>
|
|
336
|
-
Move Here
|
|
337
|
-
</button>
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
)
|
|
342
|
-
}
|