diff --git a/src/interfaces.ts b/src/interfaces.ts index 5a5094ac39..dd273da6d4 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -215,6 +215,17 @@ export interface PMOperationOptions { packageManager: IPackageManager; } +export interface GistRevision { + sha: string; + date: string; + title: string; + changes: { + deletions: number; + additions: number; + total: number; + }; +} + export enum GlobalSetting { acceleratorsToBlock = 'acceleratorsToBlock', channelsToShow = 'channelsToShow', @@ -236,6 +247,7 @@ export enum GlobalSetting { knownVersion = 'known-electron-versions', localVersion = 'local-electron-versions', packageAuthor = 'packageAuthor', + isShowingGistHistory = 'isShowingGistHistory', packageManager = 'packageManager', showObsoleteVersions = 'showObsoleteVersions', showUndownloadedVersions = 'showUndownloadedVersions', diff --git a/src/less/components/history.less b/src/less/components/history.less new file mode 100644 index 0000000000..bff838ac67 --- /dev/null +++ b/src/less/components/history.less @@ -0,0 +1,76 @@ +.revision-list { + max-height: 400px; + overflow-y: auto; +} + +.revision-list ul { + list-style-type: none; + padding: 0; + margin: 0; +} + +.revision-item { + padding: 10px; + margin-bottom: 5px; + border-radius: 3px; + cursor: pointer; + transition: background-color 0.2s ease; + border-left: 3px solid #106ba3; +} + +.revision-item:hover { + background-color: rgba(167, 182, 194, 0.3); +} + +.revision-item.active { + background-color: rgba(16, 107, 163, 0.15); + border-left-color: #48aff0; +} + +.revision-item.active:hover { + background-color: rgba(16, 107, 163, 0.25); +} + +.active-tag { + margin-left: 10px; +} + +.revision-content { + display: flex; + flex-direction: column; +} + +.revision-icon { + margin-right: 8px; +} + +.sha-label { + font-size: 12px; + color: #738694; + margin-left: 10px; + font-family: monospace; +} + +.revision-details { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 5px; +} + +.revision-date { + color: #738694; + font-size: 12px; +} + +.revision-changes { + display: flex; + gap: 5px; +} + +.history-loading { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; +} diff --git a/src/less/root.less b/src/less/root.less index e980ccc204..298ff7ccfa 100644 --- a/src/less/root.less +++ b/src/less/root.less @@ -10,6 +10,7 @@ // Components @import 'components/commands.less'; @import 'components/output.less'; +@import 'components/history.less'; @import 'components/dialogs.less'; @import 'components/mosaic.less'; @import 'components/settings.less'; diff --git a/src/renderer/app.tsx b/src/renderer/app.tsx index cbb28d2dda..54d31f2321 100644 --- a/src/renderer/app.tsx +++ b/src/renderer/app.tsx @@ -82,6 +82,7 @@ export class App { ); this.state.gistId = gistId || ''; + this.state.activeGistRevision = undefined; this.state.localPath = localFiddle?.filePath; this.state.templateName = templateName; diff --git a/src/renderer/components/commands-action-button.tsx b/src/renderer/components/commands-action-button.tsx index 4efc0d1974..9c3e976dde 100644 --- a/src/renderer/components/commands-action-button.tsx +++ b/src/renderer/components/commands-action-button.tsx @@ -133,6 +133,7 @@ export const GistActionButton = observer( }); appState.gistId = gist.data.id; + appState.activeGistRevision = undefined; appState.localPath = undefined; if (appState.isPublishingGistAsRevision) { @@ -270,6 +271,7 @@ export const GistActionButton = observer( } appState.gistId = undefined; + appState.activeGistRevision = undefined; appState.activeGistAction = GistActionState.none; this.setActionType(GistActionType.publish); } diff --git a/src/renderer/components/commands.tsx b/src/renderer/components/commands.tsx index 86292d2b38..d3cde2cbb0 100644 --- a/src/renderer/components/commands.tsx +++ b/src/renderer/components/commands.tsx @@ -9,6 +9,7 @@ import { AddressBar } from './commands-address-bar'; import { BisectHandler } from './commands-bisect'; import { Runner } from './commands-runner'; import { VersionChooser } from './commands-version-chooser'; +import { HistoryWrapper } from './history-wrapper'; import { AppState } from '../state'; interface CommandsProps { @@ -75,7 +76,14 @@ export const Commands = observer(
{title}
) : undefined}
- + + + + {appState.isShowingGistHistory && ( + + + + )}
diff --git a/src/renderer/components/history-wrapper.tsx b/src/renderer/components/history-wrapper.tsx new file mode 100644 index 0000000000..c0089d48b8 --- /dev/null +++ b/src/renderer/components/history-wrapper.tsx @@ -0,0 +1,79 @@ +import * as React from 'react'; + +import { Button } from '@blueprintjs/core'; +import { observer } from 'mobx-react'; + +import { GistHistoryDialog } from './history'; +import { AppState } from '../state'; + +interface HistoryWrapperProps { + appState: AppState; + buttonOnly?: boolean; + className?: string; +} + +/** + * A component that observes the appState and manages the history dialog. + * Can be rendered as just a button or as a button with a dialog. + */ +@observer +export class HistoryWrapper extends React.Component { + private toggleHistory = () => { + const { appState } = this.props; + appState.toggleHistory(); + }; + + private handleRevisionSelect = async (revisionId: string) => { + const { remoteLoader } = window.app; + try { + await remoteLoader.fetchGistAndLoad( + this.props.appState.gistId!, + revisionId, + ); + } catch (error: any) { + console.error('Failed to load revision', error); + this.props.appState.showErrorDialog( + `Failed to load revision: ${error.message || 'Unknown error'}`, + ); + } + }; + + public renderHistoryButton() { + const { className } = this.props; + + return ( +