diff --git a/src/less/components/commands.less b/src/less/components/commands.less index 9f2b576fa5..68f220a072 100644 --- a/src/less/components/commands.less +++ b/src/less/components/commands.less @@ -8,12 +8,12 @@ header { background-color: @background-3; -webkit-app-region: drag; - #version-chooser .bp3-button-text::before { + #version-chooser:not([data-local]) .bp3-button-text::before { content: 'Electron v'; } @media (max-width: 980px) { - #version-chooser .bp3-button-text::before { + #version-chooser:not([data-local]) .bp3-button-text::before { content: 'v'; } } diff --git a/src/less/components/settings-electron.less b/src/less/components/settings-electron.less index 46fcc6dcf4..d2c11d1798 100644 --- a/src/less/components/settings-electron.less +++ b/src/less/components/settings-electron.less @@ -11,6 +11,33 @@ overflow: hidden; background-color: @background-1; + .electron-versions-section-header { + padding: 8px 15px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: @text-color-2; + background-color: @background-2; + border-bottom: 1px solid @border-color-1; + border-top: 1px solid @border-color-1; + display: flex; + align-items: center; + gap: 6px; + + &:first-child { + border-top: none; + } + + &.local { + border-left: 3px solid @green4; + } + + &.remote { + border-left: 3px solid @blue4; + } + } + .electron-versions-header { display: flex; font-weight: 600; @@ -62,6 +89,14 @@ background-color: rgba(92, 112, 128, 0.15); } + &.local { + border-left: 3px solid @green4; + } + + &.remote { + border-left: 3px solid @blue4; + } + .version-col { flex: 1; padding: 0 15px; @@ -103,6 +138,13 @@ color: @foreground-1; } + .electron-versions-section-header { + background-color: rgba(255, 255, 255, 0.06); + border-bottom-color: @dark-gray4; + border-top-color: @dark-gray4; + color: rgba(255, 255, 255, 0.7); + } + .electron-versions-header { background-color: @dark-gray5; border-bottom-color: @dark-gray4; diff --git a/src/renderer/components/dialog-add-version.tsx b/src/renderer/components/dialog-add-version.tsx index 120b9d7be1..682299a315 100644 --- a/src/renderer/components/dialog-add-version.tsx +++ b/src/renderer/components/dialog-add-version.tsx @@ -9,7 +9,6 @@ import { Intent, } from '@blueprintjs/core'; import { observer } from 'mobx-react'; -import * as semver from 'semver'; import { Version } from '../../interfaces'; import { AppState } from '../state'; @@ -22,11 +21,20 @@ interface AddVersionDialogProps { interface AddVersionDialogState { isValidElectron: boolean; - isValidVersion: boolean; + isValidName: boolean; existingLocalVersion?: Version; folderPath?: string; localName?: string; - version: string; + name: string; +} + +/** + * Generate a unique version key for a local build. + * Uses a format that is valid semver but can never conflict + * with a real Electron release. + */ +function generateLocalVersionKey(): string { + return `0.0.0-local.${Date.now()}`; } /** @@ -41,14 +49,14 @@ export const AddVersionDialog = observer( super(props); this.state = { - isValidVersion: false, + isValidName: false, isValidElectron: false, - version: '', + name: '', }; this.onSubmit = this.onSubmit.bind(this); this.onClose = this.onClose.bind(this); - this.onChangeVersion = this.onChangeVersion.bind(this); + this.onChangeName = this.onChangeName.bind(this); } /** @@ -65,20 +73,23 @@ export const AddVersionDialog = observer( folderPath, isValidElectron, localName, + // Pre-fill name from detected binary name if available + name: localName || '', + isValidName: !!localName, }); } } /** - * Handles a change of the file input + * Handles a change of the name input */ - public onChangeVersion(event: React.ChangeEvent) { - const version = event.target.value || ''; - const isValidVersion = !!semver.valid(version); + public onChangeName(event: React.ChangeEvent) { + const name = event.target.value || ''; + const isValidName = name.trim().length > 0; this.setState({ - version, - isValidVersion, + name, + isValidName, }); } @@ -86,27 +97,21 @@ export const AddVersionDialog = observer( * Handles the submission of the dialog */ public async onSubmit(): Promise { - const { - folderPath, - version, - isValidElectron, - existingLocalVersion, - localName, - } = this.state; + const { folderPath, name, isValidElectron, existingLocalVersion } = + this.state; if (!folderPath) return; - const toAdd: Version = { - localPath: folderPath, - version, - name: localName, - }; - // swap to old local electron version if the user adds a new one with the same path if (isValidElectron && existingLocalVersion?.localPath) { // set previous version as active version this.props.appState.setVersion(existingLocalVersion.version); } else { + const toAdd: Version = { + localPath: folderPath, + version: generateLocalVersionKey(), + name: name.trim(), + }; this.props.appState.addLocalVersion(toAdd); } this.onClose(); @@ -121,9 +126,8 @@ export const AddVersionDialog = observer( } get buttons() { - const { isValidElectron, isValidVersion, existingLocalVersion } = - this.state; - const canAdd = isValidElectron && isValidVersion && !existingLocalVersion; + const { isValidElectron, isValidName, existingLocalVersion } = this.state; + const canAdd = isValidElectron && isValidName && !existingLocalVersion; const canSwitch = isValidElectron && existingLocalVersion; return [ @@ -198,9 +202,9 @@ export const AddVersionDialog = observer( const canSwitch = isValidElectron && existingLocalVersion; if (canSwitch) - return `This folder is already in use as version "${ - existingLocalVersion!.version - }". Would you like to switch to that version now?`; + return `This folder is already in use as "${ + existingLocalVersion.name ?? existingLocalVersion.version + }". Would you like to switch to that local build now?`; if (isValidElectron) return `We found an ${getElectronNameForPlatform()} in this folder.`; @@ -209,20 +213,20 @@ export const AddVersionDialog = observer( } private renderVersionInput(): JSX.Element | null { - const { isValidElectron, isValidVersion, version } = this.state; + const { isValidElectron, isValidName, name } = this.state; if (!isValidElectron) return null; return ( <>

- Please specify a version, used for typings and the name. Must be{' '} - semver compliant. + Give this local build a name so you can identify it in the version + list.

); @@ -234,8 +238,8 @@ export const AddVersionDialog = observer( private reset(): void { this.setState({ isValidElectron: false, - isValidVersion: false, - version: '', + isValidName: false, + name: '', folderPath: undefined, localName: undefined, }); diff --git a/src/renderer/components/history.tsx b/src/renderer/components/history.tsx index 5a0817e358..26b96d7e8f 100644 --- a/src/renderer/components/history.tsx +++ b/src/renderer/components/history.tsx @@ -145,7 +145,7 @@ export class GistHistoryDialog extends React.Component< ); } - private renderRevisionItem = (revision: GistRevision, index: number) => { + private renderRevisionItem = (revision: GistRevision) => { const date = new Date(revision.date).toLocaleString(); const shortSha = revision.sha.substring(0, 7); const isActive = this.props.activeRevision === revision.sha; diff --git a/src/renderer/components/settings-electron.tsx b/src/renderer/components/settings-electron.tsx index 87fa84f1c6..6368d590ea 100644 --- a/src/renderer/components/settings-electron.tsx +++ b/src/renderer/components/settings-electron.tsx @@ -71,7 +71,7 @@ const ElectronVersionRow = observer(({ index, style, data }: RowProps) => { }; const renderAction = (ver: RunnableVersion): JSX.Element => { - const { state, source, version } = ver; + const { name, state, source, version } = ver; const isLocal = source === VersionSource.local; const buttonProps: ButtonProps = { small: true, @@ -109,7 +109,7 @@ const ElectronVersionRow = observer(({ index, style, data }: RowProps) => { { /> ); - } else if (disableDownload(version)) { + } else if (!isLocal && disableDownload(version)) { return ( { return