Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert vuex/keycodes store to pinia #1370

Merged
merged 10 commits into from
Nov 7, 2024
15 changes: 11 additions & 4 deletions src/components/Keycodes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,16 @@
</div>
</template>
<script>
import { mapState, mapGetters, mapMutations } from 'vuex';
import { mapState, mapMutations } from 'vuex';
import * as pinia from 'pinia';
import isUndefined from 'lodash/isUndefined';
import debounce from 'lodash/debounce';
import Keycode from '@/components/Keycode.vue';
import Space from '@/components/Space.vue';
import store from '@/store';

import { useKeycodesStore } from '../store/keycodes.js';

export default {
name: 'keycodes-component',
components: { Keycode, Space },
Expand All @@ -78,9 +81,13 @@ export default {
this.debouncedSetSearchFilter = debounce(this.setSearchFilter, 500);
},
computed: {
...mapGetters('keycodes', ['keycodes']),
...pinia.mapState(useKeycodesStore, [
'keycodes',
'searchFilter',
'searchCounters',
'active'
]),
...mapState('app', ['configuratorSettings']),
...mapState('keycodes', ['searchFilter', 'searchCounters', 'active']),
activeTab() {
return this.keycodesByGroup[this.active];
},
Expand Down Expand Up @@ -108,7 +115,7 @@ export default {
},
methods: {
...mapMutations('app', ['setMessage', 'stopListening', 'startListening']),
...mapMutations('keycodes', ['setSearchFilter', 'changeActive']),
...pinia.mapActions(useKeycodesStore, ['setSearchFilter', 'changeActive']),
getComponent(code) {
return isUndefined(code) ? Space : Keycode;
},
Expand Down
4 changes: 3 additions & 1 deletion src/components/VisualTesterKeymap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
import BaseKeymap from '@/components/BaseKeymap.vue';
import TesterKey from '@/components/TesterKey.vue';
import { Howl } from 'howler';
import { useKeycodesStore } from '../store/keycodes';

export default {
name: 'VisualTesterKeymap',
Expand Down Expand Up @@ -185,7 +186,8 @@ export default {
},
async mounted() {
this.createKeyListeners();
this.$store.commit('keycodes/updateKeycodeNames');
const keycodesStore = useKeycodesStore();
keycodesStore.updateKeycodeNames();
await this.init();
this.setSize(this.calculateMax(this.layout));
},
Expand Down
2 changes: 0 additions & 2 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Vuex from 'vuex';
import { createPinia, PiniaVuePlugin } from 'pinia';
import app from './modules/app';
import keymap from './modules/keymap';
import keycodes from './modules/keycodes';
import tester from './modules/tester';

Vue.use(Vuex);
Expand All @@ -15,7 +14,6 @@ export default new Vuex.Store({
modules: {
app,
keymap,
keycodes,
tester
},
state: {},
Expand Down
218 changes: 218 additions & 0 deletions src/store/keycodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import { defineStore } from 'pinia';
import isUndefined from 'lodash/isUndefined';
import store from '@/store';
import keymapExtras from '@/i18n/keymap_extras';

import ansi from './modules/keycodes/ansi';
import iso_jis from './modules/keycodes/iso-jis';
import quantum from './modules/keycodes/quantum';
import settings from './modules/keycodes/kb-settings';
import media from './modules/keycodes/app-media-mouse';
import steno from './modules/keycodes/steno';

const keycodePickerTabLayout = {
ANSI_ISO: [...ansi, ...iso_jis],
ISO_ANSI: [...iso_jis, ...ansi],
special: [...quantum, ...settings, ...media]
};

/**
* Validates the os keyboard layout and if not valid returns a default of 'keymap_us'.
* @returns {string}
*/
function getOSKeyboardLayout() {
let osKeyboardLayout = store.getters['app/osKeyboardLayout'];
if (isUndefined(osKeyboardLayout) || !keymapExtras[osKeyboardLayout]) {
const fallbackOSKeyboardLayout = 'keymap_us';
console.log(
`The stored OS keyboard layout value (${osKeyboardLayout}) is not a valid value! Falling back to '${fallbackOSKeyboardLayout}'.`
);
store.commit('app/setOSKeyboardLayout', fallbackOSKeyboardLayout);
osKeyboardLayout = fallbackOSKeyboardLayout;
}
return osKeyboardLayout;
}

function isANSI() {
return keymapExtras[getOSKeyboardLayout()].isANSI;
}

/**
* Remap keycodes to their Locale equivalent for the OS layout.
* @param {Object} keycodeLUT
* @param {KeycodeDefinition|KeycodeLabel|WidthPlaceholder} keycodeObject
* @returns {KeycodeDefinition|WidthPlaceholder|KeycodeLabel}
*/
function toLocaleKeycode(keycodeLUT, keycodeObject) {
console.assert(!isUndefined(keycodeLUT));
if (!keycodeObject.name || !keycodeObject.code) {
// Not a KeycodeDefinition; return as is
return keycodeObject;
}
if (keycodeLUT[keycodeObject.code]) {
// Clone in a shallow manner the original keycodeObject object and
// override the name, title, and possibly other fields
return { ...keycodeObject, ...keycodeLUT[keycodeObject.code] };
} else {
return keycodeObject;
}
}

/**
* Used to dynamically generate the tab data for they keycode
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Used to dynamically generate the tab data for they keycode
* Used to dynamically generate the tab data for the keycode

* display. This UI is customized based on the OS keyboard layout
* selected.
*
* @param {string} osKeyboardLayout
* @param {boolean} isSteno
* @returns {Array.<KeycodeDefinition|KeycodeLabel|WidthPlaceholder}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @returns {Array.<KeycodeDefinition|KeycodeLabel|WidthPlaceholder}
* @returns {Array.<KeycodeDefinition|KeycodeLabel|WidthPlaceholder>}

*/
function generateKeycodes(osKeyboardLayout, isSteno = false) {
store.commit('app/setIso', !isANSI());
const keycodes = [
...(isANSI()
? keycodePickerTabLayout.ANSI_ISO
: keycodePickerTabLayout.ISO_ANSI),
...keycodePickerTabLayout.special,
...(isSteno ? steno : [])
];
const { keycodeLUT } = keymapExtras[getOSKeyboardLayout()];
return keycodes.map((keycodeObject) =>
toLocaleKeycode(keycodeLUT, keycodeObject)
);
}

/**
* Counts the number of potential matches in each keycode array.
* The keycode arrays represent the source data used to render the keycodes
* display. This count is used to give hints of matches per tab in the UI.
*
* @param {string} filter
* @param {Array.<KeycodeDefinition|KeycodeLabel|WidthPlaceholder>} collection of keycode objects
* @returns
*/
function countMatches(filter, collection) {
filter = filter.toUpperCase();
return collection.reduce((matchCount, { code, name, title }) => {
if (!isUndefined(code)) {
if (
code.includes(filter) ||
name?.toUpperCase().includes(filter) ||
title?.toUpperCase().includes(filter)
) {
matchCount += 1;
}
}
return matchCount;
}, 0);
}

/**
* Use Options style for now.
*/
export const useKeycodesStore = defineStore('keycodes', {
/**
*
* @returns {KeycodeStoreState}
*/
state: () => ({
keycodes: [
...keycodePickerTabLayout.ANSI_ISO,
...keycodePickerTabLayout.special
],
searchFilter: '',
searchCounters: {
ANSI: 0,
'ISO/JIS': 0,
Quantum: 0,
KeyboardSettings: 0,
AppMediaMouse: 0
},
steno: false,
active: 'ANSI'
}),
getters: {
lookupKeyPressCode: () => (searchTerm) =>
this.lookupKeycode(searchTerm, true),
lookupKeycode:
(state) =>
(searchTerm, isKeys = false) =>
state.keycodes.find(
({ code, keys }) =>
code === searchTerm || (isKeys && keys && keys === searchTerm)
)
},
actions: {
changeActive(newActive) {
this.active = newActive;
},
enableSteno() {
this.steno = true;
this.keycodes = generateKeycodes(getOSKeyboardLayout(), this.steno);
},
disableSteno() {
this.steno = false;
this.keycodes = generateKeycodes(getOSKeyboardLayout(), this.steno);
},
updateKeycodeNames() {
this.keycodes = generateKeycodes(getOSKeyboardLayout(), this.steno);
},
setSearchFilter(newVal) {
this.searchFilter = newVal;
if (this.searchFilter !== '') {
this.searchCounters = {
ANSI: countMatches(this.searchFilter, ansi),
'ISO/JIS': countMatches(this.searchFilter, iso_jis),
Quantum: countMatches(this.searchFilter, quantum),
KeyboardSettings: countMatches(this.searchFilter, settings),
AppMediaMouse: countMatches(this.searchFilter, media)
};
}
}
Comment on lines +160 to +171
Copy link
Contributor

@precondition precondition Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pre-existing issue but the collections passed to countMatches (ansi, iso_jis, etc.) are read-only variables imported from ./modules/keycodes/###. Consequently, these keycode collections are not localized at all causing incorrect match counts to be reported.

For example, if I search for "ß" with the German OS layout, the ß key will be highlighted but the count in both ANSI and ISO tabs will be "(0)". Same story if I search for "Y" with the German OS layout; DE_Y and KC_Y (=DE_Z) are both highlighted but the ANSI tab will only report a single match because it successfully finds a "Y" in the uppercase version of KC_Y's name but KC_Z aka DE_Y has "Z" as its name in the ansi variable so the countMatches does not count it.

The solution would be to map the toLocalKeycode on each collection before passing it to the countMatches. I'll open a separate issue and open a PR for a possible implementation.

EDIT: See issue #1372

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

}
});

/**
* @typedef {Object} KeycodeLabel - used to label groups of keycodes
* @property {string} label - Label name
* @property {'label'} width - always the special indicator 'label'
* @property {boolean} [group] - group following keycodes under this
* @property {string} [icon] - font-awesome icon to display
* @property {string} [iconClass] - css class to apply
*/

/**
* @typedef {Object} WidthPlaceholder - used for spacing
* @property {number} width - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000.
*/

/**
* @typedef {Object} KeycodeDefinition - metadata about a keycode
* @property {string} name - UI display label for the keycode
* @property {string} code - QMK keycode defintion
* @property {string} [keys] - javascript keypress id. Used by keyboard handler
* @property {number} [width] - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000.
Comment on lines +185 to +194
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @typedef {Object} WidthPlaceholder - used for spacing
* @property {number} width - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000.
*/
/**
* @typedef {Object} KeycodeDefinition - metadata about a keycode
* @property {string} name - UI display label for the keycode
* @property {string} code - QMK keycode defintion
* @property {string} [keys] - javascript keypress id. Used by keyboard handler
* @property {number} [width] - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000.
* @typedef {Object} WidthPlaceholder - used for spacing
* @property {number} width - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000
*/
/**
* @typedef {Object} KeycodeDefinition - metadata about a keycode
* @property {string} name - UI display label for the keycode
* @property {string} code - QMK keycode definition
* @property {string} [keys] - javascript keypress id. Used by keyboard handler
* @property {number} [width] - width in Key Units * 1000. e.g. 1U = 1000, 2U = 2000

Typo + inconsistent use of trailing period

* @property {'text'|'layer'|'container'|'layer-container'} [type]
* @property {number} [layer]
* @property {string} [title] - help text for hover
*/

/**
* @typedef {{}
* & Record<'ISO/JIS', number>
* & Record<'ANSI', number>
* & Record<'Quantum', number>
* & Record<'KeyboardSettings', number>
* & Record<'AppMediaMouse', number>
* } SearchCounters
*
*/

/**
* @typedef {Object} KeycodeStoreState
* @property {Array.<KeycodeDefinition|KeycodeLabel|WidthPlaceholder>} keycodes - active keycodes
* @property {string} searchFilter - currently search filter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property {string} searchFilter - currently search filter
* @property {string} searchFilter - current query in the keycode picker search bar

* @property {SearchCounters} searchCounters - count of matching keycodes per tab
* @property {boolean} steno - is steno tab active
* @property {'ANSI'|'ISO/JIS'|'AppMediaMouse'|'Quantum'|'Steno'|'KeyboardSettings'} active - active tab
*/
43 changes: 24 additions & 19 deletions src/store/modules/app/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ import {
} from '@/store/modules/constants';
import { getPreferredLayout, getExclusionList } from '@/util';
import { localStorageSet, CONSTS } from '@/store/localStorage';
import { useKeycodesStore } from '../../keycodes.js';

const steno_keyboards = ['gergo', 'georgi'];
const steno_keyboards = new Set([
'gboards/gergo',
'gboards/georgi',
'stenokeyboards/the_uni/pro_micro',
'stenokeyboards/the_uni/rp_2040',
'stenokeyboards/the_uni/usb_c'
]);

const actions = {
/**
Expand Down Expand Up @@ -55,7 +62,10 @@ const actions = {
*/
async loadKeymapFromUrl(_, url) {
return fetch(url).then(async (r) => {
return await r.json();
if (r.ok) {
return await r.json();
}
console.error('Error fetching keymap from URL', r.statusText);
});
},
/**
Expand Down Expand Up @@ -86,13 +96,11 @@ const actions = {
commit('setLayout', nextLayout);

// enable and disable steno in keycode UI
const stenoCheck = steno_keyboards.reduce((_, keeb) => {
return { [keeb]: true };
}, {});
if (stenoCheck[keyboard]) {
this.commit('keycodes/enableSteno');
const keycodesStore = useKeycodesStore();
if (steno_keyboards.has(keyboard)) {
keycodesStore.enableSteno();
} else {
this.commit('keycodes/disableSteno');
keycodesStore.disableSteno();
}

if (clearKeymap) {
Expand Down Expand Up @@ -147,11 +155,12 @@ const actions = {
},
async changeOSKeyboardLayout({ dispatch, state, commit }, osLayout) {
commit('setOSKeyboardLayout', osLayout);
// Important to call keycodes/updateKeycodeNames *before* keymap/updateKeycodeNames.
this.commit('keycodes/updateKeycodeNames');
const keycodesStore = useKeycodesStore();

// Important to call keycodes/updatekeycodeNames *before* keymap/updateKeycodeNames.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Important to call keycodes/updatekeycodeNames *before* keymap/updateKeycodeNames.
// Important to call keycodesStore.updateKeycodeNames *before* keymap/updateKeycodeNames.

Updating the comment to reflect changes to the call signature

keycodesStore.updateKeycodeNames();
this.commit('keymap/updateKeycodeNames');
this.commit(
'keycodes/changeActive',
keycodesStore.changeActive(
state.configuratorSettings.iso ? 'ISO/JIS' : 'ANSI'
);
this.commit(
Expand Down Expand Up @@ -230,10 +239,8 @@ const actions = {
async initKeypressListener({ commit }) {
const store = this;
const keypressListener = new keypress.Listener();
const conf = generateKeypressCombos(
store,
store.getters['keycodes/keycodes']
);
const keycodesStore = useKeycodesStore();
const conf = generateKeypressCombos(store, keycodesStore.keycodes);
keypressListener.register_many(conf);
keypressListener.simple_combo('ctrl shift i', () => {
if (!store.state.app.isPreview) {
Expand Down Expand Up @@ -269,6 +276,4 @@ const actions = {
}
};

export default {
...actions
};
export default actions;
4 changes: 1 addition & 3 deletions src/store/modules/app/getters.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,4 @@ const getters = {
}
};

export default {
...getters
};
export default getters;
Loading
Loading