Skip to content

Commit

Permalink
generate script for release binary filenames when architecture cant b…
Browse files Browse the repository at this point in the history
…e inferred
  • Loading branch information
Adam McKee committed Aug 26, 2024
1 parent 98debfb commit c19fde5
Show file tree
Hide file tree
Showing 18 changed files with 190 additions and 94 deletions.
14 changes: 14 additions & 0 deletions frontend/src/components/configure/ArchitectureUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type {Architecture} from '@eighty4/install-template'

export const ARCHITECTURE_UPDATE_EVENT_TYPE = 'architecture'

export type ArchitectureUpdateEvent = CustomEvent<ArchitectureUpdate>

export interface ArchitectureUpdate {
arch: Architecture
filename: string
}

export function createArchitectureUpdate(arch: Architecture, filename: string): CustomEvent<ArchitectureUpdate> {
return new CustomEvent<ArchitectureUpdate>(ARCHITECTURE_UPDATE_EVENT_TYPE, {detail: {arch, filename}})
}
2 changes: 1 addition & 1 deletion frontend/src/components/configure/ConfigureBinaries.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.container {
margin: 2.5rem;
margin: 2.5rem 0;
}

.os {
Expand Down
30 changes: 24 additions & 6 deletions frontend/src/components/configure/ConfigureBinaries.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type {Binary} from '@eighty4/install-github'
import {type OperatingSystem, operatingSystemLabel} from '@eighty4/install-template'
import ConfigureBinary from './ConfigureBinary.ts'
import {
ARCHITECTURE_UPDATE_EVENT_TYPE,
type ArchitectureUpdateEvent,
createArchitectureUpdate,
} from './ArchitectureUpdate.ts'
import css from './ConfigureBinaries.css?inline'
import ConfigureBinary from './ConfigureBinary.ts'
import SystemLogo from '../SystemLogo.ts'
import {cloneTemplate, removeChildNodes} from '../../dom.ts'

Expand All @@ -26,6 +31,8 @@ export default class ConfigureBinaries extends HTMLElement {

readonly #bins: Array<Binary>

readonly #container: HTMLElement

readonly #shadow: ShadowRoot

constructor(bins: Array<Binary>, os: OperatingSystem) {
Expand All @@ -35,14 +42,25 @@ export default class ConfigureBinaries extends HTMLElement {
this.#shadow.appendChild(cloneTemplate(ConfigureBinaries.TEMPLATE_ID))
this.#shadow.querySelector('.os .logo')!.appendChild(new SystemLogo({os, color: '#111', size: '1.5rem'}))
this.#shadow.querySelector('.os')!.appendChild(document.createTextNode(`${operatingSystemLabel(os)} binaries`))
this.update()
this.#container = this.#shadow.querySelector('.bins')!
}

update() {
const container = this.#shadow.querySelector('.bins')!
removeChildNodes(container)
connectedCallback() {
for (const bin of this.#bins) {
container.appendChild(new ConfigureBinary(bin))
this.#container.appendChild(new ConfigureBinary(bin))
.addEventListener(ARCHITECTURE_UPDATE_EVENT_TYPE, this.#onArchUpdate as EventListener)
}
}

disconnectedCallback() {
for (const configureBinary of this.querySelectorAll('configure-binary')) {
configureBinary.removeEventListener(ARCHITECTURE_UPDATE_EVENT_TYPE, this.#onArchUpdate as EventListener)
}
removeChildNodes(this.#container)
}

#onArchUpdate = (e: ArchitectureUpdateEvent) => {
const {arch, filename} = e.detail
this.dispatchEvent(createArchitectureUpdate(arch, filename))
}
}
29 changes: 26 additions & 3 deletions frontend/src/components/configure/ConfigureBinary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type {Binary} from '@eighty4/install-github'
import {type Architecture, ARCHITECTURES} from '@eighty4/install-template'
import {createArchitectureUpdate} from './ArchitectureUpdate.ts'
import css from './ConfigureBinary.css?inline'
import {cloneTemplate} from '../../dom.ts'

Expand Down Expand Up @@ -27,11 +29,32 @@ export default class ConfigureBinary extends HTMLElement {
this.#bin = bin
this.#shadow = this.attachShadow({mode: 'open'})
this.#shadow.appendChild(cloneTemplate(ConfigureBinary.TEMPLATE_ID))
this.update()
}

update() {
connectedCallback() {
this.#shadow.querySelector('.filename')!.textContent = this.#bin.filename
this.#shadow.querySelector('.arch')!.textContent = this.#bin.arch || '?'
const archContainer = this.#shadow.querySelector('.arch')!
if (this.#bin.arch) {
archContainer.textContent = this.#bin.arch
} else {
archContainer.replaceWith(this.#createArchSelect())
}
}

#createArchSelect(): HTMLSelectElement {
const archSelect = document.createElement('select')
archSelect.innerHTML = '<option value=""></option>' + ARCHITECTURES.map(arch => `<option>${arch}</option>`).join()
archSelect.addEventListener('input', this.#onArchUpdate)
return archSelect
}

disconnectedCallback() {
this.#shadow.querySelector('select')?.removeEventListener('change', this.#onArchUpdate)
}

#onArchUpdate = () => {
this.#shadow.querySelector('option[value=""]')?.remove()
const arch = this.#shadow.querySelector('select')!.value as Architecture
this.dispatchEvent(createArchitectureUpdate(arch, this.#bin.filename))
}
}
16 changes: 6 additions & 10 deletions frontend/src/components/configure/ConfigureScript.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.header {
width: 45vw;
}

.row {
display: flex;
gap: .75rem;
Expand All @@ -11,7 +7,7 @@

.name {
flex: 1;
font-size: 1.2rem;
font-size: 1.5rem;
font-weight: 600;
}

Expand All @@ -25,7 +21,11 @@
}

.buttons {
margin-left: 2.75rem;
display: flex;
justify-content: center;
align-items: center;
height: 5rem;
gap: 2rem;
}

.download {
Expand All @@ -47,10 +47,6 @@
color: #687;
}

.download + .download {
margin-left: 1.5rem;
}

.download svg {
margin-right: .25rem;
}
122 changes: 91 additions & 31 deletions frontend/src/components/configure/ConfigureScript.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import type {Binary, Repository} from '@eighty4/install-github'
import type {Distribution, GenerateScriptOptions, OperatingSystem} from '@eighty4/install-template'
import {
type Architecture,
type Distribution,
type GenerateScriptOptions,
type OperatingSystem,
} from '@eighty4/install-template'
import {ARCHITECTURE_UPDATE_EVENT_TYPE, type ArchitectureUpdateEvent} from './ArchitectureUpdate.ts'
import ConfigureBinaries from './ConfigureBinaries.ts'
import css from './ConfigureScript.css?inline'
import {cloneTemplate} from '../../dom.ts'
import {cloneTemplate, removeChildNodes} from '../../dom.ts'
import {downloadScript} from '../../download.ts'

// todo links to gh commit, repo and release pages
// todo release date
// todo repo languages
export default class ConfigureScript extends HTMLElement {

private static readonly TEMPLATE_ID = 'tmpl-configure-script'
Expand All @@ -15,7 +24,6 @@ export default class ConfigureScript extends HTMLElement {
<style>${css}</style>
<div class="header">
<div class="row">
<profile-picture></profile-picture>
<span class="name"></span>
<span class="version"></span>
</div>
Expand All @@ -32,6 +40,10 @@ export default class ConfigureScript extends HTMLElement {
`
}

readonly #architectureResolved: Record<string, Architecture> = {}

readonly #architectureUnresolved: Record<string, OperatingSystem> = {}

readonly #binaries: Record<OperatingSystem, Array<Binary>> = {
Linux: [],
MacOS: [],
Expand All @@ -49,12 +61,14 @@ export default class ConfigureScript extends HTMLElement {
if (repo.latestRelease?.binaries) {
for (const binary of repo.latestRelease.binaries!) {
this.#binaries[binary.os].push(binary)
if (!binary.arch) {
this.#architectureUnresolved[binary.filename] = binary.os
}
}
}
this.#repo = repo
this.#shadow = this.attachShadow({mode: 'open'})
this.#shadow.appendChild(cloneTemplate(ConfigureScript.TEMPLATE_ID))
this.#shadow.querySelector('profile-picture')!.setAttribute('owner', repo.owner)
if (this.#binaries['Linux'].length || this.#binaries['MacOS'].length) {
this.#downloadButtons['Linux'] = this.#downloadButtons['MacOS'] = this.#shadow.querySelector('.buttons')!
.appendChild(this.#createDownloadButton('Linux'))
Expand All @@ -63,35 +77,44 @@ export default class ConfigureScript extends HTMLElement {
this.#downloadButtons['Windows'] = this.#shadow.querySelector('.buttons')!
.appendChild(this.#createDownloadButton('Windows'))
}
this.update()
this.#render()
}

disconnectedCallback() {
for (const os of Object.keys(this.#downloadButtons)) {
this.#downloadButtons[os as OperatingSystem]!.removeEventListener('click', this.#onDownloadButtonClick)
}
for (const configureBinaries of this.querySelectorAll('configure-binaries')) {
configureBinaries.removeEventListener(ARCHITECTURE_UPDATE_EVENT_TYPE, this.#onArchUpdate as EventListener)
}
removeChildNodes(this.#shadow)
}

update() {
#render() {
this.#shadow.querySelector('.commit')!.textContent = this.#repo.latestRelease?.commitHash || ''
this.#shadow.querySelector<HTMLElement>('.header')!.style.viewTransitionName = `repo-${this.#repo.owner}-${this.#repo.name}`
this.#shadow.querySelector('.name')!.textContent = `${this.#repo.owner}/${this.#repo.name}`
this.#shadow.querySelector('.version')!.textContent = this.#repo.latestRelease?.tag || ''
const binariesContainer = this.#shadow.querySelector('.files') as HTMLElement
if (this.#repo.latestRelease?.binaries.length) {
if (this.#binaries['Linux'].length) {
binariesContainer.appendChild(new ConfigureBinaries(this.#binaries['Linux'], 'Linux'))
}
if (this.#binaries['MacOS'].length) {
binariesContainer.appendChild(new ConfigureBinaries(this.#binaries['MacOS'], 'MacOS'))
}
if (this.#binaries['Windows'].length) {
binariesContainer.appendChild(new ConfigureBinaries(this.#binaries['Windows'], 'Windows'))
}
this.#renderConfigureBinaries(binariesContainer, 'Linux')
this.#renderConfigureBinaries(binariesContainer, 'MacOS')
this.#renderConfigureBinaries(binariesContainer, 'Windows')
} else {
// todo will somebody think of the lack of content for repos without binaries?
}
// todo nice to have render non binary file assets
console.log('ConfigureScript', this.#repo.latestRelease?.binaries)
}

#renderConfigureBinaries(container: HTMLElement, os: OperatingSystem) {
const bins = this.#binaries[os]
if (bins.length) {
container.appendChild(new ConfigureBinaries(bins, os))
.addEventListener(ARCHITECTURE_UPDATE_EVENT_TYPE, this.#onArchUpdate as EventListener)
}
}

#createDownloadButton(os: OperatingSystem): HTMLButtonElement {
const downloadButton = document.createElement('button')
downloadButton.classList.add('download')
Expand All @@ -105,38 +128,75 @@ export default class ConfigureScript extends HTMLElement {
</svg>`
downloadButton.appendChild(document.createTextNode(`for ${os === 'Windows' ? 'Windows' : 'Linux and MacOS'}`))
downloadButton.addEventListener('click', this.#onDownloadButtonClick)
downloadButton.setAttribute('data-os', os)
downloadButton.disabled = !this.#isDownloadButtonEnabled(os)
return downloadButton
}

#isDownloadButtonEnabled(os: OperatingSystem): boolean {
if (os === 'Windows') {
downloadButton.disabled = true
return false
} else {
for (const filename of Object.keys(this.#architectureUnresolved)) {
if (this.#architectureUnresolved[filename] !== 'Windows') {
return false
}
}
return true
}
}

#updateDownloadButtonEnabled(os: OperatingSystem) {
this.#downloadButtons[os]!.disabled = !(os === 'Windows'
? this.#isDownloadButtonEnabled(os)
: (['Linux', 'MacOS'] as Array<OperatingSystem>).every(os => this.#isDownloadButtonEnabled(os)))
}

#onArchUpdate = (e: ArchitectureUpdateEvent) => {
this.#architectureResolved[e.detail.filename] = e.detail.arch
const resolvedBinOs = this.#architectureUnresolved[e.detail.filename]
if (resolvedBinOs) {
delete this.#architectureUnresolved[e.detail.filename]
this.#updateDownloadButtonEnabled(resolvedBinOs)
}
return downloadButton
}

#onDownloadButtonClick = (/*e: MouseEvent*/) => {
// (e.target as HTMLButtonElement).getAttribute('data-os')
// todo support windows
downloadScript(this.#buildGenerateScriptOptions())
// todo save generated script and template version to db
// todo save resolved architecture values to db
}

// todo binaryName needs a ui input to override default of repo name
#buildGenerateScriptOptions(): GenerateScriptOptions {
return {
binaryName: this.#repo.name,
files: this.#collectBinaryDistributions(),
repository: {
owner: this.#repo.owner,
name: this.#repo.name,
},
}
}

#collectBinaryDistributions(): Record<string, Distribution> {
const files: Record<string, Distribution> = {}
for (const os of Object.keys(this.#binaries)) {
for (const binary of this.#binaries[os as OperatingSystem]) {
if (binary.arch) {
files[binary.filename] = {
arch: binary.arch,
os: binary.os,
}
files[binary.filename] = {
arch: binary.arch || this.#expectResolvedArchitecture(binary.filename),
os: binary.os,
}
}
}
return {
repository: {
owner: this.#repo.owner,
name: this.#repo.name,
},
binaryName: this.#repo.name,
files,
return files
}

#expectResolvedArchitecture(filename: string): Architecture | never {
const arch = this.#architectureResolved[filename]
if (!arch) {
throw new Error('expected resolved architecture for ' + filename)
}
return arch
}
}
7 changes: 6 additions & 1 deletion frontend/src/components/search/RepositoryLink.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
padding: 1rem;
box-sizing: border-box;
cursor: pointer;
transform-origin: center;
transition: all .3s ease-in-out;
}

Expand All @@ -18,7 +19,11 @@
border-radius: 15px;
padding: 1rem 1.5rem;
transform: scale(.9);
transform-origin: center;
}

.link:active {
transform: scale(.85) translateY(3px);
transition: all .075s ease-in-out;
}

.deets, .header {
Expand Down
Loading

0 comments on commit c19fde5

Please sign in to comment.