Skip to content

Commit bea7175

Browse files
ert78gbmondalacicursoragent
authored
feat: add copy/paste layer support (#2992)
feat: add copy/paste layer support Adds copy and paste buttons to the layer header so a layer can be copied to a virtual clipboard and pasted onto any layer, overwriting it (closes #2692). Paste is safe across keymaps and devices: - macro references are re-linked by name, falling back to a none action - dangling switch-keymap references fall back to a none action - modules are merged by id and key count so a layer copied from a different keyboard or layout can never desync the target's structure Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: László Monda <laci@monda.hu> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a1abeec commit bea7175

17 files changed

Lines changed: 301 additions & 13 deletions

packages/uhk-common/src/models/uhk-module-ids.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ export const UHK_MODULE_IDS = Object.freeze({
1010

1111
export type UHK_MODULE_IDS_KEY_TYPE = keyof typeof UHK_MODULE_IDS;
1212
export type UHK_MODULE_IDS_TYPE = typeof UHK_MODULE_IDS[UHK_MODULE_IDS_KEY_TYPE];
13+
14+
export const UHK_HALVES_AND_MODULE_IDS = [
15+
UHK_MODULE_IDS.RIGHT_HALF,
16+
UHK_MODULE_IDS.LEFT_HALF,
17+
UHK_MODULE_IDS.LEFT_KEY_CLUSTER,
18+
UHK_MODULE_IDS.RIGHT_TRACKBALL,
19+
UHK_MODULE_IDS.RIGHT_TRACKPOINT,
20+
UHK_MODULE_IDS.RIGHT_TOUCHPAD,
21+
]

packages/uhk-web/src/app/components/keymap/edit/keymap-edit.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
[halvesInfo]="halvesInfo$ | async"
77
[keyboardLayout]="keyboardLayout$ | async"
88
[allowNewLayers]="true"
9+
[allowLayerCopy]="true"
910
[allowLayerDoubleTap]="allowLayerDoubleTap$ | async"
1011
[backlightingMode]="backlightingMode$ | async"
12+
[canPasteLayer]="hasCopiedLayer$ | async"
1113
[currentLayer]="currentLayer$ | async"
1214
[isBacklightingColoring]="isBacklightingColoring$ | async"
1315
[lastEditedKey]="lastEditedKey$ | async"

packages/uhk-web/src/app/components/keymap/edit/keymap-edit.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
backlightingColorPalette,
1717
backlightingMode,
1818
getHalvesInfo,
19+
getHasCopiedLayer,
1920
getKeyboardLayout,
2021
getLayerOptions,
2122
getSecondaryRoleOptions,
@@ -57,6 +58,7 @@ export class KeymapEditComponent implements OnDestroy {
5758
backlightingMode$: Observable<BacklightingMode>;
5859
currentLayer$: Observable<LayerOption>;
5960
deletable$: Observable<boolean>;
61+
hasCopiedLayer$: Observable<boolean>;
6062
isBacklightingColoring$: Observable<boolean>;
6163
keymap$: Observable<Keymap>;
6264
keyboardLayout$: Observable<KeyboardLayout>;
@@ -117,6 +119,7 @@ export class KeymapEditComponent implements OnDestroy {
117119
});
118120

119121
this.deletable$ = store.select(isKeymapDeletable);
122+
this.hasCopiedLayer$ = store.select(getHasCopiedLayer);
120123

121124
this.keyboardLayout$ = store.select(getKeyboardLayout);
122125
this.allowLayerDoubleTap$ = store.select(layerDoubleTapSupported);

packages/uhk-web/src/app/components/layers/layers.component.html

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,35 @@
2727
</div>
2828
</div>
2929
</div>
30+
<circle-tooltip [tooltip]="layerTooltip" width="450" placement="bottom"/>
31+
</div>
32+
33+
<div *ngIf="allowLayerCopy" role="group" class="btn-group btn-group-lg layer-actions">
34+
<button #copyTooltip="ngbTooltip"
35+
type="button"
36+
class="btn btn-default"
37+
ngbTooltip="Copy the current layer to Agent's virtual clipboard. You can then paste it onto any layer, overwriting that layer's key actions."
38+
tooltipClass="tooltip-layer-action"
39+
triggers="hover"
40+
container="body"
41+
placement="bottom"
42+
(click)="copyTooltip.close(); onCopyLayer()"
43+
>
44+
<fa-icon [icon]="faCopy"></fa-icon>
45+
</button>
46+
<button *ngIf="canPasteLayer"
47+
#pasteTooltip="ngbTooltip"
48+
type="button"
49+
class="btn btn-default"
50+
ngbTooltip="Overwrite the current layer with the layer stored in Agent's virtual clipboard."
51+
tooltipClass="tooltip-layer-action"
52+
triggers="hover"
53+
container="body"
54+
placement="bottom"
55+
(click)="pasteTooltip.close(); onPasteLayer()"
56+
>
57+
<fa-icon [icon]="faPaste"></fa-icon>
58+
</button>
3059
</div>
3160

3261
<div *ngIf="showPaletteColors" class="group-container">
@@ -80,7 +109,6 @@
80109
</div>
81110
</div>
82111

83-
<circle-tooltip [tooltip]="layerTooltip" width="450" placement="bottom bottom-end"/>
84112
<ng-template #layerTooltip>
85113
<ul class="text-start">
86114
<li>The <strong>Base</strong> layer is active by default (when no other layers are active). This layer can never be deleted.</li>

packages/uhk-web/src/app/components/layers/layers.component.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,24 @@
3232
font-size: 18px;
3333
}
3434

35+
circle-tooltip {
36+
align-self: center;
37+
}
38+
3539
.btn-group {
3640
flex-wrap: wrap;
3741
.btn {
3842
flex-grow: 0;
3943
}
4044
}
4145
}
46+
47+
.layer-actions {
48+
flex-wrap: wrap;
49+
50+
.btn {
51+
flex-grow: 0;
52+
}
53+
}
4254
}
4355
}

