Skip to content

Commit

Permalink
feat(patch-detail): show patch major events, metadata, title, descr
Browse files Browse the repository at this point in the history
Signed-off-by: Konstantinos Maninakis <maninak@protonmail.com>
  • Loading branch information
maninak committed Jan 5, 2024
1 parent bf6eba6 commit 6ff5d8d
Show file tree
Hide file tree
Showing 22 changed files with 333 additions and 112 deletions.
17 changes: 14 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@

### 🚀 Enhancements

- **patch-detail:** implement new Patch Detail webview showing detailed info about a Patch
- **patch-detail:** implement new Patch Detail webview showing in-depth info for a specific Patch
- can be opened via a new button "View Patch Details" on each item in the list of Patches
- panel's title shows the patch description in full if it's small, otherwise truncated to the nearest full word
- panel's title shows the patch description in full if it's short, otherwise truncated to the nearest full word
- the following Patch info are shown in the new view
- status
- major events like "created", "last updated", "merged" and related info with logic crafting optimal copy for each case (see similar tooltip improvements below)
- id (with on-hover button to copy Patch identifier to clipboard)
- revision authors
- labels
- title
- description
- where applicable the above have on-hover indicators hinting that they come with a tooltip showing addtional info such as author's DID, full Id in case it's shortened or full localised time in case it's a "time-ago"
- **patch-list:** auto-retry fetching list of Patches from httpd (with geometric backoff) if an error occured
- **patch-list:** Patch tooltip improvements
- show merge revision id and commit hash (if not already shown in revision event's copy) for merged Patches
Expand All @@ -16,7 +25,8 @@
- only "time-ago" is shown now; the full date is still available in the Patch Details view
- use custom "time-ago" logic producing more informative results with fewer collisions e.g. "35 days ago" instead of "1 month ago"
- **patch-list:** move button for command "Copy Patch Identifier to Clipboard" into Patch item's context menu
- **patch-list:** use smaller dot separator between data in the description of a Patch list item
- **patch-list:** use smaller dot as separator between data in the description of a Patch item
- the initial size of the Patches view (e.g. for new projects) will now be 4x that of the CLI Commands view, instead of having the area allocation split 50:50 resulting in wasted empty space allocated to the later view while the former may have the need for more area to show more content. Subsequent adjustments by the user will be respected and not get overwritten by the initial size.

### 🏡 Chores

Expand All @@ -28,6 +38,7 @@
- auto-save Webview state and auto-restore it if VS Code is restarted with the Webview panel open
- each new Webview panel opens in the currently active ViewColumn, if multi-column layout is in use (i.e. split editors)
- Webview panel gets reused without being destroyed if it is re-invoked when the user has a ViewColumn active which isn't the one already containing the running Webview
- text content in Webviews can be searched with Ctrl + F and additional actions Copy/Paste/Cut are available on right click or by using their common keyboard shortcuts
- Webviews are secured with strict Content Security Policy (CSP)

-----
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
"build": "run-p -l build:*",
"build:extension": "npm run compile:extension -- --minify",
"build:webviews": "cd ./src/webviews && npm run build",
"dev": "run-p dev:*",
"dev:extension": "npm run compile:extension -- --sourcemap --watch",
"dev": "run-s dev:*",
"dev:webviews": "cd ./src/webviews && npm run dev",
"dev:extension": "npm run compile:extension -- --sourcemap --watch",
"lint": "run-p lint:*",
"lint:extension": "eslint . --ext .vue,.ts,.tsx,.js,.jsx --max-warnings 0 --cache --cache-location node_modules/.cache/eslint",
"lint:webviews": "cd ./src/webviews && npm run lintfix",
Expand Down Expand Up @@ -262,21 +262,26 @@
"radicle": [
{
"id": "getting-started",
"contextualTitle": "Radicle",
"name": "Getting Started",
"icon": "$(radicle-logo)",
"initialSize": 1,
"when": "!radicle.isRadInitialized"
},
{
"id": "cli-commands",
"contextualTitle": "Radicle",
"name": "CLI Commands",
"icon": "$(radicle-logo)",
"initialSize": 230,
"initialSize": 1,
"when": "radicle.isRadInitialized"
},
{
"id": "patches-view",
"contextualTitle": "Radicle",
"name": "Patches",
"icon": "$(git-pull-request)",
"initialSize": 4,
"when": "radicle.isRadInitialized"
}
]
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { exec, log, showLog } from '../utils'
import {
type FilechangeNode,
checkOutPatch,
copyToClipboardAndNotifify,
copyToClipboardAndNotify,
deAuthCurrentRadicleIdentity,
launchAuthenticationFlow,
refreshPatchesEventEmitter,
Expand Down Expand Up @@ -96,7 +96,7 @@ export function registerAllCommands(): void {
})
registerVsCodeCmd('radicle.checkoutPatch', checkOutPatch)
registerVsCodeCmd('radicle.copyPatchId', async (patch: Partial<Patch> | undefined) => {
typeof patch?.id === 'string' && (await copyToClipboardAndNotifify(patch.id))
typeof patch?.id === 'string' && (await copyToClipboardAndNotify(patch.id))
})
registerVsCodeCmd(
'radicle.openDiff',
Expand Down
11 changes: 6 additions & 5 deletions src/helpers/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
window,
} from 'vscode'
import { getExtensionContext } from '../store'
import { assertUnreachable, getNonce, truncateKeepWords } from '../utils'
import { getNonce, truncateKeepWords } from '../utils'
import {
type notifyExtension,
notifyWebview as notifyWebviewBase,
} from '../utils/webview-messaging'
import type { Patch, PatchDetailInjectedState } from '../types'
import { copyToClipboardAndNotify } from '../ux'

export const webviewId = 'webview-patch-detail'

Expand Down Expand Up @@ -88,9 +89,9 @@ export function createOrShowWebview(ctx: ExtensionContext, patch: Patch) {
})
break
}

