Skip to content

Commit

Permalink
Add undo/redo buttons to the top bar (#11433)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitvakatu authored Oct 30, 2024
1 parent 0cf1c02 commit 0d731ad
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 37 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- [Changed the way of adding new column in Table Input Widget][11388]. The
"virtual column" is replaced with an explicit (+) button.
- [New dropdown-based component menu][11398].
- [Undo/redo buttons in the top bar][11433].
- [Size of Table Input Widget is preserved and restored after project
re-opening][11435]
- [Added application version to the title bar.][11446]
Expand All @@ -24,6 +25,7 @@
[11383]: https://github.com/enso-org/enso/pull/11383
[11388]: https://github.com/enso-org/enso/pull/11388
[11398]: https://github.com/enso-org/enso/pull/11398
[11398]: https://github.com/enso-org/enso/pull/11433
[11435]: https://github.com/enso-org/enso/pull/11435
[11446]: https://github.com/enso-org/enso/pull/11446
[11447]: https://github.com/enso-org/enso/pull/11447
Expand Down
42 changes: 42 additions & 0 deletions app/gui/src/project-view/components/ControlButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<template>
<div class="ControlButtons">
<div class="control left-end">
<slot name="left"></slot>
</div>
<div class="control right-end">
<slot name="right"></slot>
</div>
</div>
</template>

<style scoped>
.ControlButtons {
user-select: none;
display: flex;
place-items: center;
gap: 1px;
}
.control {
background: var(--color-frame-bg);
backdrop-filter: var(--blur-app-bg);
padding: 4px 4px;
width: 32px;
}
.left-end {
border-radius: var(--radius-full) 0 0 var(--radius-full);
> * {
margin: 0 0 0 auto;
}
}
.right-end {
border-radius: 0 var(--radius-full) var(--radius-full) 0;
> * {
margin: 0 auto 0 0;
}
}
</style>
43 changes: 7 additions & 36 deletions app/gui/src/project-view/components/RecordControl.vue
Original file line number Diff line number Diff line change
@@ -1,64 +1,35 @@
<script setup lang="ts">
import SvgButton from '@/components/SvgButton.vue'
import { useProjectStore } from '@/stores/project'
import ControlButtons from './ControlButtons.vue'
const project = useProjectStore()
</script>

<template>
<div class="RecordControl">
<div class="control left-end">
<ControlButtons class="RecordControl">
<template #left>
<SvgButton
title="Refresh"
class="iconButton"
name="refresh"
draggable="false"
@click.stop="project.executionContext.recompute()"
/>
</div>
<div class="control right-end">
</template>
<template #right>
<SvgButton
title="Write All"
class="iconButton"
name="workflow_play"
draggable="false"
@click.stop="project.executionContext.recompute('all', 'Live')"
/>
</div>
</div>
</template>
</ControlButtons>
</template>

<style scoped>
.RecordControl {
user-select: none;
display: flex;
place-items: center;
gap: 1px;
}
.control {
background: var(--color-frame-bg);
backdrop-filter: var(--blur-app-bg);
padding: 4px 4px;
width: 42px;
}
.left-end {
border-radius: var(--radius-full) 0 0 var(--radius-full);
.iconButton {
margin: 0 0 0 auto;
}
}
.right-end {
border-radius: 0 var(--radius-full) var(--radius-full) 0;
.iconButton {
margin: 0 auto 0 0;
}
}
.iconButton:active {
color: #ba4c40;
}
Expand Down
2 changes: 2 additions & 0 deletions app/gui/src/project-view/components/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ExtendedMenu from '@/components/ExtendedMenu.vue'
import NavBreadcrumbs from '@/components/NavBreadcrumbs.vue'
import RecordControl from '@/components/RecordControl.vue'
import SelectionMenu from '@/components/SelectionMenu.vue'
import UndoRedoButtons from './UndoRedoButtons.vue'
const showColorPicker = defineModel<boolean>('showColorPicker', { required: true })
const showCodeEditor = defineModel<boolean>('showCodeEditor', { required: true })
Expand All @@ -24,6 +25,7 @@ const emit = defineEmits<{
<div class="TopBar">
<NavBreadcrumbs />
<RecordControl />
<UndoRedoButtons />
<Transition name="selection-menu">
<SelectionMenu
v-if="componentsSelected > 1"
Expand Down
32 changes: 32 additions & 0 deletions app/gui/src/project-view/components/UndoRedoButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<script setup lang="ts">
import SvgButton from '@/components/SvgButton.vue'
import { useGraphStore } from '@/stores/graph'
import ControlButtons from './ControlButtons.vue'
const graphStore = useGraphStore()
</script>

<template>
<ControlButtons class="UndoRedoButtons">
<template #left>
<SvgButton
title="Undo"
class="iconButton"
name="undo"
draggable="false"
:disabled="!graphStore.undoManager.canUndo.value"
@click.stop="graphStore.undoManager.undo"
/>
</template>
<template #right>
<SvgButton
title="Redo"
class="iconButton"
name="redo"
draggable="false"
:disabled="!graphStore.undoManager.canRedo.value"
@click.stop="graphStore.undoManager.redo"
/>
</template>
</ControlButtons>
</template>
29 changes: 28 additions & 1 deletion app/gui/src/project-view/stores/graph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import { isAstId, isIdentifier } from '@/util/ast/abstract'
import { RawAst, visitRecursive } from '@/util/ast/raw'
import { reactiveModule } from '@/util/ast/reactive'
import { partition } from '@/util/data/array'
import { Events, stringUnionToArray } from '@/util/data/observable'
import { Rect } from '@/util/data/rect'
import { Err, Ok, mapOk, unwrap, type Result } from '@/util/data/result'
import { Err, mapOk, Ok, unwrap, type Result } from '@/util/data/result'
import { Vec2 } from '@/util/data/vec2'
import { normalizeQualifiedName, qnLastSegment, tryQualifiedName } from '@/util/qualifiedName'
import { useWatchContext } from '@/util/reactivity'
Expand Down Expand Up @@ -58,6 +59,7 @@ import type {
VisualizationMetadata,
} from 'ydoc-shared/yjsModel'
import { defaultLocalOrigin, sourceRangeKey, visMetadataEquals } from 'ydoc-shared/yjsModel'
import { UndoManager } from 'yjs'

const FALLBACK_BINDING_PREFIX = 'node'

Expand Down Expand Up @@ -364,6 +366,29 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
})
}