packages/uhk-web/src/app/components/layers/layers.component.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
2-
import { faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
2+
import { faCopy, faPaste, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons';
33
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
44
import { colord, RgbColor } from 'colord';
55
import { LayerName, RgbColorInterface } from 'uhk-common';
@@ -14,7 +14,9 @@ import { LayerOption, ModifyColorOfBacklightingColorPalettePayload } from '../..
1414
changeDetection: ChangeDetectionStrategy.OnPush,
1515
})
1616
export class LayersComponent {
17+
@Input() allowLayerCopy = false;
1718
@Input() allowNewLayers: boolean;
19+
@Input() canPasteLayer = false;
1820
@Input() current: LayerOption;
1921
@Input() layerOptions: LayerOption[];
2022
@Input() paletteColors: Array<RgbColor> = [];
@@ -27,10 +29,14 @@ export class LayersComponent {
2729
@Output() selectLayer = new EventEmitter<LayerOption>();
2830
@Output() toggleColorFromPalette = new EventEmitter<number>();
2931
@Output() addLayer = new EventEmitter<number>();
32+
@Output() copyLayer = new EventEmitter<void>();
33+
@Output() pasteLayer = new EventEmitter<void>();
3034
@Output() removeLayer = new EventEmitter<number>();
3135

3236
@ViewChild('deleteTooltip') deleteTooltip: NgbTooltip;
3337

38+
faCopy = faCopy;
39+
faPaste = faPaste;
3440
faPlus = faPlus;
3541
faTrash = faTrash;
3642
LayerName = LayerName;
@@ -56,6 +62,14 @@ export class LayersComponent {
5662
this.addLayer.emit(layerOption.id);
5763
}
5864

65+
onCopyLayer(): void {
66+
this.copyLayer.emit();
67+
}
68+
69+
onPasteLayer(): void {
70+
this.pasteLayer.emit();
71+
}
72+
5973
onColorSelected(index): void {
6074
this.toggleColorFromPalette.emit(index);
6175
}

packages/uhk-web/src/app/components/svg/wrap/svg-keyboard-wrap.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<ng-template [ngIf]="layers">
22
<layers [class.disabled]="animationState === 'opened'"
3+
[allowLayerCopy]="allowLayerCopy"
34
[allowNewLayers]="allowNewLayers"
5+
[canPasteLayer]="canPasteLayer"
46
[paletteColors]="paletteColors"
57
[current]="currentLayer"
68
[layerOptions]="layerOptions"
79
[selectedPaletteColorIndex]="selectedPaletteColorIndex"
810
[showPaletteColors]="showPaletteColors"
911
(addColorToPalette)="addColorToPalette.emit($event)"
1012
(addLayer)="onAddLayer($event)"
13+
(copyLayer)="onCopyLayer()"
1114
(deleteColorFromPalette)="deleteColorFromPalette.emit()"
1215
(modifyColorPaletteColor)="modifyColorPaletteColor.emit($event)"
16+
(pasteLayer)="onPasteLayer()"
1317
(removeLayer)="onRemoveLayer($event)"
1418
(selectLayer)="selectedLayerChanged.emit($event)"
1519
(toggleColorFromPalette)="toggleColorFromPalette.emit($event)"

packages/uhk-web/src/app/components/svg/wrap/svg-keyboard-wrap.component.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
import { MapperService } from '../../../services/mapper.service';
4848
import { AppState, getKeymaps, getMacros, getAnimationEnabled, getOpenPopover } from '../../../store';
4949
import { ClosePopoverAction } from '../../../store/actions/keymap';
50-
import { AddLayerAction, RemoveLayerAction, SaveKeyAction, SetKeyColorAction } from '../../../store/actions/keymap';
50+
import { AddLayerAction, CopyLayerAction, PasteLayerAction, RemoveLayerAction, SaveKeyAction, SetKeyColorAction } from '../../../store/actions/keymap';
5151
import { PopoverComponent } from '../../popover';
5252
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
5353
import { KeyActionRemap } from '../../../models/key-action-remap';
@@ -101,8 +101,10 @@ interface NameValuePair {
101101
]
102102
})
103103
export class SvgKeyboardWrapComponent implements AfterViewInit, OnInit, OnChanges, OnDestroy {
104+
@Input() allowLayerCopy = false;
104105
@Input() allowNewLayers: boolean;
105106
@Input() backlightingMode: BacklightingMode;
107+
@Input() canPasteLayer = false;
106108
@Input() currentLayer: LayerOption;
107109
@Input() isBacklightingColoring = false;
108110
@Input() keymap: Keymap;
@@ -319,6 +321,14 @@ export class SvgKeyboardWrapComponent implements AfterViewInit, OnInit, OnChange
319321
this.store.dispatch(new RemoveLayerAction(id));
320322
}
321323

