Skip to content

Commit 6f72e4b

Browse files
authored
Merge pull request #3 from CommandOSSLabs/feat/upadte-site
Feat/upadte site
2 parents e90d29d + f4e1b46 commit 6f72e4b

13 files changed

Lines changed: 495 additions & 123 deletions

File tree

apps/playground/app/files.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const testFiles = {
1+
export const testFilesSet1 = {
22
'img/glass-of-water.svg': `<svg width="100" height="120" viewBox="0 0 100 120" xmlns="http://www.w3.org/2000/svg">
33
<!-- Glass outline -->
44
<path d="M20 20 L80 20 L75 100 L25 100 Z"
@@ -56,3 +56,59 @@ export const testFiles = {
5656
</body>
5757
</html>`
5858
}
59+
60+
export const testFilesSet2 = {
61+
'img/sun.svg': `<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
62+
<!-- Sun circle -->
63+
<circle cx="50" cy="50" r="20" fill="#FFD700"/>
64+
<!-- Sun rays -->
65+
<g stroke="#FFD700" stroke-width="3" stroke-linecap="round">
66+
<line x1="50" y1="15" x2="50" y2="5"/>
67+
<line x1="50" y1="95" x2="50" y2="85"/>
68+
<line x1="15" y1="50" x2="5" y2="50"/>
69+
<line x1="95" y1="50" x2="85" y2="50"/>
70+
<line x1="25" y1="25" x2="18" y2="18"/>
71+
<line x1="75" y1="75" x2="82" y2="82"/>
72+
<line x1="25" y1="75" x2="18" y2="82"/>
73+
<line x1="75" y1="25" x2="82" y2="18"/>
74+
</g>
75+
</svg>`,
76+
'index.html': `<!DOCTYPE html>
77+
<html lang="en">
78+
<head>
79+
<meta charset="UTF-8">
80+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
81+
<title>Good Morning</title>
82+
<style>
83+
body {
84+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
85+
display: flex;
86+
flex-direction: column;
87+
align-items: center;
88+
justify-content: center;
89+
min-height: 100vh;
90+
margin: 0;
91+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
92+
color: white;
93+
}
94+
h1 {
95+
font-size: 2.5rem;
96+
margin-bottom: 20px;
97+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
98+
}
99+
p {
100+
font-size: 1.2rem;
101+
opacity: 0.9;
102+
}
103+
</style>
104+
</head>
105+
<body>
106+
<h1>Good Morning!</h1>
107+
<img src="img/sun.svg" alt="Sun" width="100" height="100">
108+
<p>Have a wonderful day!</p>
109+
</body>
110+
</html>`
111+
}
112+
113+
// For backward compatibility
114+
export const testFiles = testFilesSet1

apps/playground/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from 'next/font/google'
33
import Layout from '@/components/Layout'
44
import Providers from '@/components/Providers'
55
import './globals.css'
6+
import { Toaster } from 'sonner'
67

78
const geistSans = Geist({
89
variable: '--font-geist-sans',
@@ -31,6 +32,7 @@ export default function RootLayout({
3132
>
3233
<Providers>
3334
<Layout>{children}</Layout>
35+
<Toaster richColors />
3436
</Providers>
3537
</body>
3638
</html>

apps/playground/app/page.tsx

Lines changed: 98 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,38 @@ import {
55
objectIdToWalrusSiteUrl
66
} from '@cmdoss/site-builder'
77
import {
8-
PublishButton,
98
type SiteMetadata,
109
type SiteMetadataUpdate,
1110
useZenFsWorkspace,
1211
useZenfsFilesQuery
1312
} from '@cmdoss/site-builder-react'
14-
import { AnimatedBackground } from '@/components/AnimatedBackground'
15-
import { FileExplorer } from '@/components/file-explorer/file-explorer'
13+
import AnimatedBackground from '@/components/AnimatedBackground'
1614
import { Introduction } from '@/components/Introduction'
17-
import { Button } from '@/components/ui/button'
1815
import { CardContent } from '@/components/ui/card'
19-
import {
20-
Tooltip,
21-
TooltipContent,
22-
TooltipTrigger
23-
} from '@/components/ui/tooltip'
16+
import { useNetworkConfig } from '@/configs/networkConfig'
17+
import { $siteId } from '@/stores/siteStore'
2418
import '@cmdoss/site-builder-react/styles.css'
2519
import {
2620
useCurrentAccount,
2721
useSignAndExecuteTransaction,
2822
useSuiClient
2923
} from '@mysten/dapp-kit'
24+
import { useStore } from '@nanostores/react'
3025
import { useQueryClient } from '@tanstack/react-query'
31-
import { useCallback, useEffect } from 'react'
26+
import { useCallback, useEffect, useState } from 'react'
3227
import { toast } from 'sonner'
33-
import { testFiles } from './files'
28+
import AssetsSection from '@/components/AssetsSection'
29+
import PublishedSiteInfo from '@/components/PublishedSiteInfo'
30+
import PublishSection from '@/components/PublishSection'
31+
import { testFilesSet1, testFilesSet2 } from './files'
3432

3533
export default function Home() {
3634
const queryClient = useQueryClient()
3735
const suiClient = useSuiClient()
36+
const siteId = useStore($siteId)
37+
const networkConfig = useNetworkConfig()
38+
const [isAddingFiles, setIsAddingFiles] = useState(false)
39+
const [isClearingWorkspace, setIsClearingWorkspace] = useState(false)
3840

3941
const { loading, fileManager: fm } = useZenFsWorkspace(
4042
'/sites/site-01',
@@ -64,7 +66,7 @@ export default function Home() {
6466
if (!fm) throw new Error('FileManager not initialized')
6567

6668
try {
67-
for (const [path, content] of Object.entries(testFiles)) {
69+
for (const [path, content] of Object.entries(testFilesSet1)) {
6870
console.log('Adding test file:', path)
6971
await fm.writeFile(path, new TextEncoder().encode(content))
7072
}
@@ -78,13 +80,27 @@ export default function Home() {
7880
async (site: SiteMetadataUpdate): Promise<SiteMetadata> => {
7981
// Playground has no backend; return the provided site to satisfy types
8082
// but convert imageUrl to string if it's a File object
81-
const imageUrl =
82-
typeof site.imageUrl === 'string'
83-
? site.imageUrl
84-
: typeof site.imageUrl === 'object'
85-
? URL.createObjectURL(site.imageUrl)
86-
: ''
87-
return { ...site, imageUrl }
83+
let imageUrl = ''
84+
if (typeof site.imageUrl === 'string') {
85+
imageUrl = site.imageUrl
86+
} else if (site.imageUrl instanceof File) {
87+
imageUrl = await new Promise<string>((resolve, reject) => {
88+
const reader = new FileReader()
89+
reader.onload = () => resolve(reader.result as string)
90+
reader.onerror = reject
91+
reader.readAsDataURL(site.imageUrl as File)
92+
})
93+
}
94+
95+
if (site.id) {
96+
console.log('💾 Storing published site ID:', site.id)
97+
$siteId.set(site.id) // Store published site ID persistently
98+
}
99+
100+
const result: SiteMetadata = { ...site, imageUrl }
101+
console.log('💾 Updated site metadata:', result)
102+
103+
return result
88104
},
89105
[]
90106
)
@@ -94,28 +110,45 @@ export default function Home() {
94110
toast.error(msg)
95111
}, [])
96112

97-
const addTestFiles = useCallback(async () => {
98-
if (!fm) {
99-
toast.error('FileManager not initialized')
100-
return
101-
}
102-
try {
103-
for (const [path, content] of Object.entries(testFiles)) {
104-
console.log('Adding test file:', path)
105-
await fm.writeFile(path, new TextEncoder().encode(content))
113+
const addTestFiles = useCallback(
114+
async (fileSet: Record<string, string>, setName: string) => {
115+
if (!fm) {
116+
toast.error('FileManager not initialized')
117+
return
106118
}
107-
await refetchAssets()
108-
toast.success('Test files added')
109-
} catch (error) {
110-
console.error('Failed to create test files:', error)
111-
}
112-
}, [fm, refetchAssets])
119+
setIsAddingFiles(true)
120+
try {
121+
for (const [path, content] of Object.entries(fileSet)) {
122+
console.log('Adding test file:', path)
123+
await fm.writeFile(path, new TextEncoder().encode(content))
124+
}
125+
await refetchAssets()
126+
toast.success(`${setName} added`)
127+
} catch (error) {
128+
console.error('Failed to create test files:', error)
129+
} finally {
130+
setIsAddingFiles(false)
131+
}
132+
},
133+
[fm, refetchAssets]
134+
)
135+
136+
const addTestFilesSet1 = useCallback(
137+
() => addTestFiles(testFilesSet1, 'Sample Files Set #1'),
138+
[addTestFiles]
139+
)
140+
141+
const addTestFilesSet2 = useCallback(
142+
() => addTestFiles(testFilesSet2, 'Sample Files Set #2'),
143+
[addTestFiles]
144+
)
113145

114146
const clearWorkspace = useCallback(async () => {
115147
if (!fm) {
116148
toast.error('FileManager not initialized')
117149
return
118150
}
151+
setIsClearingWorkspace(true)
119152
try {
120153
const files = await fm.listFiles()
121154
for (const file of files) {
@@ -126,6 +159,8 @@ export default function Home() {
126159
} catch (error) {
127160
console.error('Failed to clear workspace:', error)
128161
toast.error('Failed to clear workspace')
162+
} finally {
163+
setIsClearingWorkspace(false)
129164
}
130165
}, [fm, refetchAssets])
131166

@@ -141,77 +176,37 @@ export default function Home() {
141176
<div className="flex flex-col items-center gap-6 w-full mx-auto">
142177
<CardContent className="relative w-full">
143178
<h1 className="mb-1">Assets</h1>
144-
{loading && (
145-
<div className="absolute inset-0 bg-background/80 backdrop-blur-sm z-10 flex items-center justify-center rounded-lg">
146-
<div className="flex items-center gap-2">
147-
<span className="text-sm text-muted-foreground">
148-
Loading file system...
149-
</span>
150-
</div>
151-
</div>
152-
)}
153-
{!assets.length ? (
154-
<div className="space-y-4">
155-
<div className="pt-2">
156-
<Button
157-
onClick={addTestFiles}
158-
className="w-full bg-[#97f0e5] text-[#0C0F1D] hover:bg-[#97f0e5]/90 border border-[#97F0E599]"
159-
>
160-
Add Test Files (HTML + SVG)
161-
</Button>
162-
<p className="text-xs text-gray-300 mt-2 text-center">
163-
Creates index.html with Hello World and a glass of water SVG
164-
</p>
165-
</div>
166-
</div>
167-
) : (
168-
<div className="space-y-4">
169-
<FileExplorer className="h-[300px]" assets={assets} />
170-
171-
<Button
172-
onClick={clearWorkspace}
173-
className="w-full text-[#97f0e5] hover:opacity-80 border border-[#97F0E599]"
174-
>
175-
Clear Files
176-
</Button>
177-
</div>
178-
)}
179+
180+
<AssetsSection
181+
loading={loading}
182+
assets={assets}
183+
onAddTestFilesSet1={addTestFilesSet1}
184+
onAddTestFilesSet2={addTestFilesSet2}
185+
onClearWorkspace={clearWorkspace}
186+
isAddingFiles={isAddingFiles}
187+
isClearingWorkspace={isClearingWorkspace}
188+
/>
179189

180190
<hr className="my-4 border-t border-neutral-400 dark:border-neutral-700" />
181-
<div className=" flex flex-col gap-2">
182-
{!currentAccount ? (
183-
<Tooltip>
184-
<TooltipTrigger asChild>
185-
<div>
186-
<Button
187-
className="w-full bg-[#97f0e5] text-[#0C0F1D] hover:bg-[#97f0e5]/90 border border-[#97F0E599]"
188-
disabled
189-
>
190-
Publish
191-
</Button>
192-
</div>
193-
</TooltipTrigger>
194-
<TooltipContent sideOffset={6} className="text-sm">
195-
{!assets.length
196-
? 'Add files to publish your site'
197-
: !currentAccount
198-
? 'Please connect your wallet to publish'
199-
: ''}
200-
</TooltipContent>
201-
</Tooltip>
202-
) : (
203-
<PublishButton
204-
onPrepareAssets={handlePrepareAssetsForBuilder}
205-
onUpdateSiteMetadata={handleUpdateSiteMetadataForBuilder}
206-
onError={handleBuilderError}
207-
currentAccount={currentAccount}
208-
clients={{ suiClient, queryClient }}
209-
signAndExecuteTransaction={signAndExecuteTransaction}
210-
>
211-
<Button className="w-full bg-[#97f0e5] text-[#0C0F1D] hover:bg-[#97f0e5]/90 border border-[#97F0E599]">
212-
Publish
213-
</Button>
214-
</PublishButton>
191+
192+
<div className="flex flex-col gap-2">
193+
<PublishSection
194+
siteId={siteId}
195+
currentAccount={currentAccount}
196+
assets={assets}
197+
onPrepareAssets={handlePrepareAssetsForBuilder}
198+
onUpdateSiteMetadata={handleUpdateSiteMetadataForBuilder}
199+
onError={handleBuilderError}
200+
signAndExecuteTransaction={signAndExecuteTransaction}
201+
clients={{ suiClient, queryClient }}
202+
/>
203+
204+
{siteId && (
205+
<PublishedSiteInfo
206+
siteId={siteId}
207+
network={networkConfig.network}
208+
onClearSiteId={() => $siteId.set('')}
209+
/>
215210
)}
216211
</div>
217212
</CardContent>

apps/playground/components/AnimatedBackground.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { DotLottieReact } from '@lottiefiles/dotlottie-react'
1+
'use client'
22

3-
export function AnimatedBackground() {
3+
import { DotLottieReact, type Layout } from '@lottiefiles/dotlottie-react'
4+
5+
const layoutLeft: Layout = { fit: 'cover', align: [0, 0] }
6+
const layoutRight: Layout = { fit: 'cover', align: [0.01, 0] }
7+
8+
export default function AnimatedBackground() {
49
return (
510
<div className="absolute inset-0 -z-10 overflow-hidden rounded-xl bg-[#0d0f1d]">
611
{/* Left half */}
712
<div className="absolute left-0 top-0 h-full md:w-[336px] w-[152px] overflow-hidden [clip-path:inset(0)]">
813
<div className="z-2 absolute inset-0 bg-linear-to-l from-[#0c0f1d] from-30% to-[#0c0f1d00] to-80%" />
914
<DotLottieReact
1015
src="/animations/grid_loop.lottie"
11-
layout={{ fit: 'cover', align: [0, 0] }}
12-
renderConfig={{ autoResize: true }}
13-
autoplay={true}
14-
loop={true}
16+
layout={layoutLeft}
17+
autoplay
18+
loop
1519
/>
1620
</div>
1721

@@ -21,10 +25,9 @@ export function AnimatedBackground() {
2125
<DotLottieReact
2226
src="/animations/grid_loop.lottie"
2327
style={{ transform: 'rotate(180deg)' }}
24-
layout={{ fit: 'cover', align: [0.01, 0] }}
25-
renderConfig={{ autoResize: true }}
26-
autoplay={true}
27-
loop={true}
28+
layout={layoutRight}
29+
autoplay
30+
loop
2831
/>
2932
</div>
3033
</div>

0 commit comments

Comments
 (0)