default:
assertUnreachable(message.command)
case 'copyToClipboardAndNotify':
copyToClipboardAndNotify(message.payload.textToCopy)
break
}
},
undefined,
Expand Down Expand Up @@ -169,7 +170,7 @@ function getWebviewHtml<State extends object>(webview: Webview, state?: State) {
}

function getPanelTitle(patch: Patch) {
const truncatedTitle = truncateKeepWords(patch.title, 24)
const truncatedTitle = truncateKeepWords(patch.title, 30)

return `${truncatedTitle}${truncatedTitle.length < patch.title.length ? ' …' : ''}`
}
Expand Down
48 changes: 30 additions & 18 deletions src/types/httpd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type Did = `did:key:${string}`

export interface RadicleIdentity {
id: Did
alias: string
alias?: string
}

export interface HttpdRoot {
Expand Down Expand Up @@ -75,37 +75,49 @@ export function isPatch(x: unknown): x is Patch {
return Boolean(isPatch)
}

export interface Revision {
id: string
author: RadicleIdentity
description: string
base: string
/**
* a.k.a. Object Identifier. The value is the commit hash.
*/
oid: string
refs: string[]
discussions: Comment[]
reviews: Review[]
timestamp: number
}

export interface Comment {
id: string
author: RadicleIdentity
body: string
edits: Edit[]
embeds: Embed[]
reactions: [string, string][]
timestamp: number
replyTo: string | null
}

export type ReviewVerdict = 'accept' | 'reject' | null

export interface Review {
export interface Edit {
author: RadicleIdentity
verdict?: ReviewVerdict
summary: string | null
comments: string[]
body: string
embeds: Embed[]
timestamp: number
}

export interface Revision {
id: string
export interface Embed {
name: string
content: string
}

export interface Review {
author: RadicleIdentity
description: string
base: string
/**
* a.k.a. Object Identifier. The value is the commit hash.
*/
oid: string
discussions: Comment[]
reviews: Review[]
refs: string[]
verdict: 'accept' | 'reject' | null
summary: string | null
comments: string[]
timestamp: number
}

Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from './log'
export * from './memoizeWithDebouncedCacheClear'
export * from './patch'
export * from './string'
export * from './time'
export * from './whenClauseContext'
export * from './workspace'
36 changes: 36 additions & 0 deletions src/utils/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import TimeAgo, { type LabelStyleName, type Style } from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en'

TimeAgo.addDefaultLocale(en)
const timeAgo = new TimeAgo('en-US')

// TODO: maninak add JSDocs with example outputs

export function getTimeAgo(
unixTimestamp: number,
labelStyle: LabelStyleName = 'long',
): string {
const customTimeAgoStyle: Omit<Style, 'labels'> = {
steps: [
{ formatAs: 'now' },
{ minTime: 60, formatAs: 'minute' },
{ minTime: 60 * 60, formatAs: 'hour' },
{ minTime: 60 * 60 * 24 * 2, formatAs: 'day' },
{ minTime: 60 * 60 * 24 * 365, formatAs: 'year' },
],
}

return timeAgo.format(unixTimestamp * 1000, { ...customTimeAgoStyle, labels: [labelStyle] })
}

export function getFormattedDate(unixTimestamp: number, locale?: string): string {
return new Date(unixTimestamp * 1000).toLocaleDateString(locale, {
weekday: 'short',
month: 'short',
year: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'shortGeneric',
})
}
5 changes: 3 additions & 2 deletions src/utils/webview-messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ type MessageToWebview =
| Message<'resetCount'>
| Message<'updateState', PatchDetailInjectedState>

type MessageToExtension = Message<'showInfoNotification', { text: string }>
// append more message types with `| Message<'...', SomeType>`
type MessageToExtension =
| Message<'showInfoNotification', { text: string }>
| Message<'copyToClipboardAndNotify', { textToCopy: string }>

/** Sends a message, usually from the host window, to the provided webview. */
export function notifyWebview(message: MessageToWebview, webview: Webview): void {
Expand Down
2 changes: 1 addition & 1 deletion src/ux/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { env, window } from 'vscode'
/**
* Copies `val` to the user's clipboard and notifies the user of what was just copied.
*/
export async function copyToClipboardAndNotifify(val: string) {
export async function copyToClipboardAndNotify(val: string) {
await env.clipboard.writeText(val)
window.showInformationMessage(`"${val}" copied to clipboard`)
}
38 changes: 4 additions & 34 deletions src/ux/patchesView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import {
TreeItemCollapsibleState,
Uri,
} from 'vscode'
import TimeAgo, { type LabelStyleName, type Style } from 'javascript-time-ago'
import en from 'javascript-time-ago/locale/en'
import {
debouncedClearMemoizedgetRepoIdCache,
fetchFromHttpd,
Expand All @@ -26,6 +24,7 @@ import {
getCurrentGitBranch,
getFirstAndLatestRevisions,
getIdentityAliasOrId,
getTimeAgo,
log,
memoizeWithDebouncedCacheClear,
shortenHash,
Expand Down Expand Up @@ -402,9 +401,9 @@ function getPatchTreeItemTooltip(
const shouldShowRevisionEvent = patch.revisions.length >= 2 // more than the initial Revision

const tooltipTopSection = [
`${getHtmlIconForPatch(patch)} ${dat(patch.state.status)} ${separator} ${dat(
patch.id,
)} ${checkedOutIndicator}`,
`${getHtmlIconForPatch(patch)} ${dat(
capitalizeFirstLetter(patch.state.status),
)} ${separator} ${dat(patch.id)} ${checkedOutIndicator}`,
].join(lineBreak)

const tooltipMiddleSection = [
Expand Down Expand Up @@ -506,32 +505,3 @@ function getCssColor(themeColor: ThemeColor | undefined): string {
// @ts-expect-error id is set as private but there's no other API currently https://github.com/microsoft/vscode/issues/34411#issuecomment-329741042
return `var(--vscode-${(themeColor.id as string).replace('.', '-')})`
}

// function getFormattedDate(unixTimestamp: number): string {
// return new Date(unixTimestamp * 1000).toLocaleDateString(undefined, {
// weekday: 'short',
// month: 'short',
// year: 'numeric',
// day: 'numeric',
// hour: 'numeric',
// minute: 'numeric',
// // TODO: show zulu?
// })
// }

TimeAgo.addDefaultLocale(en)
const timeAgo = new TimeAgo('en-US')

function getTimeAgo(unixTimestamp: number, labelStyle: LabelStyleName = 'long'): string {
const customTimeAgoStyle: Omit<Style, 'labels'> = {
steps: [
{ formatAs: 'now' },
{ minTime: 60, formatAs: 'minute' },
{ minTime: 60 * 60, formatAs: 'hour' },
{ minTime: 60 * 60 * 24 * 2, formatAs: 'day' },
{ minTime: 60 * 60 * 24 * 365, formatAs: 'year' },
],
}

return timeAgo.format(unixTimestamp * 1000, { ...customTimeAgoStyle, labels: [labelStyle] })
}
3 changes: 2 additions & 1 deletion src/webviews/.prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"tabWidth": 2,
"singleQuote": true,
"printWidth": 95,
"trailingComma": "none"
"trailingComma": "none",
"htmlWhitespaceSensitivity": "strict"
}
2 changes: 2 additions & 0 deletions src/webviews/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
"dependencies": {
"@vscode/webview-ui-toolkit": "^1.4.0",
"@vueuse/core": "^10.7.0",
"javascript-time-ago": "^2.5.9",
"pinia": "^2.1.7",
"vue": "^3.3.10"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.3.3",
"@tsconfig/node18": "^18.2.2",
"@types/javascript-time-ago": "^2.0.8",
"@types/jsdom": "^21.1.6",
"@types/node": "^18.19.2",
"@vitejs/plugin-vue": "^4.5.1",
Expand Down
Loading

0 comments on commit 6ff5d8d

Please sign in to comment.