ui: add Billing admin nav, drop Editor nav, replace Editor tile with Premiere panel download modal
- shell.jsx: add Billing item under Admin (routes to the parody pricing page); drop Editor from the Operations section - app.jsx: route 'billing' to TokensParody; remove 'editor' and 'tokens-parody' routes - screens-admin.jsx: rename parody h1 from 'Tokens' to 'Billing'; drop the cross-link from the real Tokens page (no longer needed) - screens-home.jsx: replace the Editor launcher tile with a 'Premiere panel' tile that opens a new PremiereDownloadModal listing every registered release (ZXP + Windows installer) with version + LATEST badge + release date + notes - styles-screens.css: .premiere-release-* row styles for the modal Editor screen + nav button retired; users get the Premiere panel as the recommended editor instead, with all download options in one place from Home.
This commit is contained in:
parent
342b56af35
commit
303f12e0f9
5 changed files with 113 additions and 20 deletions
|
|
@ -113,10 +113,9 @@ function App() {
|
||||||
case 'capture': content = <Capture navigate={navigate} />; break;
|
case 'capture': content = <Capture navigate={navigate} />; break;
|
||||||
case 'monitors': content = <Monitors navigate={navigate} />; break;
|
case 'monitors': content = <Monitors navigate={navigate} />; break;
|
||||||
case 'jobs': content = <Jobs navigate={navigate} />; break;
|
case 'jobs': content = <Jobs navigate={navigate} />; break;
|
||||||
case 'editor': content = <Editor />; break;
|
|
||||||
case 'users': content = <Users />; break;
|
case 'users': content = <Users />; break;
|
||||||
case 'tokens': content = <Tokens />; break;
|
case 'tokens': content = <Tokens />; break;
|
||||||
case 'tokens-parody': content = <TokensParody />; break;
|
case 'billing': content = <TokensParody />; break;
|
||||||
case 'containers':content = <Containers />; break;
|
case 'containers':content = <Containers />; break;
|
||||||
case 'cluster': content = <Cluster />; break;
|
case 'cluster': content = <Cluster />; break;
|
||||||
case 'settings': content = <Settings />; break;
|
case 'settings': content = <Settings />; break;
|
||||||
|
|
|
||||||
|
|
@ -538,8 +538,8 @@ function GroupsPanel({ groups, users, onChange }) {
|
||||||
|
|
||||||
// Real Tokens admin page: wraps ApiTokensSection (defined further down) in a
|
// Real Tokens admin page: wraps ApiTokensSection (defined further down) in a
|
||||||
// .page shell so it can be a top-level admin nav destination. The old parody
|
// .page shell so it can be a top-level admin nav destination. The old parody
|
||||||
// page lives below as TokensParody and is still reachable via the hidden
|
// pricing page lives below as TokensParody and is now routed at /billing in
|
||||||
// `tokens-parody` route for posterity.
|
// the Admin section.
|
||||||
function Tokens() {
|
function Tokens() {
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
|
|
@ -549,9 +549,6 @@ function Tokens() {
|
||||||
</div>
|
</div>
|
||||||
<div className="page-body">
|
<div className="page-body">
|
||||||
<ApiTokensSection />
|
<ApiTokensSection />
|
||||||
<div style={{ marginTop: 16, fontSize: 11.5, color: 'var(--text-3)' }}>
|
|
||||||
Looking for the old satirical pricing page? <a href="#tokens-parody" onClick={(e) => { e.preventDefault(); window.dispatchEvent(new CustomEvent('df:nav', { detail: 'tokens-parody' })); }} style={{ color: 'var(--accent-text)' }}>It's still here.</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
@ -611,7 +608,7 @@ function TokensParody() {
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<h1>Tokens</h1>
|
<h1>Billing</h1>
|
||||||
<span className="subtitle">Token-metered pricing parody · You actually pay <strong style={{ color: "var(--success)" }}>$0.00</strong></span>
|
<span className="subtitle">Token-metered pricing parody · You actually pay <strong style={{ color: "var(--success)" }}>$0.00</strong></span>
|
||||||
<div className="spacer" />
|
<div className="spacer" />
|
||||||
<span className="badge warning"><Icon name="alert" size={10} /> SATIRE</span>
|
<span className="badge warning"><Icon name="alert" size={10} /> SATIRE</span>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@
|
||||||
// Anything that would just say "all clear" is hidden, not rendered.
|
// Anything that would just say "all clear" is hidden, not rendered.
|
||||||
|
|
||||||
function Home({ navigate }) {
|
function Home({ navigate }) {
|
||||||
|
const [showPremiereDownload, setShowPremiereDownload] = React.useState(false);
|
||||||
|
|
||||||
// Pull live counts so the tile subtitles ("34 assets", "0 live", "3 running")
|
// Pull live counts so the tile subtitles ("34 assets", "0 live", "3 running")
|
||||||
// reflect what's actually in the DB right now, not a stale boot-time cache.
|
// reflect what's actually in the DB right now, not a stale boot-time cache.
|
||||||
const [cards, setCards] = React.useState({});
|
const [cards, setCards] = React.useState({});
|
||||||
|
|
@ -62,12 +64,12 @@ function Home({ navigate }) {
|
||||||
desc: 'SDI · SRT · RTMP ingest. Start, stop, schedule.',
|
desc: 'SDI · SRT · RTMP ingest. Start, stop, schedule.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'editor',
|
id: '__premiere',
|
||||||
label: 'Editor',
|
label: 'Premiere panel',
|
||||||
icon: 'editor',
|
icon: 'editor',
|
||||||
tone: 'purple',
|
tone: 'purple',
|
||||||
sub: 'Beta',
|
sub: 'v' + ((window.PREMIERE_LATEST || {}).version || '·'),
|
||||||
desc: 'Timeline editor with cross-clip preview and render queue.',
|
desc: 'Download the Adobe Premiere Pro panel for frame-accurate editing.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'jobs',
|
id: 'jobs',
|
||||||
|
|
@ -125,7 +127,7 @@ function Home({ navigate }) {
|
||||||
<button
|
<button
|
||||||
key={t.id}
|
key={t.id}
|
||||||
className={'launcher-tile tone-' + t.tone}
|
className={'launcher-tile tone-' + t.tone}
|
||||||
onClick={() => navigate(t.id)}
|
onClick={() => t.id === '__premiere' ? setShowPremiereDownload(true) : navigate(t.id)}
|
||||||
>
|
>
|
||||||
<span className="launcher-tile-icon">
|
<span className="launcher-tile-icon">
|
||||||
<Icon name={t.icon} size={26} />
|
<Icon name={t.icon} size={26} />
|
||||||
|
|
@ -239,6 +241,78 @@ function Home({ navigate }) {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{showPremiereDownload && <PremiereDownloadModal onClose={() => setShowPremiereDownload(false)} />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modal listing all Premiere panel downloads (ZXP + Windows installer for
|
||||||
|
// each released version). Sourced from window.PREMIERE_RELEASES, written by
|
||||||
|
// the Settings → SDKs section in screens-admin.jsx.
|
||||||
|
function PremiereDownloadModal({ onClose }) {
|
||||||
|
const releases = (window.PREMIERE_RELEASES || []).slice().sort((a, b) => {
|
||||||
|
// Newest first; fall back to lexicographic compare on version string.
|
||||||
|
const av = String(a.version || ''), bv = String(b.version || '');
|
||||||
|
return bv.localeCompare(av, undefined, { numeric: true });
|
||||||
|
});
|
||||||
|
const latest = window.PREMIERE_LATEST || releases[0] || null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-backdrop" onClick={onClose}>
|
||||||
|
<div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 560 }}>
|
||||||
|
<div className="modal-head">
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: 15, fontWeight: 600 }}>Premiere panel</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--text-3)', marginTop: 2 }}>
|
||||||
|
Adobe Premiere Pro integration. Install one ZXP per workstation.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button className="icon-btn" aria-label="Close" onClick={onClose}><Icon name="x" /></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-body">
|
||||||
|
{releases.length === 0 && (
|
||||||
|
<div style={{ padding: '24px 0', textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
|
||||||
|
No releases registered yet. Upload one from Settings → Capture SDKs.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{releases.map((rel, i) => (
|
||||||
|
<div key={rel.version || i} className="premiere-release">
|
||||||
|
<div className="premiere-release-head">
|
||||||
|
<span className="premiere-release-version mono">v{rel.version}</span>
|
||||||
|
{latest && latest.version === rel.version && (
|
||||||
|
<span className="badge accent">LATEST</span>
|
||||||
|
)}
|
||||||
|
{rel.released_at && (
|
||||||
|
<span className="premiere-release-date mono">
|
||||||
|
{new Date(rel.released_at).toLocaleDateString()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{rel.notes && <div className="premiere-release-notes">{rel.notes}</div>}
|
||||||
|
<div className="premiere-release-actions">
|
||||||
|
{rel.zxp && (
|
||||||
|
<a href={rel.zxp} download className="btn primary sm">
|
||||||
|
<Icon name="download" />ZXP
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{rel.installer && (
|
||||||
|
<a href={rel.installer} download className="btn ghost sm">
|
||||||
|
<Icon name="download" />Windows installer
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-foot">
|
||||||
|
<div style={{ flex: 1, fontSize: 11.5, color: 'var(--text-3)' }}>
|
||||||
|
Need help installing? Use the Adobe Extension Manager or UPIA.
|
||||||
|
</div>
|
||||||
|
<button className="btn ghost" onClick={onClose}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ const NAV_SECTIONS = [
|
||||||
items: [
|
items: [
|
||||||
{ id: "capture", label: "Capture", icon: "capture" },
|
{ id: "capture", label: "Capture", icon: "capture" },
|
||||||
{ id: "jobs", label: "Jobs", icon: "jobs" },
|
{ id: "jobs", label: "Jobs", icon: "jobs" },
|
||||||
{ id: "editor", label: "Editor", icon: "editor", badge: { kind: 'neutral', text: 'BETA' } },
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -37,6 +36,7 @@ const NAV_SECTIONS = [
|
||||||
items: [
|
items: [
|
||||||
{ id: "users", label: "Users", icon: "users" },
|
{ id: "users", label: "Users", icon: "users" },
|
||||||
{ id: "tokens", label: "Tokens", icon: "token" },
|
{ id: "tokens", label: "Tokens", icon: "token" },
|
||||||
|
{ id: "billing", label: "Billing", icon: "token" },
|
||||||
{ id: "containers", label: "Containers", icon: "container" },
|
{ id: "containers", label: "Containers", icon: "container" },
|
||||||
{ id: "cluster", label: "Cluster", icon: "cluster" },
|
{ id: "cluster", label: "Cluster", icon: "cluster" },
|
||||||
{ id: "settings", label: "Settings", icon: "settings" },
|
{ id: "settings", label: "Settings", icon: "settings" },
|
||||||
|
|
@ -44,12 +44,9 @@ const NAV_SECTIONS = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Hidden routes: not in the sidebar but still reachable by direct nav.
|
// No hidden routes currently; Billing (the satirical pricing page) lives in
|
||||||
// `tokens-parody` is the old satirical pricing page (see issue #152). Real
|
// the Admin section above. Real API token management is at /tokens.
|
||||||
// API token management lives at /tokens (in the Admin section above).
|
const NAV_HIDDEN = [];
|
||||||
const NAV_HIDDEN = [
|
|
||||||
{ id: "tokens-parody", label: "Tokens (parody)", icon: "token" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Back-compat: NAV_TREE and ADMIN_TREE were used by other modules.
|
// Back-compat: NAV_TREE and ADMIN_TREE were used by other modules.
|
||||||
// NAV_FLAT is consumed by topbar search and the keyboard router.
|
// NAV_FLAT is consumed by topbar search and the keyboard router.
|
||||||
|
|
|
||||||
|
|
@ -1346,3 +1346,29 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
.job-row .job-row-cancel { color: var(--danger); }
|
.job-row .job-row-cancel { color: var(--danger); }
|
||||||
|
|
||||||
|
/* Premiere panel download modal (rows for each released version) */
|
||||||
|
.premiere-release {
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.premiere-release:last-child { border-bottom: 0; }
|
||||||
|
.premiere-release-head {
|
||||||
|
display: flex; align-items: center; gap: 8px;
|
||||||
|
}
|
||||||
|
.premiere-release-version {
|
||||||
|
font-size: 13px; font-weight: 600; color: var(--text-1);
|
||||||
|
}
|
||||||
|
.premiere-release-date {
|
||||||
|
font-size: 11px; color: var(--text-3);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.premiere-release-notes {
|
||||||
|
font-size: 12px; color: var(--text-2); line-height: 1.5;
|
||||||
|
}
|
||||||
|
.premiere-release-actions {
|
||||||
|
display: flex; gap: 8px; margin-top: 4px;
|
||||||
|
}
|
||||||
|
.premiere-release-actions a { text-decoration: none; }
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue