Skip to content

Commit 1262d33

Browse files
committed
UI: Convert images page to TypeScript
Signed-off-by: Mark Yen <mark.yen@suse.com>
1 parent 8dfe89d commit 1262d33

File tree

5 files changed

+135
-73
lines changed

5 files changed

+135
-73
lines changed

pkg/rancher-desktop/entry/store.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createStore, mapGetters, mapState, ModuleTree, Plugin } from 'vuex';
1+
import { createStore, mapActions, mapGetters, mapMutations, mapState, ModuleTree, Plugin } from 'vuex';
22

33
import * as ActionMenu from '../store/action-menu';
44
import * as ApplicationSettings from '../store/applicationSettings';
@@ -95,3 +95,60 @@ export function mapTypedState
9595
export function mapTypedState(namespace: string, arg: any) {
9696
return mapState(namespace, arg);
9797
}
98+
99+
/**
100+
* mapTypedMutations is a wrapper around mapMutations that is aware of the types
101+
* of the Vuex stores we have available, and returns the correctly typed values.
102+
* @see https://vuex.vuejs.org/guide/mutations.html#committing-mutations-in-components
103+
*/
104+
// mapTypedMutations('namespace', ['mutation', 'mutation'])
105+
export function mapTypedMutations
106+
<
107+
N extends keyof Modules,
108+
M extends Modules[N] extends { mutations: any } ? Modules[N]['mutations'] : never,
109+
K extends keyof M,
110+
>(namespace: N, keys: K[]): { [key in K]: (payload: Parameters<M[key]>[1]) => ReturnType<M[key]> };
111+
// mapTypedMutations('namespace', {key: 'name', key: (state) => (state.key)})
112+
export function mapTypedMutations
113+
<
114+
N extends keyof Modules,
115+
M extends Modules[N] extends { mutations: any } ? Modules[N]['mutations'] : never,
116+
K extends keyof M,
117+
G extends Record<string, K>,
118+
>(namespace: N, mappings: G): {
119+
[key in keyof G]: (payload: Parameters<M[G[key]]>[1]) => ReturnType<M[G[key]]>;
120+
};
121+
// Actual implementation defers to mapState
122+
export function mapTypedMutations(namespace: string, arg: any) {
123+
return mapMutations(namespace, arg);
124+
}
125+
126+
/**
127+
* mapTypedActions is a wrapper around mapActions that is aware of the types
128+
* of the Vuex stores we have available, and returns the correctly typed values.
129+
* @see https://vuex.vuejs.org/guide/actions.html#dispatching-actions-in-components
130+
*/
131+
export function mapTypedActions
132+
<
133+
N extends keyof Modules,
134+
M extends Modules[N] extends { actions: any } ? Modules[N]['actions'] : never,
135+
K extends keyof M,
136+
>(namespace: N, keys: K[]): {
137+
[key in K]: Parameters<M[key]> extends [state: any]
138+
? () => Promise<Awaited<ReturnType<M[key]>>> // no arguments
139+
: (payload: Parameters<M[key]>[1]) => Promise<Awaited<ReturnType<M[key]>>>
140+
};
141+
export function mapTypedActions
142+
<
143+
N extends keyof Modules,
144+
M extends Modules[N] extends { actions: any } ? Modules[N]['actions'] : never,
145+
K extends keyof M,
146+
G extends Record<string, K>,
147+
>(namespace: N, mappings: G): {
148+
[key in keyof G]: Parameters<M[G[key]]> extends [state: any]
149+
? () => Promise<Awaited<ReturnType<M[G[key]]>>> // no arguments
150+
: (payload: Parameters<M[G[key]]>[1]) => Promise<Awaited<ReturnType<M[G[key]]>>>;
151+
};
152+
export function mapTypedActions(namespace: string, arg: any) {
153+
return mapActions(namespace, arg) as any;
154+
}

pkg/rancher-desktop/pages/Images.vue

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,92 +17,90 @@
1717
</div>
1818
</template>
1919

20-
<script>
20+
<script lang="ts">
2121
2222
import _ from 'lodash';
23-
import { mapGetters } from 'vuex';
23+
import { defineComponent } from 'vue';
2424
2525
import { State as K8sState } from '@pkg/backend/backend';
2626
import Images from '@pkg/components/Images.vue';
2727
import { defaultSettings } from '@pkg/config/settings';
28+
import { mapTypedActions, mapTypedGetters, mapTypedMutations, mapTypedState } from '@pkg/entry/store';
29+
import { IpcRendererEvents } from '@pkg/typings/electron-ipc';
2830
import { ipcRenderer } from '@pkg/utils/ipcRenderer';
2931
30-
const ImageMangerStates = Object.freeze({
31-
UNREADY: 'IMAGE_MANAGER_UNREADY',
32-
READY: 'READY',
33-
});
32+
type Image = Parameters<IpcRendererEvents['images-changed']>[0][number];
33+
34+
enum ImageManagerStates {
35+
UNREADY = 'IMAGE_MANAGER_UNREADY',
36+
READY = 'READY',
37+
}
3438
35-
export default {
39+
export default defineComponent({
3640
components: { Images },
3741
data() {
3842
return {
3943
settings: defaultSettings,
40-
images: [],
41-
imageNamespaces: [],
44+
images: [] as Image[],
45+
imageNamespaces: [] as string[],
4246
supportsNamespaces: true,
4347
};
4448
},
4549
4650
computed: {
4751
state() {
48-
if (window.imagesListMock) {
52+
if ((window as any).imagesListMock) {
4953
// Override for screenshots
50-
return ImageMangerStates.READY;
54+
return ImageManagerStates.READY;
5155
}
5256
5357
if (![K8sState.STARTED, K8sState.DISABLED].includes(this.k8sState)) {
54-
return ImageMangerStates.UNREADY;
58+
return ImageManagerStates.UNREADY;
5559
}
5660
57-
return this.imageManagerState ? ImageMangerStates.READY : ImageMangerStates.UNREADY;
61+
return this.imageManagerState ? ImageManagerStates.READY : ImageManagerStates.UNREADY;
5862
},
59-
rancherImages() {
63+
rancherImages(): string[] {
6064
return this.images
61-
.filter(image => image.imageName.startsWith('rancher/'))
62-
.map(image => image.imageName);
65+
.map(image => image.imageName)
66+
.filter(name => name.startsWith('rancher/'));
6367
},
64-
installedExtensionImages() {
68+
installedExtensionImages(): string[] {
6569
return this.installedExtensions.map(image => image.id);
6670
},
67-
protectedImages() {
71+
protectedImages(): string[] {
6872
return [
6973
'moby/buildkit',
7074
'ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy',
7175
...this.rancherImages,
7276
...this.installedExtensionImages,
7377
];
7478
},
75-
...mapGetters('k8sManager', { k8sState: 'getK8sState' }),
76-
...mapGetters('imageManager', { imageManagerState: 'getImageManagerState' }),
77-
...mapGetters('extensions', ['installedExtensions']),
79+
...mapTypedState('imageManager', ['imageManagerState']),
80+
...mapTypedGetters('k8sManager', { k8sState: 'getK8sState' }),
81+
...mapTypedGetters('extensions', ['installedExtensions']),
7882
},
7983
8084
watch: {
8185
state: {
82-
handler(state) {
83-
this.$store.dispatch(
84-
'page/setHeader',
85-
{ title: this.t('images.title') },
86-
);
86+
handler(state: string) {
87+
this.setHeader({ title: this.t('images.title') });
8788
88-
if (!state || state === ImageMangerStates.UNREADY) {
89+
if (!state || state === ImageManagerStates.UNREADY) {
8990
return;
9091
}
9192
92-
this.$store.dispatch(
93-
'page/setAction',
94-
{ action: 'ImagesButtonAdd' },
95-
);
93+
this.setAction({ action: 'ImagesButtonAdd' });
9694
},
9795
immediate: true,
9896
},
9997
},
10098
10199
mounted() {
102100
ipcRenderer.on('images-changed', async(event, images) => {
103-
if (window.imagesListMock) {
101+
if ((window as any).imagesListMock) {
104102
// Override for screenshots
105-
images = await window.imagesListMock();
103+
images = await (window as any).imagesListMock();
106104
}
107105
if (_.isEqual(images, this.images)) {
108106
return;
@@ -118,47 +116,49 @@ export default {
118116
}
119117
});
120118
121-
ipcRenderer.on('images-check-state', (event, state) => {
122-
this.$store.dispatch('imageManager/setImageManagerState', state);
119+
ipcRenderer.on('images-check-state', (event, state: any) => {
120+
this.setImageManagerState(state);
123121
});
124122
125-
ipcRenderer.invoke('images-check-state').then((state) => {
126-
this.$store.dispatch('imageManager/setImageManagerState', state);
123+
ipcRenderer.invoke('images-check-state').then((state: any) => {
124+
this.setImageManagerState(state);
127125
});
128126
129-
ipcRenderer.on('settings-update', (event, settings) => {
127+
ipcRenderer.on('settings-update', (event, settings: any) => {
130128
// TODO: put in a status bar
131129
this.$data.settings = settings;
132130
this.checkSelectedNamespace();
133131
});
134132
135133
(async() => {
136-
this.$data.images = await ipcRenderer.invoke('images-mounted', true);
134+
this.images = await ipcRenderer.invoke('images-mounted', true);
137135
})();
138136
139-
ipcRenderer.on('images-namespaces', (event, namespaces) => {
137+
ipcRenderer.on('images-namespaces', (event, namespaces: string[]) => {
140138
// TODO: Use a specific message to indicate whether or not messages are supported.
141-
this.$data.imageNamespaces = namespaces;
142-
this.$data.supportsNamespaces = namespaces.length > 0;
139+
this.imageNamespaces = namespaces;
140+
this.supportsNamespaces = namespaces.length > 0;
143141
this.checkSelectedNamespace();
144142
});
145143
ipcRenderer.send('images-namespaces-read');
146-
ipcRenderer.on('settings-read', (event, settings) => {
147-
this.$data.settings = settings;
144+
ipcRenderer.on('settings-read', (event, settings: any) => {
145+
this.settings = settings;
148146
});
149147
ipcRenderer.send('settings-read');
150148
151149
ipcRenderer.on('extensions/changed', this.fetchExtensions);
152-
this.$store.dispatch('extensions/fetch');
150+
this.fetchExtensions();
153151
},
154152
beforeUnmount() {
155153
ipcRenderer.invoke('images-mounted', false);
156-
ipcRenderer.removeAllListeners('images-mounted');
157154
ipcRenderer.removeAllListeners('images-changed');
158155
ipcRenderer.removeListener('extensions/changed', this.fetchExtensions);
159156
},
160157
161158
methods: {
159+
...mapTypedActions('extensions', { fetchExtensions: 'fetch' }),
160+
...mapTypedActions('page', ['setAction', 'setHeader']),
161+
...mapTypedMutations('imageManager', { setImageManagerState: 'SET_IMAGE_MANAGER_STATE' }),
162162
checkSelectedNamespace() {
163163
if (!this.supportsNamespaces || this.imageNamespaces.length === 0) {
164164
// Nothing to verify yet
@@ -171,21 +171,18 @@ export default {
171171
{ images: { namespace: defaultNamespace } } );
172172
}
173173
},
174-
onShowAllImagesChanged(value) {
174+
onShowAllImagesChanged(value: boolean) {
175175
if (value !== this.settings.images.showAll) {
176176
ipcRenderer.invoke('settings-write',
177177
{ images: { showAll: value } } );
178178
}
179179
},
180-
onChangeNamespace(value) {
180+
onChangeNamespace(value: string) {
181181
if (value !== this.settings.images.namespace) {
182182
ipcRenderer.invoke('settings-write',
183183
{ images: { namespace: value } } );
184184
}
185185
},
186-
fetchExtensions() {
187-
this.$store.dispatch('extensions/fetch');
188-
},
189186
},
190-
};
187+
});
191188
</script>

pkg/rancher-desktop/store/imageManager.js

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { GetterTree, MutationTree } from 'vuex';
2+
3+
import { ActionTree, MutationsType } from './ts-helpers';
4+
5+
interface ImageManagerState {
6+
imageManagerState: boolean;
7+
}
8+
9+
export const state: () => ImageManagerState = () => ({ imageManagerState: false });
10+
11+
export const mutations = {
12+
SET_IMAGE_MANAGER_STATE(state: ImageManagerState, imageManagerState: boolean) {
13+
state.imageManagerState = imageManagerState;
14+
},
15+
} satisfies Partial<MutationsType<ImageManagerState>> & MutationTree<ImageManagerState>;
16+
17+
export const actions = {
18+
setImageManagerState({ commit }, imageManagerState: boolean) {
19+
commit('SET_IMAGE_MANAGER_STATE', imageManagerState);
20+
},
21+
} satisfies ActionTree<ImageManagerState, any, typeof mutations>;
22+
23+
export const getters = {
24+
getImageManagerState({ imageManagerState }: ImageManagerState) {
25+
return imageManagerState;
26+
},
27+
} satisfies GetterTree<ImageManagerState, any>;

pkg/rancher-desktop/typings/electron-ipc.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export interface IpcMainInvokeEvents {
127127
'k8s-progress': () => Readonly<{ current: number, max: number, description?: string, transitionTime?: Date }>;
128128

129129
// #region main/imageEvents
130-
'images-mounted': (mounted: boolean) => { imageName: string, tag: string, imageID: string, size: string }[];
130+
'images-mounted': (mounted: boolean) => (import('@pkg/backend/images/imageProcessor').imageType)[];
131131
'images-check-state': () => boolean;
132132
// #endregion
133133

0 commit comments

Comments
 (0)