Skip to content

Commit 749fdec

Browse files
committed
feat: enhance shortcuts functionality with control API integration, remove need for ctrl modifier
1 parent d38f643 commit 749fdec

File tree

8 files changed

+52
-92
lines changed

8 files changed

+52
-92
lines changed

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ revert functionality.
101101
- `team/`: Team-specific settings and information
102102
- `game-events/`: Game event input forms and displays
103103

104+
**Keyboard Shortcuts**: Centralized in `frontend/src/providers/shortcuts/` with global event listeners registered in the
105+
control plugin. All shortcuts (numpad commands, Ctrl+Space for auto-continue, Ctrl+1-9 for continue actions) are handled
106+
in one place with automatic disabling during text input.
107+
104108
### Adding New Teams
105109

106110
For new teams, add the team name to `defaultTeams` in `internal/app/engine/config.go` and submit a pull request.

frontend/src/components/common/NumberInput.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {Shortcuts} from "@/providers/shortcuts";
66
const props = defineProps<{
77
modelValue?: number | bigint | "NaN" | "Infinity" | "-Infinity",
88
label?: string,
9+
clearable?: boolean,
910
}>()
1011
1112
const emit = defineEmits<(event: 'update:modelValue', payload: number | undefined) => void>();
@@ -47,7 +48,7 @@ const updateValue = (value: string | number | null) => {
4748
@focusout="onFocusout"
4849
>
4950
<template v-slot:prepend>
50-
<q-icon name="close" @click="updateValue(null)"/>
51+
<q-icon v-if="clearable" name="close" @click="updateValue(null)"/>
5152
</template>
5253
</q-input>
5354
</template>
Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import type {Vector2Json} from "@/proto/geom/ssl_gc_geometry_pb";
3+
import NumberInput from "@/components/common/NumberInput.vue";
34
45
const props = defineProps<{
56
modelValue?: Vector2Json,
@@ -11,26 +12,18 @@ const emit = defineEmits<(event: 'update:modelValue', payload: Vector2Json | und
1112
const updateValue = (v: Vector2Json | undefined) => {
1213
emit('update:modelValue', v)
1314
}
14-
const updateX = (x: string | number | null) => {
15+
const updateX = (x: number | undefined) => {
1516
if (x !== null) {
1617
const y = props.modelValue?.y || 0
17-
if (typeof x === 'string') {
18-
updateValue({x: parseFloat(x), y})
19-
} else {
20-
updateValue({x, y})
21-
}
18+
updateValue({x, y})
2219
} else {
2320
updateValue(undefined)
2421
}
2522
}
26-
const updateY = (y: string | number | null) => {
23+
const updateY = (y: number | undefined) => {
2724
if (y !== null) {
2825
const x = props.modelValue?.x || 0
29-
if (typeof y === 'string') {
30-
updateValue({x, y: parseFloat(y)})
31-
} else {
32-
updateValue({x, y})
33-
}
26+
updateValue({x, y})
3427
} else {
3528
updateValue(undefined)
3629
}
@@ -41,32 +34,26 @@ const updateY = (y: string | number | null) => {
4134
<template>
4235
<q-item>
4336
<q-item-section>
44-
<q-input
45-
input-class="text-center"
46-
dense
47-
type="number"
37+
<NumberInput
4838
:label="(label || 'location') + ' x (m)'"
4939
:model-value="modelValue?.x"
5040
@update:model-value="updateX"
5141
>
5242
<template v-slot:prepend>
53-
<q-icon name="close" @click="updateX(null)"/>
43+
<q-icon name="close" @click="updateX(undefined)"/>
5444
</template>
55-
</q-input>
45+
</NumberInput>
5646
</q-item-section>
5747
<q-item-section>
58-
<q-input
59-
input-class="text-center"
60-
dense
61-
type="number"
48+
<NumberInput
6249
:label="(label || 'location') + ' y (m)'"
6350
:model-value="modelValue?.y"
6451
@update:model-value="updateY"
6552
>
6653
<template v-slot:prepend>
67-
<q-icon name="close" @click="updateY(null)"/>
54+
<q-icon name="close" @click="updateY(undefined)"/>
6855
</template>
69-
</q-input>
56+
</NumberInput>
7057
</q-item-section>
7158
</q-item>
7259
</template>

frontend/src/plugins/control/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const control = {
1111
install(app: App) {
1212
const controlApi = new ControlApi()
1313
const manualActions = new ManualActions(controlApi)
14-
const shortcuts = new Shortcuts(manualActions)
14+
const shortcuts = new Shortcuts(manualActions, controlApi)
1515
app.provide('control-api', controlApi)
1616
app.provide('command-actions', manualActions)
1717
app.provide('shortcuts', shortcuts)

frontend/src/providers/manualActions/index.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {isPausedStage} from "@/helpers";
44
import type {ControlApi} from "@/providers/controlApi";
55
import {useMatchStateStore} from "@/store/matchState";
66
import {commandName} from "@/helpers/texts";
7-
import {useGcStateStore} from "@/store/gcState";
87

98
export interface ManualAction {
109
send: () => void,
@@ -16,27 +15,12 @@ export interface ManualAction {
1615

1716
export class ManualActions {
1817
private readonly matchStateStore = useMatchStateStore()
19-
private readonly gcStateStore = useGcStateStore()
2018
private readonly controlApi: ControlApi
2119

2220
constructor(controlApi: ControlApi) {
2321
this.controlApi = controlApi;
2422
}
2523

26-
public getContinueAction(): ManualAction {
27-
const continueAction = this.gcStateStore.gcState.continueActions?.[0]
28-
const enabled = continueAction?.state === 'READY_AUTO'
29-
|| continueAction?.state === 'READY_MANUAL'
30-
|| continueAction?.state === 'WAITING'
31-
return {
32-
send: () => continueAction && this.controlApi.Continue(continueAction),
33-
enabled: enabled,
34-
label: "Continue",
35-
shortcutLabel: "NumpadEnter",
36-
team: undefined,
37-
}
38-
}
39-
4024
public getCommandAction(commandType: Command_TypeJson, forTeam?: TeamJson): ManualAction {
4125
return {
4226
send: () => this.sendCommand(commandType, forTeam),

frontend/src/providers/shortcuts/index.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import type {ManualActions} from "@/providers/manualActions";
2+
import type {ControlApi} from "@/providers/controlApi";
3+
import {useGcStateStore} from "@/store/gcState";
24

35

46
export class Shortcuts {
57
private readonly manualActions: ManualActions
8+
private readonly controlApi: ControlApi
9+
private readonly gcStateStore = useGcStateStore()
610
private enabled: boolean = true
711

8-
constructor(manualActions: ManualActions) {
12+
constructor(manualActions: ManualActions, controlApi: ControlApi) {
913
this.manualActions = manualActions
14+
this.controlApi = controlApi
1015
this.enabled = true
1116
}
1217

@@ -29,6 +34,17 @@ export class Shortcuts {
2934
return
3035
}
3136

37+
if (e.key === " ") {
38+
this.toggleAutoContinue()
39+
e.preventDefault()
40+
return
41+
} else if (e.code.startsWith('Digit') && !isNaN(Number(e.key))) {
42+
const id = Number(e.key)
43+
this.continueWithAction(id - 1)
44+
e.preventDefault()
45+
return
46+
}
47+
3248
switch (e.code) {
3349
case "Numpad0":
3450
this.manualActions.getCommandAction('STOP').send()
@@ -61,11 +77,21 @@ export class Shortcuts {
6177
this.manualActions.getCommandAction('TIMEOUT', 'BLUE').send()
6278
break
6379
case "NumpadEnter":
64-
this.manualActions.getContinueAction().send()
80+
this.continueWithAction(0)
6581
break
6682
default:
6783
return
6884
}
6985
e.preventDefault()
7086
}
87+
88+
private continueWithAction(id: number) {
89+
if (this.gcStateStore.gcState.continueActions!.length > id) {
90+
this.controlApi.Continue(this.gcStateStore.gcState.continueActions![id])
91+
}
92+
}
93+
94+
private toggleAutoContinue() {
95+
this.controlApi.ChangeConfig({autoContinue: !this.gcStateStore.config.autoContinue})
96+
}
7197
}

frontend/src/views/MatchView.vue

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
<script setup lang="ts">
2-
import {computed, inject, onMounted, onUnmounted} from "vue";
2+
import {computed} from "vue";
33
import ContinueActionButtonList from "@/components/match/ContinueActionButtonList.vue";
44
import AutoContinueInput from "@/components/match/AutoContinueInput.vue";
55
import MatchTeamTable from "@/components/match/MatchTeamTable.vue";
6-
import type {ControlApi} from "@/providers/controlApi";
76
import {useGcStateStore} from "@/store/gcState";
87
import {useUiStateStore} from "@/store/uiState";
98
import SwitchColorButton from "@/components/start/SwitchColorButton.vue";
@@ -15,51 +14,16 @@ import CommandButton from "@/components/control/CommandButton.vue";
1514
const store = useMatchStateStore()
1615
const gcStore = useGcStateStore()
1716
const uiStore = useUiStateStore()
18-
const control = inject<ControlApi>('control-api')
19-
20-
const continueWithAction = (id: number) => {
21-
if (gcStore.gcState.continueActions!.length > id) {
22-
control?.Continue(gcStore.gcState.continueActions![id])
23-
}
24-
}
2517
2618
const continueHints = computed(() => {
2719
return gcStore.gcState.continueHints || []
2820
})
2921
30-
const toggleAutoContinue = () => {
31-
control?.ChangeConfig({autoContinue: !gcStore.config.autoContinue})
32-
}
33-
3422
const halftime = computed(() => {
3523
return store.matchState.stage === 'NORMAL_HALF_TIME' ||
3624
store.matchState.stage === 'EXTRA_HALF_TIME'
3725
})
3826
39-
const keyListenerContinue = function (e: KeyboardEvent) {
40-
if (!e.ctrlKey) {
41-
return
42-
}
43-
if (e.key === " ") {
44-
toggleAutoContinue()
45-
e.preventDefault()
46-
} else {
47-
const id = Number(e.key)
48-
if (!isNaN(id)) {
49-
continueWithAction(id - 1)
50-
e.preventDefault()
51-
}
52-
}
53-
};
54-
55-
onMounted(() => {
56-
document.addEventListener('keydown', keyListenerContinue)
57-
})
58-
59-
onUnmounted(() => {
60-
document.removeEventListener('keydown', keyListenerContinue)
61-
})
62-
6327
</script>
6428

6529
<template>
@@ -82,13 +46,12 @@ onUnmounted(() => {
8246
<AutoContinueInput/>
8347
</div>
8448
<div class="row justify-evenly">
85-
Press
8649
<div class="q-my-auto">
50+
Press
8751
<em>
88-
Ctrl +
8952
<q-badge label="id" color="orange"/>
9053
</em>
91-
|
54+
or
9255
<em>NumpadEnter</em>
9356
to continue
9457
</div>

frontend/src/views/PlaceBallView.vue

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {isPausedStage} from "@/helpers";
66
import type {TeamJson} from "@/proto/state/ssl_gc_common_pb";
77
import type {ControlApi} from "@/providers/controlApi";
88
import type {Vector2} from "@/proto/geom/ssl_gc_geometry_pb";
9+
import NumberInput from "@/components/common/NumberInput.vue";
910
1011
const store = useMatchStateStore()
1112
const control = inject<ControlApi>('control-api')
@@ -55,18 +56,12 @@ const disable = computed(() => {
5556
</div>
5657

5758
<div class="row justify-evenly q-mt-md">
58-
<q-input
59-
input-class="text-center"
60-
dense
59+
<NumberInput
6160
label="X-Coordinate"
62-
type="number"
6361
v-model="newBallPos.x"
6462
/>
65-
<q-input
66-
input-class="text-center"
67-
dense
63+
<NumberInput
6864
label="Y-Coordinate"
69-
type="number"
7065
v-model="newBallPos.y"
7166
/>
7267
</div>

0 commit comments

Comments
 (0)