Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
refactor: switch from direct-vuex to vuex-typescript
`direct-vuex` is a typed wrapper around Vuex, but it has too much magic, and I can't figure out how to unit test it. `vuex-typescript` is more boilerplate, but less magic: we can still use `this.$store` as normal.
  • Loading branch information
arxanas committed Apr 19, 2020
1 parent 743c55a commit 9a8c0c0
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 74 deletions.
37 changes: 5 additions & 32 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -12,14 +12,14 @@
},
"dependencies": {
"core-js": "^2.6.11",
"direct-vuex": "^0.9.11",
"mem": "^5.1.1",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.1",
"vue-router": "^3.1.6",
"vuetify": "^2.2.19",
"vuex": "^3.1.3"
"vuex": "^3.1.3",
"vuex-typescript": "^3.0.2"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
Expand Down
5 changes: 2 additions & 3 deletions src/App.vue
Expand Up @@ -69,7 +69,7 @@ a {
<script lang="ts">
import Vue from "vue";
import Component from "vue-class-component";
import { getStore } from "./store";
import { dispatchRestoreState, dispatchSaveState } from "./store";
@Component({ name: "App" })
export default class extends Vue {
Expand All @@ -80,8 +80,7 @@ export default class extends Vue {
}
public async mounted() {
const store = getStore();
await store.dispatch.restoreState();
await dispatchRestoreState(this.$store);
}
}
</script>
16 changes: 11 additions & 5 deletions src/components/training/CharacterSelector.vue
Expand Up @@ -37,7 +37,12 @@
import Vue from "vue";
import Component from "vue-class-component";
import { Prop } from "vue-property-decorator";
import { getStore } from "../../store";
import {
commitSelectCharacter,
commitUnselectCharacter,
dispatchSaveState,
readSelectedCharacters,
} from "../../store";
import {
allCharacterMetadata,
CharacterId,
Expand Down Expand Up @@ -66,21 +71,22 @@ export default class extends Vue {
}
get selectedCharacterId() {
return getStore().state.local.selectedCharacters[this.gameId];
const selectedCharacters = readSelectedCharacters(this.$store);
return selectedCharacters[this.gameId];
}
public async onChange(
characterId: CharacterId<GameId> | undefined,
): Promise<void> {
if (characterId !== undefined) {
getStore().commit.selectCharacter({
commitSelectCharacter(this.$store, {
gameId: this.gameId,
characterId,
});
} else {
getStore().commit.unselectCharacter(this.gameId);
commitUnselectCharacter(this.$store, this.gameId);
}
await getStore().dispatch.saveState();
await dispatchSaveState(this.$store);
}
public filter(
Expand Down
7 changes: 4 additions & 3 deletions src/components/training/TrainingHeader.vue
Expand Up @@ -81,10 +81,10 @@
</template>

<script lang="ts">
import { getStore } from "@/store";
import { allCharacterMetadata, GameId } from "@/tech/AllCharacterMetadata";
import Vue from "vue";
import Component from "vue-class-component";
import { dispatchRestoreState, readSelectedCharacters } from "../../store";
import CharacterSelector from "./CharacterSelector.vue";
import TrainingPerformanceSelector from "./TrainingPerformanceSelector.vue";
Expand All @@ -99,7 +99,8 @@ export default class extends Vue {
public value: number | null | undefined = null;
get selectedCharacter() {
return getStore().state.local.selectedCharacters[this.gameId];
const selectedCharacters = readSelectedCharacters(this.$store);
return selectedCharacters[this.gameId];
}
get selectedCharacterName(): string {
Expand All @@ -112,7 +113,7 @@ export default class extends Vue {
}
public async created() {
await getStore().dispatch.restoreState();
await dispatchRestoreState(this.$store);
if (this.selectedCharacter === null) {
this.value = 2;
}
Expand Down
1 change: 0 additions & 1 deletion src/components/training/TrainingPanel.vue
Expand Up @@ -46,7 +46,6 @@
</template>

<script lang="ts">
import { getStore } from "@/store";
import allTechData, { getTechMetadata, TechId } from "@/tech/AllTechMetadata";
import {
AllTechVariants,
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Expand Up @@ -8,7 +8,7 @@ Vue.config.productionTip = false;

new Vue({
router,
store: store.original,
store,
vuetify,
render: h => h(App),
}).$mount("#app");
82 changes: 65 additions & 17 deletions src/store.ts
Expand Up @@ -5,9 +5,9 @@ import {
Performance,
updateItem,
} from "@/tech/SpacedRepetition";
import { createDirectStore } from "direct-vuex";
import Vue from "vue";
import Vuex, { ActionContext } from "vuex";
import { getStoreAccessors } from "vuex-typescript";
import {
CharacterId,
GameAndCharacterId,
Expand All @@ -32,7 +32,7 @@ export interface PracticeSet<T extends TechId> {
timestamp: number;
}

interface State {
interface MainState {
version: 1;
local: {
loaded: boolean;
Expand All @@ -44,7 +44,13 @@ interface State {
};
}

const defaultState: State = {
interface RootState {
modules: {
main: MainState;
};
}

const defaultState: MainState = {
version: 1,
local: {
loaded: false,
Expand All @@ -60,11 +66,24 @@ const defaultState: State = {

Vue.use(Vuex);

const { store } = createDirectStore({
type MainContext = ActionContext<MainState, RootState>;

const mainStore = {
namespaced: true,
state: defaultState,
getters: {
selectedCharacters(
state: MainState,
): MainState["local"]["selectedCharacters"] {
return state.local.selectedCharacters;
},
recordedPracticeSets(
state: MainState,
): MainState["remote"]["recordedPracticeSets"] {
return state.remote.recordedPracticeSets;
},
spacedRepetitionItems(
state: State,
state: MainState,
): Map<SerializedTechVariant, SpacedRepetitionItem> {
const result = new Map<SerializedTechVariant, SpacedRepetitionItem>();
for (const practiceSet of state.remote.recordedPracticeSets) {
Expand All @@ -86,7 +105,7 @@ const { store } = createDirectStore({
},
mutations: {
selectCharacter<T extends GameId>(
state: State,
state: MainState,
gameAndCharacterId: GameAndCharacterId<T>,
): void {
const { gameId, characterId } = gameAndCharacterId;
Expand All @@ -96,25 +115,25 @@ const { store } = createDirectStore({
// with `characterId as CharacterId<typeof gameId>` doesn't work.
selectedCharacters[gameId] = characterId;
},
unselectCharacter(state: State, gameId: GameId): void {
unselectCharacter(state: MainState, gameId: GameId): void {
state.local.selectedCharacters[gameId] = null;
},
recordPracticeSet<T extends TechId>(
state: State,
state: MainState,
practiceSet: PracticeSet<T>,
): void {
state.remote.recordedPracticeSets.push(practiceSet);
},
restoreState(state: State, newState: State) {
restoreState(state: MainState, newState: MainState) {
Object.assign(state, newState);
},
},
actions: {
async saveState(context: ActionContext<State, {}>): Promise<void> {
async saveState(context: MainContext): Promise<void> {
const state = { ...context.state, loaded: false };
localStorage.setItem("state", JSON.stringify(state));
},
async restoreState(context: ActionContext<State, {}>): Promise<void> {
async restoreState(context: MainContext): Promise<void> {
if (context.state.local.loaded) {
return;
}
Expand All @@ -123,7 +142,7 @@ const { store } = createDirectStore({
if (previousStateString === null) {
return;
}
const previousState: State = JSON.parse(previousStateString);
const previousState: MainState = JSON.parse(previousStateString);
const oldVersion: number = previousState.version;
const currentVersion = context.state.version;
if (oldVersion !== currentVersion) {
Expand All @@ -140,12 +159,41 @@ const { store } = createDirectStore({
});
},
},
};

export type MainStore = typeof mainStore;

const rootStore = new Vuex.Store({
modules: {
main: mainStore,
},
});

export default store;
export type RootStore = typeof rootStore;

export type Store = typeof store;
export default rootStore;

export function getStore(): Store {
return store;
}
const { commit, read, dispatch } = getStoreAccessors<MainState, RootState>(
"main",
);

export const readSpacedRepetitionItems = read(
mainStore.getters.spacedRepetitionItems,
);
export const readRecordedPracticeSets = read(
mainStore.getters.recordedPracticeSets,
);
export const readSelectedCharacters = read(
mainStore.getters.selectedCharacters,
);
export const dispatchRestoreState = dispatch(mainStore.actions.restoreState);
export const dispatchSaveState = dispatch(mainStore.actions.saveState);
export const commitRecordPracticeSet = commit(
mainStore.mutations.recordPracticeSet,
);
export const commitSelectCharacter = commit(
mainStore.mutations.selectCharacter,
);
export const commitUnselectCharacter = commit(
mainStore.mutations.unselectCharacter,
);

0 comments on commit 9a8c0c0

Please sign in to comment.