Skip to content

Commit

Permalink
make branch actions available via context menu too
Browse files Browse the repository at this point in the history
  • Loading branch information
phil294 committed Apr 30, 2023
1 parent 823c720 commit 0a66679
Show file tree
Hide file tree
Showing 17 changed files with 220 additions and 65 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,13 @@
"**/.git/**"
],
"cSpell.words": [
"codicon",
"datetime",
"mhutchie",
"numstat",
"oneline",
"onglobalclick",
"onglobalkeyup",
"reflog",
"scroller",
"shortstat"
Expand Down
15 changes: 15 additions & 0 deletions web/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,21 @@ input:not([type='checkbox']):not([type='radio']).filter
padding 0 0 0 5px
background black
color #d5983d
ul.context-menu-wrapper
position absolute
background #111111dd
min-width 150px
cursor pointer
box-shadow 0 2px 3px 2px #111111dd
user-select none
z-index 10
> li
padding 4px 8px
&:not(:last-child)
border-bottom 1px solid #424242
&:hover
background black
</style>

<style lang="stylus" scoped>
Expand Down
95 changes: 95 additions & 0 deletions web/src/directives/context-menu.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import Vue from 'vue'

``###* @typedef {{label:string,icon?:string,action:()=>any}} ContextMenuEntry ###
``###* @typedef {{
oncontextmenu: (this: HTMLElement, ev: MouseEvent) => any
onglobalclick: (ev: MouseEvent) => any
onglobalkeyup: (ev: KeyboardEvent) => any
entries: ContextMenuEntry[]
}} ContextMenuData
###

``###* @type {Map<HTMLElement,ContextMenuData>} ###
context_menu_data_by_el = new Map