const undoManagerStatus = reactive({
canUndo: false,
canRedo: false,
update(m: UndoManager) {
this.canUndo = m.canUndo()
this.canRedo = m.canRedo()
},
})
watch(
() => proj.module?.undoManager,
(m) => {
if (m) {
const update = () => undoManagerStatus.update(m)
const events = stringUnionToArray<keyof Events<UndoManager>>()(
'stack-item-added',
'stack-item-popped',
'stack-cleared',
'stack-item-updated',
)
events.forEach((event) => m.on(event, update))
}
},
)
const undoManager = {
undo() {
proj.module?.undoManager.undo()
Expand All @@ -374,6 +399,8 @@ export const { injectFn: useGraphStore, provideFn: provideGraphStore } = createC
undoStackBoundary() {
proj.module?.undoManager.stopCapturing()
},
canUndo: computed(() => undoManagerStatus.canUndo),
canRedo: computed(() => undoManagerStatus.canRedo),
}

function setNodePosition(nodeId: NodeId, position: Vec2) {
Expand Down
10 changes: 10 additions & 0 deletions app/gui/src/project-view/util/data/observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

import type { ObservableV2 } from 'lib0/observable'

/** Possible events which can be emitted by Observable. */
export type Events<O extends ObservableV2<any>> = O extends ObservableV2<infer E> ? E : never

/** Validate if a list of arguments covers all variants of the union type. */
export function stringUnionToArray<T>() {
return <U extends NonEmptyArray<T>>(...elements: MustInclude<T, U>) => elements
}

type ValueOf<T> = T[keyof T]
type NonEmptyArray<T> = [T, ...T[]]
type MustInclude<T, U extends T[]> = [T] extends [ValueOf<U>] ? U : never

/**
* Returns promise which will resolve on the next event. The promise will have the event's
* payload.
Expand Down

0 comments on commit 0d731ad

Please sign in to comment.