@even-toolkit/create-even-app 1.1.0
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/index.js +159 -0
- package/package.json +28 -0
- package/templates/chat/README.md +27 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +34 -0
- package/templates/chat/src/App.tsx +61 -0
- package/templates/chat/src/app.css +54 -0
- package/templates/chat/src/contexts/ChatContext.tsx +99 -0
- package/templates/chat/src/glass/AppGlasses.tsx +70 -0
- package/templates/chat/src/glass/screens/home.ts +24 -0
- package/templates/chat/src/glass/selectors.ts +9 -0
- package/templates/chat/src/glass/shared.ts +8 -0
- package/templates/chat/src/glass/splash.ts +25 -0
- package/templates/chat/src/main.tsx +13 -0
- package/templates/chat/src/screens/ChatScreen.tsx +69 -0
- package/templates/chat/src/screens/Settings.tsx +88 -0
- package/templates/chat/src/types.ts +13 -0
- package/templates/chat/src/vite-env.d.ts +1 -0
- package/templates/chat/template.json +7 -0
- package/templates/chat/tsconfig.json +20 -0
- package/templates/chat/tsconfig.node.json +13 -0
- package/templates/chat/vite.config.ts +12 -0
- package/templates/dashboard/README.md +17 -0
- package/templates/dashboard/index.html +12 -0
- package/templates/dashboard/package.json +34 -0
- package/templates/dashboard/src/App.tsx +27 -0
- package/templates/dashboard/src/app.css +54 -0
- package/templates/dashboard/src/glass/AppGlasses.tsx +53 -0
- package/templates/dashboard/src/glass/screens/home.ts +23 -0
- package/templates/dashboard/src/glass/selectors.ts +9 -0
- package/templates/dashboard/src/glass/shared.ts +8 -0
- package/templates/dashboard/src/glass/splash.ts +22 -0
- package/templates/dashboard/src/main.tsx +13 -0
- package/templates/dashboard/src/screens/ChartsScreen.tsx +99 -0
- package/templates/dashboard/src/screens/OverviewScreen.tsx +102 -0
- package/templates/dashboard/src/screens/SettingsScreen.tsx +60 -0
- package/templates/dashboard/src/vite-env.d.ts +1 -0
- package/templates/dashboard/template.json +7 -0
- package/templates/dashboard/tsconfig.json +20 -0
- package/templates/dashboard/tsconfig.node.json +13 -0
- package/templates/dashboard/vite.config.ts +12 -0
- package/templates/media/README.md +27 -0
- package/templates/media/index.html +12 -0
- package/templates/media/package.json +34 -0
- package/templates/media/src/App.tsx +24 -0
- package/templates/media/src/app.css +54 -0
- package/templates/media/src/contexts/MediaContext.tsx +108 -0
- package/templates/media/src/glass/AppGlasses.tsx +59 -0
- package/templates/media/src/glass/screens/home.ts +24 -0
- package/templates/media/src/glass/selectors.ts +9 -0
- package/templates/media/src/glass/shared.ts +8 -0
- package/templates/media/src/glass/splash.ts +25 -0
- package/templates/media/src/layouts/shell.tsx +39 -0
- package/templates/media/src/main.tsx +13 -0
- package/templates/media/src/screens/AudioScreen.tsx +78 -0
- package/templates/media/src/screens/GalleryScreen.tsx +98 -0
- package/templates/media/src/screens/Settings.tsx +86 -0
- package/templates/media/src/screens/UploadScreen.tsx +95 -0
- package/templates/media/src/types.ts +29 -0
- package/templates/media/src/vite-env.d.ts +1 -0
- package/templates/media/template.json +7 -0
- package/templates/media/tsconfig.json +20 -0
- package/templates/media/tsconfig.node.json +13 -0
- package/templates/media/vite.config.ts +12 -0
- package/templates/minimal/README.md +27 -0
- package/templates/minimal/index.html +12 -0
- package/templates/minimal/package.json +34 -0
- package/templates/minimal/src/App.tsx +50 -0
- package/templates/minimal/src/app.css +54 -0
- package/templates/minimal/src/glass/AppGlasses.tsx +54 -0
- package/templates/minimal/src/glass/screens/home.ts +24 -0
- package/templates/minimal/src/glass/selectors.ts +9 -0
- package/templates/minimal/src/glass/shared.ts +8 -0
- package/templates/minimal/src/glass/splash.ts +25 -0
- package/templates/minimal/src/main.tsx +13 -0
- package/templates/minimal/src/vite-env.d.ts +1 -0
- package/templates/minimal/template.json +7 -0
- package/templates/minimal/tsconfig.json +20 -0
- package/templates/minimal/tsconfig.node.json +13 -0
- package/templates/minimal/vite.config.ts +12 -0
- package/templates/notes/README.md +27 -0
- package/templates/notes/index.html +12 -0
- package/templates/notes/package.json +34 -0
- package/templates/notes/src/App.tsx +25 -0
- package/templates/notes/src/app.css +54 -0
- package/templates/notes/src/contexts/NotesContext.tsx +140 -0
- package/templates/notes/src/glass/AppGlasses.tsx +58 -0
- package/templates/notes/src/glass/screens/home.ts +24 -0
- package/templates/notes/src/glass/selectors.ts +9 -0
- package/templates/notes/src/glass/shared.ts +8 -0
- package/templates/notes/src/glass/splash.ts +24 -0
- package/templates/notes/src/layouts/shell.tsx +36 -0
- package/templates/notes/src/main.tsx +13 -0
- package/templates/notes/src/screens/NoteDetail.tsx +104 -0
- package/templates/notes/src/screens/NoteForm.tsx +84 -0
- package/templates/notes/src/screens/NoteList.tsx +108 -0
- package/templates/notes/src/screens/Settings.tsx +88 -0
- package/templates/notes/src/types.ts +14 -0
- package/templates/notes/src/vite-env.d.ts +1 -0
- package/templates/notes/template.json +7 -0
- package/templates/notes/tsconfig.json +20 -0
- package/templates/notes/tsconfig.node.json +13 -0
- package/templates/notes/vite.config.ts +12 -0
- package/templates/tracker/README.md +27 -0
- package/templates/tracker/index.html +12 -0
- package/templates/tracker/package.json +34 -0
- package/templates/tracker/src/App.tsx +24 -0
- package/templates/tracker/src/app.css +54 -0
- package/templates/tracker/src/contexts/TrackerContext.tsx +193 -0
- package/templates/tracker/src/glass/AppGlasses.tsx +64 -0
- package/templates/tracker/src/glass/screens/home.ts +24 -0
- package/templates/tracker/src/glass/selectors.ts +9 -0
- package/templates/tracker/src/glass/shared.ts +8 -0
- package/templates/tracker/src/glass/splash.ts +24 -0
- package/templates/tracker/src/layouts/shell.tsx +37 -0
- package/templates/tracker/src/main.tsx +13 -0
- package/templates/tracker/src/screens/HistoryScreen.tsx +106 -0
- package/templates/tracker/src/screens/NewEntryScreen.tsx +135 -0
- package/templates/tracker/src/screens/Settings.tsx +135 -0
- package/templates/tracker/src/screens/TodayScreen.tsx +147 -0
- package/templates/tracker/src/types.ts +34 -0
- package/templates/tracker/src/vite-env.d.ts +1 -0
- package/templates/tracker/template.json +7 -0
- package/templates/tracker/tsconfig.json +20 -0
- package/templates/tracker/tsconfig.node.json +13 -0
- package/templates/tracker/vite.config.ts +12 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Card, ListItem, ScreenHeader, Button, useDrawerHeader } from 'even-toolkit/web'
|
|
2
|
+
import { IcEditPlay, IcEditPause } from 'even-toolkit/web/icons/svg-icons'
|
|
3
|
+
import { useMedia } from '../contexts/MediaContext'
|
|
4
|
+
|
|
5
|
+
export function AudioScreen() {
|
|
6
|
+
const { audioTracks, selectedTrackId, setSelectedTrackId } = useMedia()
|
|
7
|
+
|
|
8
|
+
useDrawerHeader({ title: 'Audio' })
|
|
9
|
+
|
|
10
|
+
const selectedTrack = audioTracks.find((t) => t.id === selectedTrackId)
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
14
|
+
<ScreenHeader
|
|
15
|
+
title="Audio"
|
|
16
|
+
subtitle={`${audioTracks.length} track${audioTracks.length !== 1 ? 's' : ''}`}
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<div className="rounded-[6px] overflow-hidden divide-y divide-border">
|
|
20
|
+
{audioTracks.map((track) => (
|
|
21
|
+
<ListItem
|
|
22
|
+
key={track.id}
|
|
23
|
+
title={track.title}
|
|
24
|
+
subtitle={track.artist}
|
|
25
|
+
trailing={
|
|
26
|
+
<div className="flex items-center gap-3">
|
|
27
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim tabular-nums">
|
|
28
|
+
{track.duration}
|
|
29
|
+
</span>
|
|
30
|
+
<Button
|
|
31
|
+
variant="ghost"
|
|
32
|
+
size="icon"
|
|
33
|
+
onClick={(e) => {
|
|
34
|
+
e.stopPropagation()
|
|
35
|
+
setSelectedTrackId(selectedTrackId === track.id ? null : track.id)
|
|
36
|
+
}}
|
|
37
|
+
className="w-8 h-8 rounded-full bg-accent text-text-highlight"
|
|
38
|
+
>
|
|
39
|
+
{selectedTrackId === track.id ? (
|
|
40
|
+
<IcEditPause width={14} height={14} />
|
|
41
|
+
) : (
|
|
42
|
+
<IcEditPlay width={14} height={14} />
|
|
43
|
+
)}
|
|
44
|
+
</Button>
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
47
|
+
onPress={() => setSelectedTrackId(track.id)}
|
|
48
|
+
/>
|
|
49
|
+
))}
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{selectedTrack && (
|
|
53
|
+
<Card className="p-3 mt-3">
|
|
54
|
+
<div className="flex items-center gap-3">
|
|
55
|
+
<Button
|
|
56
|
+
variant="ghost"
|
|
57
|
+
size="icon"
|
|
58
|
+
onClick={() => setSelectedTrackId(null)}
|
|
59
|
+
className="shrink-0 w-10 h-10 rounded-full bg-accent text-text-highlight"
|
|
60
|
+
>
|
|
61
|
+
<IcEditPause width={16} height={16} />
|
|
62
|
+
</Button>
|
|
63
|
+
<div className="flex-1 min-w-0">
|
|
64
|
+
<p className="text-[15px] tracking-[-0.15px] text-text truncate">{selectedTrack.title}</p>
|
|
65
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim">{selectedTrack.artist}</p>
|
|
66
|
+
</div>
|
|
67
|
+
<span className="text-[13px] tracking-[-0.13px] text-text-dim tabular-nums shrink-0">
|
|
68
|
+
{selectedTrack.duration}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="mt-2 h-1 bg-surface rounded-full overflow-hidden">
|
|
72
|
+
<div className="h-full bg-accent rounded-full w-1/3" />
|
|
73
|
+
</div>
|
|
74
|
+
</Card>
|
|
75
|
+
)}
|
|
76
|
+
</main>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { CategoryFilter, BottomSheet, Card, ScreenHeader, Button, useDrawerHeader } from 'even-toolkit/web'
|
|
3
|
+
import { IcGuideChevronSmallBack, IcGuideChevronSmallDrillIn } from 'even-toolkit/web/icons/svg-icons'
|
|
4
|
+
import { useMedia } from '../contexts/MediaContext'
|
|
5
|
+
import { ALL_FILTER, CATEGORIES, type CategoryFilter as CategoryFilterType } from '../types'
|
|
6
|
+
|
|
7
|
+
const FILTER_OPTIONS = [ALL_FILTER, ...CATEGORIES]
|
|
8
|
+
|
|
9
|
+
export function GalleryScreen() {
|
|
10
|
+
const { filteredGallery, selectedCategory, setSelectedCategory, gridColumns } = useMedia()
|
|
11
|
+
const [viewerIndex, setViewerIndex] = useState<number | null>(null)
|
|
12
|
+
|
|
13
|
+
useDrawerHeader({})
|
|
14
|
+
|
|
15
|
+
const selectedItem = viewerIndex !== null ? filteredGallery[viewerIndex] : null
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
19
|
+
<ScreenHeader
|
|
20
|
+
title="Gallery"
|
|
21
|
+
subtitle={`${filteredGallery.length} item${filteredGallery.length !== 1 ? 's' : ''}`}
|
|
22
|
+
/>
|
|
23
|
+
|
|
24
|
+
<CategoryFilter
|
|
25
|
+
categories={FILTER_OPTIONS}
|
|
26
|
+
selected={selectedCategory}
|
|
27
|
+
onSelect={(c) => setSelectedCategory(c as CategoryFilterType)}
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<div className={`grid gap-3 ${gridColumns === 2 ? 'grid-cols-2' : 'grid-cols-3'}`}>
|
|
31
|
+
{filteredGallery.map((item, index) => (
|
|
32
|
+
<Card
|
|
33
|
+
key={item.id}
|
|
34
|
+
className="overflow-hidden cursor-pointer"
|
|
35
|
+
onClick={() => setViewerIndex(index)}
|
|
36
|
+
>
|
|
37
|
+
<div
|
|
38
|
+
className="w-full aspect-square rounded-[6px]"
|
|
39
|
+
style={{ background: item.gradient }}
|
|
40
|
+
/>
|
|
41
|
+
<div className="p-2">
|
|
42
|
+
<p className="text-[13px] tracking-[-0.13px] text-text truncate">{item.title}</p>
|
|
43
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim">{item.date}</p>
|
|
44
|
+
</div>
|
|
45
|
+
</Card>
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<BottomSheet open={viewerIndex !== null} onClose={() => setViewerIndex(null)}>
|
|
50
|
+
{selectedItem && (
|
|
51
|
+
<div className="px-4 pb-2">
|
|
52
|
+
<div
|
|
53
|
+
className="w-full aspect-video rounded-[6px] mb-3"
|
|
54
|
+
style={{ background: selectedItem.gradient }}
|
|
55
|
+
/>
|
|
56
|
+
<div className="flex items-center justify-between mb-2">
|
|
57
|
+
<div>
|
|
58
|
+
<p className="text-[17px] tracking-[-0.17px] text-text">{selectedItem.title}</p>
|
|
59
|
+
<p className="text-[13px] tracking-[-0.13px] text-text-dim">
|
|
60
|
+
{selectedItem.category} · {selectedItem.date}
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex items-center justify-between pt-2">
|
|
65
|
+
<Button
|
|
66
|
+
variant="ghost"
|
|
67
|
+
size="sm"
|
|
68
|
+
onClick={() => {
|
|
69
|
+
if (viewerIndex !== null && viewerIndex > 0) setViewerIndex(viewerIndex - 1)
|
|
70
|
+
}}
|
|
71
|
+
disabled={viewerIndex === 0}
|
|
72
|
+
className="flex items-center gap-1 text-[13px] tracking-[-0.13px] text-text-dim"
|
|
73
|
+
>
|
|
74
|
+
<IcGuideChevronSmallBack width={16} height={16} />
|
|
75
|
+
Previous
|
|
76
|
+
</Button>
|
|
77
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim tabular-nums">
|
|
78
|
+
{viewerIndex !== null ? viewerIndex + 1 : 0} / {filteredGallery.length}
|
|
79
|
+
</span>
|
|
80
|
+
<Button
|
|
81
|
+
variant="ghost"
|
|
82
|
+
size="sm"
|
|
83
|
+
onClick={() => {
|
|
84
|
+
if (viewerIndex !== null && viewerIndex < filteredGallery.length - 1) setViewerIndex(viewerIndex + 1)
|
|
85
|
+
}}
|
|
86
|
+
disabled={viewerIndex === filteredGallery.length - 1}
|
|
87
|
+
className="flex items-center gap-1 text-[13px] tracking-[-0.13px] text-text-dim"
|
|
88
|
+
>
|
|
89
|
+
Next
|
|
90
|
+
<IcGuideChevronSmallDrillIn width={16} height={16} />
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
95
|
+
</BottomSheet>
|
|
96
|
+
</main>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { SettingsGroup, Toggle, ListItem, Card, Button, Divider, useDrawerHeader } from 'even-toolkit/web'
|
|
3
|
+
import { useMedia } from '../contexts/MediaContext'
|
|
4
|
+
|
|
5
|
+
export function Settings() {
|
|
6
|
+
const { gridColumns, setGridColumns, uploads, clearUploads } = useMedia()
|
|
7
|
+
const [confirmClear, setConfirmClear] = useState(false)
|
|
8
|
+
|
|
9
|
+
useDrawerHeader({ title: 'Settings', backTo: '/' })
|
|
10
|
+
|
|
11
|
+
function handleClearCache() {
|
|
12
|
+
if (!confirmClear) {
|
|
13
|
+
setConfirmClear(true)
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
clearUploads()
|
|
17
|
+
setConfirmClear(false)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<main className="px-3 pt-4 pb-8 space-y-6">
|
|
22
|
+
<SettingsGroup label="Display">
|
|
23
|
+
<Card className="p-4 space-y-3">
|
|
24
|
+
<div className="flex items-center justify-between">
|
|
25
|
+
<div>
|
|
26
|
+
<p className="text-[15px] tracking-[-0.15px] text-text">Grid Columns</p>
|
|
27
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim mt-0.5">
|
|
28
|
+
Toggle between 2 and 3 column layout
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
<Toggle
|
|
32
|
+
checked={gridColumns === 2}
|
|
33
|
+
onChange={(v) => setGridColumns(v ? 2 : 3)}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
<Divider />
|
|
37
|
+
<div>
|
|
38
|
+
<p className="text-[15px] tracking-[-0.15px] text-text">Thumbnail Size</p>
|
|
39
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim mt-0.5">
|
|
40
|
+
{gridColumns === 2 ? 'Large thumbnails (2 columns)' : 'Compact thumbnails (3 columns)'}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
</Card>
|
|
44
|
+
</SettingsGroup>
|
|
45
|
+
|
|
46
|
+
<SettingsGroup label="Storage">
|
|
47
|
+
<Card className="divide-y divide-border">
|
|
48
|
+
<div>
|
|
49
|
+
<ListItem
|
|
50
|
+
title={confirmClear ? 'Tap again to confirm' : 'Clear Cache'}
|
|
51
|
+
subtitle={`${uploads.length} uploaded file${uploads.length !== 1 ? 's' : ''} cached`}
|
|
52
|
+
onPress={handleClearCache}
|
|
53
|
+
/>
|
|
54
|
+
{confirmClear && (
|
|
55
|
+
<div className="px-4 pb-3">
|
|
56
|
+
<Button
|
|
57
|
+
variant="ghost"
|
|
58
|
+
size="sm"
|
|
59
|
+
className="w-full"
|
|
60
|
+
onClick={() => setConfirmClear(false)}
|
|
61
|
+
>
|
|
62
|
+
Cancel
|
|
63
|
+
</Button>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
<ListItem
|
|
68
|
+
title="Storage Used"
|
|
69
|
+
subtitle="In-memory only (no persistent storage)"
|
|
70
|
+
/>
|
|
71
|
+
</Card>
|
|
72
|
+
</SettingsGroup>
|
|
73
|
+
|
|
74
|
+
<SettingsGroup label="About">
|
|
75
|
+
<Card className="p-4 space-y-1.5">
|
|
76
|
+
<p className="text-[15px] tracking-[-0.15px] text-text">{{DISPLAY_NAME}}</p>
|
|
77
|
+
<p className="text-[13px] tracking-[-0.13px] text-text-dim">Version 1.0.0</p>
|
|
78
|
+
<Divider className="my-2" />
|
|
79
|
+
<p className="text-[11px] tracking-[-0.11px] text-text-dim">
|
|
80
|
+
A media gallery app for Even Realities G2 smart glasses. Browse photos, listen to audio, and manage uploads.
|
|
81
|
+
</p>
|
|
82
|
+
</Card>
|
|
83
|
+
</SettingsGroup>
|
|
84
|
+
</main>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FileUpload, EmptyState, ListItem, Card, Button, ScreenHeader, useDrawerHeader } from 'even-toolkit/web'
|
|
2
|
+
import { IcEditUploadToCloud, IcEditTrash } from 'even-toolkit/web/icons/svg-icons'
|
|
3
|
+
import { useMedia } from '../contexts/MediaContext'
|
|
4
|
+
|
|
5
|
+
function formatSize(bytes: number): string {
|
|
6
|
+
if (bytes < 1024) return `${bytes} B`
|
|
7
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
8
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatTime(timestamp: number): string {
|
|
12
|
+
const diff = Date.now() - timestamp
|
|
13
|
+
const minutes = Math.floor(diff / 60000)
|
|
14
|
+
if (minutes < 1) return 'Just now'
|
|
15
|
+
if (minutes < 60) return `${minutes}m ago`
|
|
16
|
+
const hours = Math.floor(minutes / 60)
|
|
17
|
+
if (hours < 24) return `${hours}h ago`
|
|
18
|
+
return new Date(timestamp).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function UploadScreen() {
|
|
22
|
+
const { uploads, addUpload, removeUpload, clearUploads } = useMedia()
|
|
23
|
+
|
|
24
|
+
useDrawerHeader({ title: 'Upload' })
|
|
25
|
+
|
|
26
|
+
function handleFiles(files: File[]) {
|
|
27
|
+
files.forEach((file) => {
|
|
28
|
+
addUpload(file.name, formatSize(file.size), file.type || 'unknown')
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<main className="px-3 pt-4 pb-8 space-y-3">
|
|
34
|
+
<ScreenHeader
|
|
35
|
+
title="Upload"
|
|
36
|
+
subtitle="Add media to your library"
|
|
37
|
+
actions={
|
|
38
|
+
uploads.length > 0 ? (
|
|
39
|
+
<Button variant="ghost" size="sm" onClick={clearUploads}>
|
|
40
|
+
Clear All
|
|
41
|
+
</Button>
|
|
42
|
+
) : undefined
|
|
43
|
+
}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<FileUpload
|
|
47
|
+
onFiles={handleFiles}
|
|
48
|
+
accept="image/*,audio/*,video/*"
|
|
49
|
+
multiple
|
|
50
|
+
label="Drop media files or tap to browse"
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
{uploads.length === 0 ? (
|
|
54
|
+
<EmptyState
|
|
55
|
+
icon={<IcEditUploadToCloud width={48} height={48} />}
|
|
56
|
+
title="No uploads yet"
|
|
57
|
+
description="Use the drop zone above to add photos, audio, or video files to your media library."
|
|
58
|
+
/>
|
|
59
|
+
) : (
|
|
60
|
+
<div>
|
|
61
|
+
<p className="text-[13px] tracking-[-0.13px] text-text-dim mb-2">
|
|
62
|
+
Recent Uploads ({uploads.length})
|
|
63
|
+
</p>
|
|
64
|
+
<Card className="rounded-[6px] overflow-hidden divide-y divide-border">
|
|
65
|
+
{uploads.map((upload) => (
|
|
66
|
+
<ListItem
|
|
67
|
+
key={upload.id}
|
|
68
|
+
title={upload.name}
|
|
69
|
+
subtitle={`${upload.size} \u00b7 ${upload.type}`}
|
|
70
|
+
trailing={
|
|
71
|
+
<div className="flex items-center gap-2">
|
|
72
|
+
<span className="text-[11px] tracking-[-0.11px] text-text-dim">
|
|
73
|
+
{formatTime(upload.timestamp)}
|
|
74
|
+
</span>
|
|
75
|
+
<Button
|
|
76
|
+
variant="ghost"
|
|
77
|
+
size="icon"
|
|
78
|
+
onClick={(e) => {
|
|
79
|
+
e.stopPropagation()
|
|
80
|
+
removeUpload(upload.id)
|
|
81
|
+
}}
|
|
82
|
+
className="text-text-dim hover:text-negative"
|
|
83
|
+
>
|
|
84
|
+
<IcEditTrash width={16} height={16} />
|
|
85
|
+
</Button>
|
|
86
|
+
</div>
|
|
87
|
+
}
|
|
88
|
+
/>
|
|
89
|
+
))}
|
|
90
|
+
</Card>
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
93
|
+
</main>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type MediaCategory = 'Photos' | 'Artwork' | 'Screenshots'
|
|
2
|
+
|
|
3
|
+
export interface GalleryItem {
|
|
4
|
+
id: string
|
|
5
|
+
title: string
|
|
6
|
+
category: MediaCategory
|
|
7
|
+
date: string
|
|
8
|
+
gradient: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AudioTrack {
|
|
12
|
+
id: string
|
|
13
|
+
title: string
|
|
14
|
+
artist: string
|
|
15
|
+
duration: string
|
|
16
|
+
durationSeconds: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UploadItem {
|
|
20
|
+
id: string
|
|
21
|
+
name: string
|
|
22
|
+
size: string
|
|
23
|
+
type: string
|
|
24
|
+
timestamp: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CATEGORIES: MediaCategory[] = ['Photos', 'Artwork', 'Screenshots']
|
|
28
|
+
export const ALL_FILTER = 'All'
|
|
29
|
+
export type CategoryFilter = typeof ALL_FILTER | MediaCategory
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "media",
|
|
3
|
+
"displayName": "Media Gallery",
|
|
4
|
+
"description": "A media gallery app with photo grid, audio player, file uploads, and glass display",
|
|
5
|
+
"tags": ["media", "gallery", "audio", "photos"],
|
|
6
|
+
"components": ["DrawerShell", "Card", "ListItem", "CategoryFilter", "BottomSheet", "ImageViewer", "AudioPlayer", "FileUpload", "EmptyState", "SettingsGroup", "Toggle", "Button", "ScreenHeader"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"isolatedModules": true,
|
|
15
|
+
"baseUrl": ".",
|
|
16
|
+
"paths": { "@/*": ["./src/*"] }
|
|
17
|
+
},
|
|
18
|
+
"include": ["src"],
|
|
19
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import react from '@vitejs/plugin-react'
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react(), tailwindcss()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: { '@': path.resolve(__dirname, './src') },
|
|
10
|
+
dedupe: ['react', 'react-dom', 'react-router', '@evenrealities/even_hub_sdk', '@jappyjan/even-better-sdk', 'upng-js'],
|
|
11
|
+
},
|
|
12
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# {{DISPLAY_NAME}}
|
|
2
|
+
|
|
3
|
+
A G2 smart glasses app built with [even-toolkit](https://www.npmjs.com/package/even-toolkit).
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Open [http://localhost:5173](http://localhost:5173) in your browser.
|
|
13
|
+
|
|
14
|
+
## Test with Simulator
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @evenrealities/evenhub-simulator@latest http://localhost:5173
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Build for Even Hub
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run build
|
|
24
|
+
npx @evenrealities/evenhub-cli pack app.json dist
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Upload the generated `.ehpk` file to the Even Hub.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
|
6
|
+
<title>{{DISPLAY_NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite --port 5173 --host",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@evenrealities/even_hub_sdk": "^0.0.9",
|
|
13
|
+
"@jappyjan/even-better-sdk": "^0.0.11",
|
|
14
|
+
"class-variance-authority": "^0.7.1",
|
|
15
|
+
"clsx": "^2.1.0",
|
|
16
|
+
"even-toolkit": "^1.5.0",
|
|
17
|
+
"react": "^19.0.0",
|
|
18
|
+
"react-dom": "^19.0.0",
|
|
19
|
+
"react-is": "^19.2.4",
|
|
20
|
+
"react-router": "^7.0.0",
|
|
21
|
+
"tailwind-merge": "^3.0.0",
|
|
22
|
+
"upng-js": "^2.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"@types/node": "^22.0.0",
|
|
29
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
30
|
+
"tailwindcss": "^4.0.0",
|
|
31
|
+
"typescript": "~5.4.0",
|
|
32
|
+
"vite": "^5.4.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Routes, Route } from 'react-router'
|
|
2
|
+
import { AppShell, NavHeader, ScreenHeader, Card, Button, SectionHeader, ListItem } from 'even-toolkit/web'
|
|
3
|
+
import { AppGlasses } from './glass/AppGlasses'
|
|
4
|
+
|
|
5
|
+
function Home() {
|
|
6
|
+
return (
|
|
7
|
+
<AppShell header={<NavHeader title="{{DISPLAY_NAME}}" />}>
|
|
8
|
+
<div className="px-3 pt-4 pb-8 space-y-3">
|
|
9
|
+
<ScreenHeader
|
|
10
|
+
title="Welcome"
|
|
11
|
+
subtitle="Your G2 glasses app is ready"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<Card>
|
|
15
|
+
<ListItem
|
|
16
|
+
title="Get started"
|
|
17
|
+
subtitle="Edit src/App.tsx to build your app"
|
|
18
|
+
/>
|
|
19
|
+
<ListItem
|
|
20
|
+
title="Components"
|
|
21
|
+
subtitle="55+ React components from even-toolkit"
|
|
22
|
+
/>
|
|
23
|
+
<ListItem
|
|
24
|
+
title="Glasses SDK"
|
|
25
|
+
subtitle="G2 display, gestures, and speech-to-text"
|
|
26
|
+
/>
|
|
27
|
+
</Card>
|
|
28
|
+
|
|
29
|
+
<SectionHeader title="Quick links" />
|
|
30
|
+
<div className="flex gap-2">
|
|
31
|
+
<Button variant="highlight" size="sm" onClick={() => window.open('https://www.npmjs.com/package/even-toolkit', '_blank')}>
|
|
32
|
+
Toolkit Docs
|
|
33
|
+
</Button>
|
|
34
|
+
<Button variant="highlight" size="sm" onClick={() => window.open('https://even-demo.vercel.app', '_blank')}>
|
|
35
|
+
Component Demo
|
|
36
|
+
</Button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<AppGlasses />
|
|
40
|
+
</AppShell>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function App() {
|
|
45
|
+
return (
|
|
46
|
+
<Routes>
|
|
47
|
+
<Route path="/*" element={<Home />} />
|
|
48
|
+
</Routes>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "even-toolkit/web/theme-light.css";
|
|
3
|
+
@import "even-toolkit/web/typography.css";
|
|
4
|
+
@import "even-toolkit/web/utilities.css";
|
|
5
|
+
@source "../src";
|
|
6
|
+
@source "../node_modules/even-toolkit/web";
|
|
7
|
+
@source "../node_modules/even-toolkit/dist";
|
|
8
|
+
|
|
9
|
+
@theme {
|
|
10
|
+
--color-bg: var(--color-bg);
|
|
11
|
+
--color-surface: var(--color-surface);
|
|
12
|
+
--color-surface-light: var(--color-surface-light);
|
|
13
|
+
--color-surface-lighter: var(--color-surface-lighter);
|
|
14
|
+
--color-border: var(--color-border);
|
|
15
|
+
--color-border-light: var(--color-border-light);
|
|
16
|
+
--color-text: var(--color-text);
|
|
17
|
+
--color-text-dim: var(--color-text-dim);
|
|
18
|
+
--color-text-muted: var(--color-text-muted);
|
|
19
|
+
--color-text-highlight: var(--color-text-highlight);
|
|
20
|
+
--color-accent: var(--color-accent);
|
|
21
|
+
--color-accent-alpha: var(--color-accent-alpha);
|
|
22
|
+
--color-accent-warning: var(--color-accent-warning);
|
|
23
|
+
--color-positive: var(--color-positive);
|
|
24
|
+
--color-positive-alpha: var(--color-positive-alpha);
|
|
25
|
+
--color-negative: var(--color-negative);
|
|
26
|
+
--color-negative-alpha: var(--color-negative-alpha);
|
|
27
|
+
--color-overlay: var(--color-overlay);
|
|
28
|
+
--color-input-bg: var(--color-input-bg);
|
|
29
|
+
--radius-default: var(--radius-default);
|
|
30
|
+
--font-display: var(--font-display);
|
|
31
|
+
--font-body: var(--font-body);
|
|
32
|
+
--font-mono: var(--font-mono);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
html, body {
|
|
36
|
+
background: var(--color-bg);
|
|
37
|
+
color: var(--color-text);
|
|
38
|
+
min-height: 100dvh;
|
|
39
|
+
font-family: var(--font-display);
|
|
40
|
+
-webkit-font-smoothing: antialiased;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#root {
|
|
44
|
+
max-width: 430px;
|
|
45
|
+
margin: 0 auto;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.scrollbar-hide {
|
|
49
|
+
-ms-overflow-style: none;
|
|
50
|
+
scrollbar-width: none;
|
|
51
|
+
}
|
|
52
|
+
.scrollbar-hide::-webkit-scrollbar {
|
|
53
|
+
display: none;
|
|
54
|
+
}
|