set_context_menu = (###* @type HTMLElement ### el, ###* @type ContextMenuEntry[] ### entries) =>
existing_context_menu_data = context_menu_data_by_el.get el
if existing_context_menu_data
existing_context_menu_data.entries = entries
return

``###* @type {HTMLElement | null} ###
wrapper_el = null

# The element(s) created by this is quite similar to the template of <git-action-button>
build_context_menu = (###* @type number ### x, ###* @type number ### y) =>
wrapper_el = document.createElement('ul')
wrapper_el.setAttribute('aria-label', 'Context menu')
wrapper_el.classList.add 'context-menu-wrapper'
wrapper_el.style.setProperty 'left', x + 'px'
wrapper_el.style.setProperty 'top', y + 'px'
for entry from entries
entry_el = document.createElement('li')
entry_el.setAttribute('role', 'button')
entry_el.classList.add('row', 'gap-5')
icon_el = document.createElement('i')
if entry.icon
icon_el.classList.add "codicon", "codicon-#{entry.icon}"
label_el = document.createElement('span')
label_el.textContent = entry.label
entry_el.appendChild(icon_el)
entry_el.appendChild(label_el)
entry_el.onclick = entry.action
wrapper_el.appendChild(entry_el)
document.body.appendChild(wrapper_el)
destroy_context_menu = =>
return if not wrapper_el
document.body.removeChild(wrapper_el)
wrapper_el = null

``###* @type ContextMenuData ###
context_menu_data =
oncontextmenu: (e) =>
e.preventDefault()
e.stopPropagation()
destroy_context_menu()
build_context_menu(e.clientX, e.clientY)
onglobalclick: =>
destroy_context_menu()
onglobalkeyup: (e) =>
if e.key == "Escape"
destroy_context_menu()
entries: entries

el.addEventListener 'contextmenu', context_menu_data.oncontextmenu, false
document.addEventListener 'contextmenu', context_menu_data.onglobalclick, false
document.addEventListener 'click', context_menu_data.onglobalclick, false
document.addEventListener 'keyup', context_menu_data.onglobalkeyup, false

context_menu_data_by_el.set el, context_menu_data

disable_context_menu = (###* @type {HTMLElement} ### el) =>
context_menu_data = context_menu_data_by_el.get el
if not context_menu_data
return
el.removeEventListener 'contextmenu', context_menu_data.oncontextmenu
document.removeEventListener 'contextmenu', context_menu_data.onglobalclick
document.removeEventListener 'click', context_menu_data.onglobalclick
document.removeEventListener 'keyup', context_menu_data.onglobalkeyup
context_menu_data_by_el.delete el

``###* @type {Vue.Directive} ###
directive =
mounted: (el, { value }) ->
if value
set_context_menu el, value
updated: (el, { value }) ->
value = value or null
if not value
disable_context_menu el
else
set_context_menu el, value
unmounted: (el) ->
disable_context_menu el

export default directive
14 changes: 4 additions & 10 deletions web/src/views/CommitDetails.coffee
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ref, computed, defineComponent, watchEffect } from 'vue'
import { git, open_diff, get_config } from '../bridge.coffee'
import { git, open_diff } from '../bridge.coffee'
import { commit_actions, stash_actions, branch_actions } from './store.coffee'
import { parse_config_actions } from './GitInput.coffee'
import GitActionButton from './GitActionButton.vue'
import RefTip from './RefTip.vue'
``###*
Expand All @@ -11,7 +10,6 @@ import RefTip from './RefTip.vue'
###* @template T @typedef {import('vue').ComputedRef<T>} ComputedRef ###

export default defineComponent
emits: ['change']
components: { GitActionButton, RefTip }
props:
commit:
Expand Down Expand Up @@ -48,12 +46,8 @@ export default defineComponent
show_diff = (###* @type string ### filepath) =>
open_diff props.commit.hash, filepath

_commit_actions = computed =>
parse_config_actions(commit_actions.value, [['{COMMIT_HASH}', props.commit.hash]])
_branch_actions = (###* @type string ### branch_name) =>
parse_config_actions(branch_actions.value, [['{BRANCH_NAME}', branch_name]])
_stash_actions = computed =>
parse_config_actions(stash_actions.value, [['{COMMIT_HASH}', props.commit.hash]])
_commit_actions = commit_actions(props.commit.hash)
_stash_actions = stash_actions(props.commit.hash)

{
branch_tips
Expand All @@ -62,6 +56,6 @@ export default defineComponent
show_diff
body
commit_actions: _commit_actions
branch_actions: _branch_actions
branch_actions
stash_actions: _stash_actions
}
8 changes: 4 additions & 4 deletions web/src/views/CommitDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ div
div v-if="stash"
h3 Stash:
.row.gap-5.wrap
git-action-button v-for="action of stash_actions" :git_action="action" @change="$emit('change')"
git-action-button v-for="action of stash_actions" :git_action="action"

div v-if="branch_tips.length"
ul.branches v-for="branch_tip of branch_tips"
li
ref-tip :git_ref="branch_tip"
ref-tip :git_ref="branch_tip" :commit="commit"
.row.gap-5.wrap
git-action-button v-for="action of branch_actions(branch_tip.name)" :git_action="action" @change="$emit('change')"
git-action-button v-for="action of branch_actions(branch_tip.name)" :git_action="action"

h3 This commit {{ commit.hash }}:
.row.gap-5.wrap
git-action-button v-for="action of commit_actions" :git_action="action" @change="$emit('change')"
git-action-button v-for="action of commit_actions" :git_action="action"

h3 Changed files:
ul.changed-files
Expand Down
22 changes: 4 additions & 18 deletions web/src/views/GitActionButton.coffee
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
import { ref, defineComponent } from 'vue'
import GitInput from './GitInput.vue'
import { defineComponent } from 'vue'
import { selected_git_action } from './store.coffee'

###* visual representation of a GitAction, resolving to a Button and onclick a Popup with GitInput inside ###
export default defineComponent
inheritAttrs: false
components: { GitInput }
emits: [ 'change' ]
props:
git_action:
###* @type {() => import('./GitInput.coffee').GitAction} ###
type: Object
required: true
setup: (_, { emit }) ->
show_popup = ref false
keep_open = ref false

success = =>
if not keep_open.value
show_popup.value = false
emit 'change'

{
show_popup
keep_open
success
}
setup: ->
{ selected_git_action }
20 changes: 5 additions & 15 deletions web/src/views/GitActionButton.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
<template lang="slm">
div
button.btn.gap-5 @click="show_popup=true" :title="git_action.description"
.icon.center v-if="git_action.icon"
i.codicon :class="'codicon-'+git_action.icon"
.title v-if="git_action.title"
| {{ git_action.title }}
popup v-if="show_popup" @close="show_popup=false"
.selected-input
p Execute Git command
git-input :git_action="git_action" @success="success()"
label.row.align-center.gap-5
input type="checkbox" v-model="keep_open"
| Keep window open after success
button.btn.gap-5 @click="selected_git_action=git_action" :title="git_action.description"
.icon.center v-if="git_action.icon"
i.codicon :class="'codicon-'+git_action.icon"
.title v-if="git_action.title"
| {{ git_action.title }}
</template>

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

<style lang="stylus">
.selected-input
width: clamp(200px, 50vw, 50vw)
</style>
9 changes: 5 additions & 4 deletions web/src/views/MainView.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import GitActionButton from './GitActionButton.vue'
import CommitDetails from './CommitDetails.vue'
import Visualization from './Visualization.vue'
import AllBranches from './AllBranches.vue'
import SelectedGitAction from './SelectedGitAction.vue'
import RefTip from './RefTip.vue'
``###*
# @typedef {import('./types').Commit} Commit
Expand All @@ -23,7 +24,7 @@ import RefTip from './RefTip.vue'
is_truthy = (value) => !!value

export default
components: { CommitDetails, GitInput, GitActionButton, Visualization, AllBranches, RefTip }
components: { CommitDetails, GitInput, GitActionButton, Visualization, AllBranches, RefTip, SelectedGitAction }
setup: ->


Expand Down Expand Up @@ -91,8 +92,7 @@ export default

``###* @type {Ref<GitInputModel | null>} ###
git_input_ref = ref null
do_log = =>
git_input_ref.value?.execute()
store.main_view_git_input_ref.value = git_input_ref
log_action =
# rearding the -greps: Under normal circumstances, when showing stashes in
# git log, each of the stashes 2 or 3 parents are being shown. That because of
Expand Down Expand Up @@ -237,7 +237,6 @@ export default
head_branch: store.head_branch
git_input_ref
run_log
do_log
log_action
commits_scroller_updated
visible_branches
Expand All @@ -257,4 +256,6 @@ export default
invisible_branch_tips_of_visible_branches_elems
connection_fake_commit
escape_pressed
refresh_main_view: store.refresh_main_view
selected_git_action: store.selected_git_action
}
10 changes: 6 additions & 4 deletions web/src/views/MainView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
input type="radio" v-model="txt_filter_type" value="search"
| Search
section#actions.center.gap-5 aria-roledescription="Global actions"
git-action-button.global-action v-for="action of global_actions" :git_action="action" @change="do_log()"
button#refresh.btn.center @click="do_log()" title="Refresh"
git-action-button.global-action v-for="action of global_actions" :git_action="action"
button#refresh.btn.center @click="refresh_main_view()" title="Refresh"
i.codicon.codicon-refresh
#quick-branch-tips
all-branches @branch_selected="scroll_to_branch_tip($event)"
Expand Down Expand Up @@ -51,15 +51,17 @@
progress.diff v-if="commit.stats" :value="(commit.stats.insertions / (commit.stats.insertions + commit.stats.deletions)) || 0" title="Ratio insertions / deletions"
.datetime.flex-noshrink {{ commit.datetime }}
#right.col.flex-1 v-if="selected_commit"
commit-details#selected-commit.flex-1.fill-w.padding :commit="selected_commit" @change="do_log()"
commit-details#selected-commit.flex-1.fill-w.padding :commit="selected_commit"
button#close-selected-commit.center @click="selected_commit=null" title="Close"
i.codicon.codicon-close
#resize-hint v-if="selected_commit"
| ← resize

popup v-if="combine_branches_from_branch_name" @close="combine_branches_from_branch_name=''"
.drag-drop-branch-actions.col.center.gap-5
git-action-button.drag-drop-branch-action v-for="action of combine_branches_actions" :git_action="action" @change="do_log()"
git-action-button.drag-drop-branch-action v-for="action of combine_branches_actions" :git_action="action"
popup v-if="selected_git_action" @close="selected_git_action=null"
selected-git-action
</template>

<script lang="coffee" src="./MainView.coffee"></script>
Expand Down
20 changes: 18 additions & 2 deletions web/src/views/RefTip.coffee
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
import { defineComponent, computed } from 'vue'
import { head_branch, combine_branches } from './store.coffee'
import { head_branch, combine_branches, branch_actions, stash_actions, selected_git_action } from './store.coffee'
``###* @typedef {import('./types').GitRef} GitRef ###
``###* @typedef {import('./types').Commit} Commit ###
``###* @typedef {import('./types').GitAction} GitAction ###

export default defineComponent
props:
git_ref:
required: true
###* @type {() => GitRef} ###
type: Object
commit:
###* @type {() => Commit} ###
type: Object
setup: (props) ->
is_branch = computed =>
props.git_ref.type == 'branch'
to_context_menu_entries = (###* @type GitAction[] ### actions) =>
actions.map (action) =>
label: action.title
icon: action.icon
action: =>
selected_git_action.value = action
bind: computed =>
style:
color: props.git_ref.color
Expand All @@ -23,4 +34,9 @@ export default defineComponent
return if not is_branch.value
source_branch_name = event.data
return if typeof source_branch_name != 'string'
combine_branches(source_branch_name, props.git_ref.name)
combine_branches(source_branch_name, props.git_ref.name)
context_menu:
if is_branch.value
to_context_menu_entries(branch_actions(props.git_ref.name))
else if props.git_ref.type == 'stash' and props.commit
to_context_menu_entries(stash_actions(props.commit.hash))
2 changes: 1 addition & 1 deletion web/src/views/RefTip.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template lang="slm">
.ref-tip v-bind="bind" v-drag="drag" v-drop="drop"
.ref-tip v-bind="bind" v-drag="drag" v-drop="drop" v-context-menu="context_menu"
| {{ git_ref.name }}
</template>

Expand Down

0 comments on commit 0a66679

Please sign in to comment.