Add tabs to separate apps and tool repos
This commit is contained in:
parent
946cdb1794
commit
267e4e59f7
|
|
@ -15,26 +15,6 @@ const TOES_URL = process.env.TOES_URL!
|
|||
const REPOS_DIR = resolve(DATA_ROOT, 'repos')
|
||||
const VISIBILITY_PATH = join(DATA_DIR, 'visibility.json')
|
||||
|
||||
const TOGGLE_SCRIPT = `
|
||||
function toggleVisibility(btn) {
|
||||
var repo = btn.dataset.repo;
|
||||
var current = btn.dataset.visibility;
|
||||
var next = current === 'public' ? 'private' : 'public';
|
||||
btn.dataset.visibility = next;
|
||||
btn.textContent = next;
|
||||
btn.classList.toggle('public', next === 'public');
|
||||
fetch('/api/visibility/' + encodeURIComponent(repo), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ visibility: next })
|
||||
}).catch(function() {
|
||||
btn.dataset.visibility = current;
|
||||
btn.textContent = current;
|
||||
btn.classList.toggle('public', current === 'public');
|
||||
});
|
||||
}
|
||||
`
|
||||
|
||||
const app = new Hype({ prettyHTML: false, layout: false })
|
||||
const deployLocks = new Map<string, Promise<void>>()
|
||||
|
||||
|
|
@ -113,6 +93,31 @@ const RepoName = define('RepoName', {
|
|||
color: theme('colors-text'),
|
||||
})
|
||||
|
||||
const Tab = define('Tab', {
|
||||
base: 'button',
|
||||
padding: '6px 0',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
borderBottom: '2px solid transparent',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
color: theme('colors-textMuted'),
|
||||
states: {
|
||||
':hover': { color: theme('colors-text') },
|
||||
'.active': {
|
||||
color: theme('colors-text'),
|
||||
borderBottomColor: theme('colors-primary'),
|
||||
fontWeight: '500',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const TabBar = define('TabBar', {
|
||||
display: 'flex',
|
||||
gap: '24px',
|
||||
marginBottom: '20px',
|
||||
})
|
||||
|
||||
const Toggle = define('Toggle', {
|
||||
base: 'button',
|
||||
display: 'inline-flex',
|
||||
|
|
@ -156,7 +161,7 @@ interface LayoutProps {
|
|||
interface RepoListPageProps {
|
||||
baseUrl: string
|
||||
external: boolean
|
||||
repos: Array<{ name: string; commits: boolean; branch: string; visibility: Visibility }>
|
||||
repos: Array<{ name: string; commits: boolean; branch: string; visibility: Visibility; tool: boolean }>
|
||||
tunnelUrl?: string
|
||||
}
|
||||
|
||||
|
|
@ -544,31 +549,16 @@ function AppRepo({ appName, baseUrl, branch, exists, commits }: AppRepoProps) {
|
|||
)
|
||||
}
|
||||
|
||||
function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps) {
|
||||
function RepoListItems({ baseUrl, external, repos, tunnelUrl }: {
|
||||
baseUrl: string
|
||||
external: boolean
|
||||
repos: RepoListPageProps['repos']
|
||||
tunnelUrl?: string
|
||||
}) {
|
||||
if (repos.length === 0) {
|
||||
return <HelpText>No repositories yet.</HelpText>
|
||||
}
|
||||
return (
|
||||
<Layout title="Git">
|
||||
{!external && (
|
||||
<>
|
||||
<Heading>Push to Deploy</Heading>
|
||||
<HelpText>
|
||||
Push a git repository to deploy it as a toes app.
|
||||
The repo must contain a <code>package.json</code> with a <code>scripts.toes</code> entry.
|
||||
</HelpText>
|
||||
|
||||
<CodeBlock>{[
|
||||
'# Add this server as a remote and push',
|
||||
`git remote add toes ${baseUrl}/<app-name>`,
|
||||
'git push toes main',
|
||||
'',
|
||||
'# Or push an existing repo',
|
||||
`git push ${baseUrl}/<app-name> main`,
|
||||
].join('\n')}</CodeBlock>
|
||||
</>
|
||||
)}
|
||||
|
||||
{repos.length > 0 && (
|
||||
<>
|
||||
<Heading>Repositories</Heading>
|
||||
<RepoList>
|
||||
{repos.map(({ name, commits, branch, visibility }) => (
|
||||
<RepoItem>
|
||||
|
|
@ -602,7 +592,51 @@ function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps
|
|||
</RepoItem>
|
||||
))}
|
||||
</RepoList>
|
||||
{!external && <script src="/toggle.js" />}
|
||||
)
|
||||
}
|
||||
|
||||
function RepoListPage({ baseUrl, external, repos, tunnelUrl }: RepoListPageProps) {
|
||||
const appRepos = repos.filter(r => !r.tool)
|
||||
const toolRepos = repos.filter(r => r.tool)
|
||||
|
||||
return (
|
||||
<Layout title="Git">
|
||||
{!external && (
|
||||
<>
|
||||
<Heading>Push to Deploy</Heading>
|
||||
<HelpText>
|
||||
Push a git repository to deploy it as a toes app.
|
||||
The repo must contain a <code>package.json</code> with a <code>scripts.toes</code> entry.
|
||||
</HelpText>
|
||||
|
||||
<CodeBlock>{[
|
||||
'# Add this server as a remote and push',
|
||||
`git remote add toes ${baseUrl}/<app-name>`,
|
||||
'git push toes main',
|
||||
'',
|
||||
'# Or push an existing repo',
|
||||
`git push ${baseUrl}/<app-name> main`,
|
||||
].join('\n')}</CodeBlock>
|
||||
</>
|
||||
)}
|
||||
|
||||
{repos.length > 0 && (
|
||||
<>
|
||||
<Heading>Repositories</Heading>
|
||||
<TabBar>
|
||||
<Tab class="active" data-tab="tab-apps" onclick="switchTab(this)">Apps</Tab>
|
||||
<Tab data-tab="tab-tools" onclick="switchTab(this)">Tools</Tab>
|
||||
</TabBar>
|
||||
<div>
|
||||
<div id="tab-apps">
|
||||
<RepoListItems baseUrl={baseUrl} external={external} repos={appRepos} tunnelUrl={tunnelUrl} />
|
||||
</div>
|
||||
<div id="tab-tools" style="display: none">
|
||||
<RepoListItems baseUrl={baseUrl} external={external} repos={toolRepos} tunnelUrl={tunnelUrl} />
|
||||
</div>
|
||||
</div>
|
||||
{!external && <script src="/client/toggle.js" />}
|
||||
<script src="/client/tabs.js" />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
@ -630,9 +664,6 @@ app.get('/styles.css', c =>
|
|||
c.text(baseStyles + stylesToCSS(), 200, { 'Content-Type': 'text/css; charset=utf-8' }),
|
||||
)
|
||||
|
||||
app.get('/toggle.js', c =>
|
||||
c.text(TOGGLE_SCRIPT, 200, { 'Content-Type': 'application/javascript; charset=utf-8' }),
|
||||
)
|
||||
|
||||
// GET /:repo[.git]/info/refs?service=git-upload-pack|git-receive-pack
|
||||
app.on('GET', ['/:repo{.+\\.git}/info/refs', '/:repo/info/refs'], async c => {
|
||||
|
|
@ -780,6 +811,19 @@ app.get('/', async c => {
|
|||
|
||||
// No app selected — show all repos
|
||||
const repos = await listRepos()
|
||||
|
||||
// Fetch all apps to determine which repos are tools
|
||||
let toolSet = new Set<string>()
|
||||
try {
|
||||
const res = await fetch(`${TOES_URL}/api/apps`)
|
||||
if (res.ok) {
|
||||
const apps = await res.json() as Array<{ name: string; tool?: boolean | string }>
|
||||
for (const a of apps) {
|
||||
if (a.tool) toolSet.add(a.name)
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
const repoData = await Promise.all(repos.map(async name => {
|
||||
const bare = repoPath(name)
|
||||
const [commits, branch, visibility] = await Promise.all([
|
||||
|
|
@ -787,7 +831,7 @@ app.get('/', async c => {
|
|||
getDefaultBranch(bare),
|
||||
getVisibility(name),
|
||||
])
|
||||
return { name, commits, branch, visibility }
|
||||
return { name, commits, branch, visibility, tool: toolSet.has(name) }
|
||||
}))
|
||||
|
||||
// Hide private repos from external (sneaker) requests
|
||||
|
|
|
|||
13
apps/git/src/client/tabs.ts
Normal file
13
apps/git/src/client/tabs.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
function switchTab(btn: HTMLButtonElement) {
|
||||
const tabs = btn.parentElement!.querySelectorAll('button')
|
||||
for (const tab of tabs) tab.classList.remove('active')
|
||||
btn.classList.add('active')
|
||||
|
||||
const panels = btn.parentElement!.nextElementSibling!.children
|
||||
for (const panel of panels) (panel as HTMLElement).style.display = 'none'
|
||||
|
||||
const target = document.getElementById(btn.dataset.tab!)
|
||||
if (target) target.style.display = 'block'
|
||||
}
|
||||
|
||||
Object.assign(window, { switchTab })
|
||||
19
apps/git/src/client/toggle.ts
Normal file
19
apps/git/src/client/toggle.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
function toggleVisibility(btn: HTMLButtonElement) {
|
||||
const repo = btn.dataset.repo!
|
||||
const current = btn.dataset.visibility!
|
||||
const next = current === 'public' ? 'private' : 'public'
|
||||
btn.dataset.visibility = next
|
||||
btn.textContent = next
|
||||
btn.classList.toggle('public', next === 'public')
|
||||
fetch('/api/visibility/' + encodeURIComponent(repo), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ visibility: next }),
|
||||
}).catch(() => {
|
||||
btn.dataset.visibility = current
|
||||
btn.textContent = current
|
||||
btn.classList.toggle('public', current === 'public')
|
||||
})
|
||||
}
|
||||
|
||||
Object.assign(window, { toggleVisibility })
|
||||
Loading…
Reference in New Issue
Block a user