324+
onCopyLayer(): void {
325+
this.store.dispatch(new CopyLayerAction(this.currentLayer.id));
326+
}
327+
328+
onPasteLayer(): void {
329+
this.store.dispatch(new PasteLayerAction(this.currentLayer.id));
330+
}
331+
322332
showPopover(keyAction: KeyAction): void {
323333
setTimeout(() => {
324334
this.keyPosition = this.keyElement.getBoundingClientRect();

packages/uhk-web/src/app/store/actions/keymap.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { ExchangeKeysActionModel, OpenPopoverModel } from '../../models';
99
export enum ActionTypes {
1010
Add = '[Keymap] Add keymap',
1111
AddLayer = '[Keymap] Add keymap layer',
12+
CopyLayer = '[Keymap] Copy layer',
13+
PasteLayer = '[Keymap] Paste layer',
1214
Duplicate = '[Keymap] Duplicate keymap',
1315
EditAbbr = '[Keymap] Edit keymap abbreviation',
1416
EditName = '[Keymap] Edit keymap title',
@@ -41,6 +43,20 @@ export class AddLayerAction implements Action {
4143
}
4244
}
4345

46+
export class CopyLayerAction implements Action {
47+
type = ActionTypes.CopyLayer;
48+
49+
constructor(public payload: number) {
50+
}
51+
}
52+
53+
export class PasteLayerAction implements Action {
54+
type = ActionTypes.PasteLayer;
55+
56+
constructor(public payload: number) {
57+
}
58+
}
59+
4460
export class DuplicateKeymapAction implements Action {
4561
type = ActionTypes.Duplicate;
4662

@@ -170,6 +186,8 @@ export class SelectLayerAction implements Action {
170186
export type Actions
171187
= AddKeymapAction
172188
| AddLayerAction
189+
| CopyLayerAction
190+
| PasteLayerAction
173191
| DuplicateKeymapAction
174192
| EditKeymapAbbreviationAction
175193
| EditKeymapNameAction

packages/uhk-web/src/app/store/effects/user-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export class UserConfigEffects {
111111
Keymaps.ActionTypes.EditAbbr, Keymaps.ActionTypes.SetDefault, Keymaps.ActionTypes.Remove,
112112
Keymaps.ActionTypes.SaveKey, Keymaps.ActionTypes.EditDescription, Keymaps.ActionTypes.ExchangeKeys,
113113
Keymaps.ActionTypes.AddLayer, Keymaps.ActionTypes.RemoveLayer, Keymaps.ActionTypes.SetKeyColor,
114+
Keymaps.ActionTypes.PasteLayer,
114115
Macros.ActionTypes.Add, Macros.ActionTypes.Duplicate, Macros.ActionTypes.EditName, Macros.ActionTypes.Remove,
115116
Macros.ActionTypes.AddAction, Macros.ActionTypes.SaveAction, Macros.ActionTypes.DeleteAction,
116117
Macros.ActionTypes.ReorderAction, Macros.ActionTypes.DuplicateAction,

0 commit comments

Comments
 (0)