Skip to content

Commit

Permalink
new svg visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
phil294 committed Apr 25, 2023
1 parent 939ed41 commit 6a9b422
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/extension.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module.exports.activate = (###* @type vscode.ExtensionContext ### context) =>

context.subscriptions.push vscode.commands.registerCommand 'git-log--graph.start', =>
panel = vscode.window.createWebviewPanel(EXT_NAME, EXT_NAME, vscode.window.activeTextEditor?.viewColumn or 1, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ vscode.Uri.joinPath(context.extensionUri, 'web-dist') ] })
panel.iconPath = vscode.Uri.joinPath(context.extensionUri, "logo.png");
panel.iconPath = vscode.Uri.joinPath(context.extensionUri, "logo.png")
view = panel.webview

view.onDidReceiveMessage (###* @type MsgRequest ### message) =>
Expand Down
36 changes: 10 additions & 26 deletions web/src/views/MainView.coffee
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { git, show_error_message, get_config } from '../bridge.coffee'
# TODO: type errors
import { parse, Branch, Commit } from './log-utils.coffee'
import { ref, Ref, computed, watch } from 'vue'
import GitInputModel, { parse_config_actions, GitAction } from './GitInput.coffee'
import GitInput from './GitInput.vue'
import GitActionButton from './GitActionButton.vue'
import SelectedCommit from './SelectedCommit.vue'
import Visualization from './Visualization.vue'

export default
components: { SelectedCommit, GitInput, GitActionButton }
components: { SelectedCommit, GitInput, GitActionButton, Visualization }
setup: ->
``###* @type {Ref<Branch[]>} ###
branches = ref []
# this is either a branch name or HEAD in which case it will simply not be shown
# which is also not necessary because HEAD is then also visible as a branch tip.
head_branch = ref ''
vis_style = ref {}
vis_max_length = ref 0
``###* @type {Ref<Commit | null>} ###
selected_commit = ref null
``###* @type {Ref<GitInputModel | null>} ###
Expand All @@ -30,6 +32,7 @@ export default
txt_filter = ref ''
``###* @type {Ref<'filter' | 'search'>} ###
txt_filter_type = ref 'filter'
# TODO: error here somewhere
clear_filter = =>
txt_filter.value = ''
if selected_commit.value
Expand Down Expand Up @@ -106,32 +109,14 @@ export default
color: '#fff'
returned_commits.value = parsed.commits
branches.value = parsed.branches
vis_style.value = 'min-width': "min(50vw, #{parsed.vis_max_length/2}em)"
vis_max_length.value = parsed.vis_max_length
if selected_commit.value
selected_commit.value = (commits.value.find (commit) =>
commit.hash == selected_commit.value?.hash) or null
await new Promise (ok) => setTimeout(ok, 0)
commits_scroller_ref.value?.scrollToItem scroll_item_offset.value
head_branch.value = await git 'rev-parse --abbrev-ref HEAD'

mousemove_debouncer = -1
hover_branch_debouncer = -1
``###* @type {Ref<string | null>} ###
hovered_branch_name = ref null
document.addEventListener 'mousemove', (e) =>
window.clearTimeout mousemove_debouncer
mousemove_debouncer = window.setTimeout (=>
#@ts-ignore
if e.target?.classList.contains('vis-v') #@ts-ignore
if branch_name = e.target.dataset.branchName
hovered_branch_name.value = branch_name
window.clearTimeout hover_branch_debouncer
hover_branch_debouncer = window.setTimeout (=>
hovered_branch_name.value = null
), 300
), 100


show_invisible_branches = ref false

``###* @type {Ref<Commit[]>} ###
Expand Down Expand Up @@ -179,12 +164,12 @@ export default
branches.value.filter (branch) =>
not visible_branches.value.includes branch

scroll_to_branch_tip = (###* @type Branch ### branch) =>
scroll_to_branch_tip = (###* @type string ### branch_name) =>
first_branch_commit_i = commits.value.findIndex (commit) =>
# Only applicable if virtual branches are excluded as these don't have a tip. Otherwise, each vis would need to be traversed
commit.refs.some (ref) => ref.name == branch.name
commit.refs.some (ref) => ref.name == branch_name
if first_branch_commit_i == -1
return show_error_message "No commit found for branch #{branch.name}. No idea why :/"
return show_error_message "No commit found for branch #{branch_name}. No idea why :/"
commits_scroller_ref.value?.scrollToItem first_branch_commit_i
show_invisible_branches.value = false
# Not only scroll to tip, but also select it, so the behavior is equal to clicking on
Expand Down Expand Up @@ -216,7 +201,7 @@ export default
{
commits
branches
vis_style
vis_max_length
head_branch
git_input_ref
run_log
Expand All @@ -235,7 +220,6 @@ export default
txt_filter_type
txt_filter_enter
clear_filter
hovered_branch_name
global_actions
branch_drop
drag_drop_target_branch_name
Expand Down
41 changes: 22 additions & 19 deletions web/src/views/MainView.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
1<template lang="slm">
<template lang="slm">
#main-view.fill.col
details
summary Configure...
Expand All @@ -9,14 +9,14 @@
| No commits found
nav.row.align-center.justify-space-between.gap-10
ul#branches.row.align-center.wrap.flex-1.gap-3
li.ref.branch.visible.active v-for="branch of visible_branches" :class="{is_head:branch.name===head_branch, is_hovered:branch.name===hovered_branch_name}"
button :style="{color:branch.color}" @click="scroll_to_branch_tip(branch)" title="Jump to branch tip" v-drag="branch.name" v-drop="branch_drop(branch.name)"
li.ref.branch-tip.visible.active v-for="branch of visible_branches" :class="{is_head:branch.name===head_branch}"
button :style="{color:branch.color}" @click="scroll_to_branch_tip(branch.name)" title="Jump to branch tip" v-drag="branch.name" v-drop="branch_drop(branch.name)"
| {{ branch.name }}
li.show-invisible_branches v-if="invisible_branches.length"
button @click="show_invisible_branches = ! show_invisible_branches"
| Show all >>
template v-if="show_invisible_branches"
li.ref.branch.invisible v-for="branch of invisible_branches" :class="{is_head:branch.name===head_branch}"
li.ref.branch-tip.invisible v-for="branch of invisible_branches" :class="{is_head:branch.name===head_branch}"
button :style="{color:branch.color}" @click="scroll_to_branch_tip(branch)" title="Jump to branch tip" v-drag="branch.name" v-drop="branch_drop(branch.name)"
| {{ branch.name }}
li Click on any of the branch names to jump to the tip of it.
Expand All @@ -35,19 +35,14 @@
input type="radio" v-model="txt_filter_type" value="search"
| Search
recycle-scroller#log.scroller.fill-w.flex-1 role="list" :items="commits" v-slot="{ item: commit }" key-field="i" size-field="scroll_height" :buffer="0" :emit-update="true" @update="commits_scroller_updated" ref="commits_scroller_ref" tabindex="-1"
.row.commit :class="{active:commit===selected_commit,empty:!commit.hash}"
.vis :style="vis_style"
span.vis-v v-for="v of commit.vis" :style="v.branch? {color:v.branch.color} : undefined" :class="{is_head:v.branch&&v.branch.name===head_branch}" :data-branch-name="v.branch? v.branch.name : undefined"
| {{ v.char }}
.info.flex-1.row.gap-20 v-if="commit.hash" @click="commit_clicked(commit)"
.row.commit :class="{active:commit===selected_commit,empty:!commit.hash}" @click="commit_clicked(commit)" role="button"
visualization.vis :commit="commit" :vis_max_length="vis_max_length" :head_branch="head_branch" @branch_drop="branch_drop($event)"
.info.flex-1.row.gap-20 v-if="commit.hash"
button
.hash.flex-noshrink {{ commit.hash }}
.subject-wrapper.flex-1.row.align-center
div.vis.vis-v :style="commit.branch? {color:commit.branch.color} : undefined"
| ●
.ref v-for="ref of commit.refs" :class="{is_head:ref.name===head_branch,branch:ref.type==='branch'}" v-drag="ref.type==='branch'?ref.name:undefined" v-drop="ref.type==='branch'?branch_drop(ref.name):undefined"
span :style="{color:ref.color}"
| {{ ref.name }}
.subject {{ commit.subject }}
.author.flex-noshrink :title="commit.author_email"
| {{ commit.author_name }}
Expand Down Expand Up @@ -77,7 +72,7 @@ details
&[open]
color unset
padding 10px
.ref
:deep(.ref)
background black
font-weight bold
font-style italic
Expand All @@ -86,12 +81,13 @@ details
border 1px solid #505050
border-radius 7px
white-space pre
margin 0 1px
&.is_head > *:after
content ' (HEAD)'
color white
&.is_hovered
outline 3px solid #c54a4a
&.branch
&.branch-tip
&.dragenter
background white !important
color red !important
Expand Down Expand Up @@ -126,27 +122,35 @@ details
width 0
color grey
.is_head
:deep(.is_head)
box-shadow 0px 0px 6px 4px #ffffff30, 0px 0px 4px 0px #ffffff30 inset
border 3px solid white
border-radius 25px
#log.scroller
&:focus
// Need tabindex so that pgUp/Down works consistently (idk why, probably vvs bug), but focus outline adds no value here
outline none
.commit
--h 18px // must be synced with JS
--h 19px // must be synced with JS
&.empty
--h 11px // same
--h 6px // same
height var(--h)
line-height var(--h)
cursor pointer
&.active
box-shadow 0 0 3px 0px gold
.vis
font-weight bold
font-family monospace
// TODO: wait for vscode to be process.versions.chrome (dev tools) >= 112, then:
// .vis:has(+.info:hover)
// overflow hidden
// Workaround until then:
.info:hover
z-index 1
background #202020
.info
cursor pointer
border-top 1px solid #2e2e2e
> *
white-space pre
Expand All @@ -163,7 +167,6 @@ details
overflow hidden
flex 0 3 auto
min-width 55px
margin 0 1px
> .subject
overflow hidden
flex 1 1 30%
Expand Down
117 changes: 117 additions & 0 deletions web/src/views/Visualization.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { computed, defineComponent } from 'vue'
``###* @typedef {import('./log-utils').Commit} Commit ###

export default defineComponent
props:
commit:
required: true
###* @type {() => Commit} ###
type: Object
vis_max_length:
required: true
type: Number
head_branch:
required: true
type: String
emits: ['branch_drop']
setup: (props) ->
v_width = 7
padding_left = 5
padding_right = 20
vis_width = props.vis_max_length * v_width + padding_right
v_height = computed =>
props.commit.scroll_height
vis_style = computed =>
'min-width': "min(50vw, #{vis_width}px)"
lines = computed =>
props.commit.vis
.map (v, i) =>
return null if v.char == ' '
coords = switch v.char
when '*', '|'
x1: padding_left + v_width * (i + 0.5)
x2: padding_left + v_width * (i + 0.5)
y1: 0
y2: v_height.value
when '⎺*', '⎺|'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 0.5)
y1: 0
y2: v_height.value
when '*⎺', '|⎺'
x1: padding_left + v_width * (i + 1)
x2: padding_left + v_width * (i + 0.5)
y1: 0
y2: v_height.value
when '_'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 1)
y1: v_height.value
y2: v_height.value
when '\\'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 1)
y1: 0
y2: v_height.value
when '\\'
x1: padding_left + v_width * (i - 0.5)
x2: padding_left + v_width * (i + 1)
y1: 0
y2: v_height.value
when '\\'
x1: padding_left + v_width * (i - 0.5)
x2: padding_left + v_width * (i + 1.5)
y1: 0
y2: v_height.value
when '/'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 1)
y1: v_height.value
y2: 0
when '/⎺'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 1.5)
y1: v_height.value
y2: 0
when '.', '-'
x1: padding_left + v_width * i
x2: padding_left + v_width * (i + 1)
y1: 0
y2: 0
else
throw new Error 'unexpected vis char '+v.char
{
style:
stroke: v.branch?.color
class:
is_head: v.branch?.name == props.head_branch
...coords
}
.filter Boolean
vis_circle_index = computed =>
props.commit.vis.findIndex (v) =>
['*', '⎺*', '*⎺'].includes v.char
circle = computed =>
if vis_circle_index.value > -1
v = props.commit.vis[vis_circle_index.value]
style:
stroke: v.branch?.color
class:
is_head: v.branch?.name == props.head_branch
cx: padding_left + v_width * (vis_circle_index.value + 0.5)
cy: v_height.value * 0.5
r: 4
refs_elems = computed =>
refs:
props.commit.refs.map (ref) =>
ref: ref
bind:
style:
color: ref.color
class:
is_head: ref.name == props.head_branch
'branch-tip': ref.type == 'branch'
style:
left: padding_left + v_width * (vis_circle_index.value + 1) + 3 + 'px'

{ vis_style, lines, vis_width, circle, refs_elems }
28 changes: 28 additions & 0 deletions web/src/views/Visualization.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template lang="slm">
.vis :style="vis_style"
svg :width="vis_width"
line.vis-v v-for="line of lines" v-bind="line"
circle.vis-v v-if="circle" v-bind="circle"
.refs.row.align-center :style="refs_elems.style"
.ref v-for="ref_elem of refs_elems.refs" v-drag="ref_elem.ref.type==='branch'?ref_elem.ref.name:undefined" v-drop="ref_elem.ref.type==='branch'?$emit('branch_drop',ref_elem.ref.name):undefined" v-bind="ref_elem.bind"
| {{ ref_elem.ref.name }}
</template>

<script lang="coffee" src="./Visualization.coffee"></script>

<style lang="stylus" scoped>
.vis
position relative
.vis-v
stroke-width 2
.refs
position absolute
top 0
line-height 1em
opacity 85%
svg
> line.vis-v.is_head
filter drop-shadow(4px 0px 1px #ffffffdd) drop-shadow(-4px 0px 1px #ffffffdd)
> circle.vis-v.is_head
fill white
</style>

0 comments on commit 6a9b422

Please sign in to comment.