mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(apps): publish button in app toolbar + published links in detail
- toolbar (local apps): 'Publish' button; turns into a green 'Published' badge-check once published (click opens the detail panel) - detail Source section: clickable repo + release links for published apps
This commit is contained in:
parent
8e4f234d69
commit
5e0fc059e7
2 changed files with 58 additions and 4 deletions
|
|
@ -180,8 +180,20 @@ export function AppDetail({ folder, onClose }: { folder: string; onClose: () =>
|
|||
<InfoRow k="Installed" v={new Date(app.install.installedAt).toLocaleString()} />
|
||||
{app.install.updatedAt && <InfoRow k="Updated" v={new Date(app.install.updatedAt).toLocaleString()} />}
|
||||
</>
|
||||
) : app.publish ? (
|
||||
<>
|
||||
<p className="text-muted-foreground">Local app — published to the Rowboat catalog.</p>
|
||||
<InfoRow k="Repository" v={app.publish.repo} mono link={`https://github.com/${app.publish.repo}`} />
|
||||
{app.publish.lastPublishedVersion && (
|
||||
<InfoRow
|
||||
k="Published"
|
||||
v={`v${app.publish.lastPublishedVersion}`}
|
||||
link={`https://github.com/${app.publish.repo}/releases/tag/v${app.publish.lastPublishedVersion}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<p className="text-muted-foreground">Local app — created on this machine{app.publish ? `, published as ${app.publish.repo}` : ''}.</p>
|
||||
<p className="text-muted-foreground">Local app — created on this machine.</p>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2 pt-1">
|
||||
{app.kind === 'installed' && app.install?.repo && (
|
||||
|
|
@ -268,11 +280,22 @@ export function AppDetail({ folder, onClose }: { folder: string; onClose: () =>
|
|||
)
|
||||
}
|
||||
|
||||
function InfoRow({ k, v, mono }: { k: string; v: string; mono?: boolean }) {
|
||||
function InfoRow({ k, v, mono, link }: { k: string; v: string; mono?: boolean; link?: string }) {
|
||||
return (
|
||||
<div className="flex items-baseline gap-2">
|
||||
<span className="w-28 shrink-0 text-xs text-muted-foreground">{k}</span>
|
||||
<span className={`min-w-0 truncate ${mono ? 'font-mono text-xs' : ''}`}>{v}</span>
|
||||
{link ? (
|
||||
<a
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={`min-w-0 truncate text-primary hover:underline ${mono ? 'font-mono text-xs' : ''}`}
|
||||
>
|
||||
{v}
|
||||
</a>
|
||||
) : (
|
||||
<span className={`min-w-0 truncate ${mono ? 'font-mono text-xs' : ''}`}>{v}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { ArrowLeft, ExternalLink, Info, RotateCw } from 'lucide-react'
|
||||
import { ArrowLeft, BadgeCheck, ExternalLink, Info, RotateCw, UploadCloud } from 'lucide-react'
|
||||
import type { rowboatApp } from '@x/shared'
|
||||
import { appOpened } from '@/lib/analytics'
|
||||
import { AppDetail } from '@/components/apps/app-detail'
|
||||
import { PublishDialog } from '@/components/apps/publish-dialog'
|
||||
|
||||
// Full-height iframe on the app's own origin (spec §6.6). No sandbox attr —
|
||||
// per-app browser origins are the isolation boundary. Toolbar: back, reload,
|
||||
|
|
@ -11,6 +12,7 @@ import { AppDetail } from '@/components/apps/app-detail'
|
|||
export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack: () => void }) {
|
||||
const [reloadNonce, setReloadNonce] = useState(0)
|
||||
const [showDetail, setShowDetail] = useState(false)
|
||||
const [showPublish, setShowPublish] = useState(false)
|
||||
// Load watchdog: if the iframe hasn't fired `load` within the deadline,
|
||||
// surface a visible retry state instead of a silent blank pane.
|
||||
const [loadState, setLoadState] = useState<'loading' | 'ok' | 'stuck'>('loading')
|
||||
|
|
@ -62,6 +64,27 @@ export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack:
|
|||
>
|
||||
<ExternalLink className="size-4" />
|
||||
</button>
|
||||
{app.kind === 'local' && (
|
||||
app.publish ? (
|
||||
<button
|
||||
type="button"
|
||||
title={`Published as ${app.publish.repo} — view details`}
|
||||
onClick={() => setShowDetail(true)}
|
||||
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium text-green-600 hover:bg-green-500/10 dark:text-green-500"
|
||||
>
|
||||
<BadgeCheck className="size-4" /> Published
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
title="Publish this app"
|
||||
onClick={() => setShowPublish(true)}
|
||||
className="flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium text-muted-foreground hover:bg-accent hover:text-foreground"
|
||||
>
|
||||
<UploadCloud className="size-4" /> Publish
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
title="App details"
|
||||
|
|
@ -100,6 +123,14 @@ export function AppFrame({ app, onBack }: { app: rowboatApp.AppSummary; onBack:
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showPublish && (
|
||||
<PublishDialog
|
||||
folder={app.folder}
|
||||
appName={title}
|
||||
onClose={() => setShowPublish(false)}
|
||||
onPublished={() => setShowDetail(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue