@vue-skuilder/common-ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index.css +10 -0
- package/dist/common-ui.es.js +16404 -0
- package/dist/common-ui.es.js.map +1 -0
- package/dist/common-ui.umd.js +9 -0
- package/dist/common-ui.umd.js.map +1 -0
- package/dist/components/HeatMap.types.d.ts +13 -0
- package/dist/components/PaginatingToolbar.types.d.ts +40 -0
- package/dist/components/SkMouseTrap.types.d.ts +3 -0
- package/dist/components/SkMouseTrapToolTip.types.d.ts +35 -0
- package/dist/components/SnackbarService.d.ts +11 -0
- package/dist/components/StudySession.types.d.ts +6 -0
- package/dist/components/auth/index.d.ts +4 -0
- package/dist/components/cardRendering/MarkdownRendererHelpers.d.ts +22 -0
- package/dist/components/studentInputs/BaseUserInput.d.ts +16 -0
- package/dist/components/studentInputs/RadioMultipleChoice.types.d.ts +5 -0
- package/dist/composables/CompositionViewable.d.ts +33 -0
- package/dist/composables/Displayable.d.ts +47 -0
- package/dist/composables/index.d.ts +2 -0
- package/dist/index.d.ts +36 -0
- package/dist/plugins/pinia.d.ts +5 -0
- package/dist/stores/useAuthStore.d.ts +225 -0
- package/dist/stores/useCardPreviewModeStore.d.ts +8 -0
- package/dist/stores/useConfigStore.d.ts +11 -0
- package/dist/utils/SkldrMouseTrap.d.ts +32 -0
- package/package.json +67 -0
- package/src/components/HeatMap.types.ts +15 -0
- package/src/components/HeatMap.vue +354 -0
- package/src/components/PaginatingToolbar.types.ts +48 -0
- package/src/components/PaginatingToolbar.vue +75 -0
- package/src/components/SkMouseTrap.types.ts +3 -0
- package/src/components/SkMouseTrap.vue +70 -0
- package/src/components/SkMouseTrapToolTip.types.ts +41 -0
- package/src/components/SkMouseTrapToolTip.vue +316 -0
- package/src/components/SnackbarService.ts +39 -0
- package/src/components/SnackbarService.vue +71 -0
- package/src/components/StudySession.types.ts +6 -0
- package/src/components/StudySession.vue +670 -0
- package/src/components/StudySessionTimer.vue +121 -0
- package/src/components/auth/UserChip.vue +106 -0
- package/src/components/auth/UserLogin.vue +141 -0
- package/src/components/auth/UserLoginAndRegistrationContainer.vue +85 -0
- package/src/components/auth/UserRegistration.vue +181 -0
- package/src/components/auth/index.ts +4 -0
- package/src/components/cardRendering/AudioAutoPlayer.vue +131 -0
- package/src/components/cardRendering/CardLoader.vue +123 -0
- package/src/components/cardRendering/CardViewer.vue +101 -0
- package/src/components/cardRendering/CodeBlockRenderer.vue +81 -0
- package/src/components/cardRendering/MarkdownRenderer.vue +46 -0
- package/src/components/cardRendering/MarkdownRendererHelpers.ts +114 -0
- package/src/components/cardRendering/MdTokenRenderer.vue +244 -0
- package/src/components/studentInputs/BaseUserInput.ts +71 -0
- package/src/components/studentInputs/MultipleChoiceOption.vue +127 -0
- package/src/components/studentInputs/RadioMultipleChoice.types.ts +6 -0
- package/src/components/studentInputs/RadioMultipleChoice.vue +168 -0
- package/src/components/studentInputs/TrueFalse.vue +27 -0
- package/src/components/studentInputs/UserInputNumber.vue +63 -0
- package/src/components/studentInputs/UserInputString.vue +89 -0
- package/src/components/studentInputs/fillInInput.vue +71 -0
- package/src/composables/CompositionViewable.ts +180 -0
- package/src/composables/Displayable.ts +133 -0
- package/src/composables/index.ts +2 -0
- package/src/index.ts +79 -0
- package/src/plugins/pinia.ts +24 -0
- package/src/stores/useAuthStore.ts +92 -0
- package/src/stores/useCardPreviewModeStore.ts +32 -0
- package/src/stores/useConfigStore.ts +60 -0
- package/src/utils/SkldrMouseTrap.ts +141 -0
- package/src/vue-shims.d.ts +5 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// stores/useAuthStore.ts
|
|
2
|
+
import { defineStore, setActivePinia } from 'pinia';
|
|
3
|
+
import { getDataLayer, UserDBInterface } from '@vue-skuilder/db';
|
|
4
|
+
import { getPinia } from '../plugins/pinia';
|
|
5
|
+
|
|
6
|
+
export interface AuthState {
|
|
7
|
+
_user: UserDBInterface | undefined;
|
|
8
|
+
loginAndRegistration: {
|
|
9
|
+
init: boolean;
|
|
10
|
+
loggedIn: boolean;
|
|
11
|
+
regDialogOpen: boolean;
|
|
12
|
+
loginDialogOpen: boolean;
|
|
13
|
+
};
|
|
14
|
+
onLoadComplete: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function getCurrentUser(): Promise<UserDBInterface> {
|
|
18
|
+
const store = useAuthStore();
|
|
19
|
+
|
|
20
|
+
if (!store.onLoadComplete) {
|
|
21
|
+
// Wait for initialization
|
|
22
|
+
let retries = 200;
|
|
23
|
+
const timeout = 50;
|
|
24
|
+
while (!store.onLoadComplete && retries > 0) {
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, timeout));
|
|
26
|
+
retries--;
|
|
27
|
+
}
|
|
28
|
+
if (!store.onLoadComplete) {
|
|
29
|
+
throw new Error('User initialization timed out');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return getDataLayer().getUserDB();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const useAuthStore = () => {
|
|
37
|
+
// Get the Pinia instance from the plugin
|
|
38
|
+
const pinia = getPinia();
|
|
39
|
+
if (pinia) {
|
|
40
|
+
setActivePinia(pinia);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Return the store
|
|
44
|
+
return defineStore('auth', {
|
|
45
|
+
state: (): AuthState => ({
|
|
46
|
+
_user: undefined as UserDBInterface | undefined,
|
|
47
|
+
loginAndRegistration: {
|
|
48
|
+
init: false,
|
|
49
|
+
loggedIn: false,
|
|
50
|
+
regDialogOpen: false,
|
|
51
|
+
loginDialogOpen: false,
|
|
52
|
+
},
|
|
53
|
+
onLoadComplete: false,
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
actions: {
|
|
57
|
+
async init() {
|
|
58
|
+
try {
|
|
59
|
+
this._user = getDataLayer().getUserDB();
|
|
60
|
+
|
|
61
|
+
this.loginAndRegistration.loggedIn = this._user.isLoggedIn();
|
|
62
|
+
|
|
63
|
+
this.onLoadComplete = true;
|
|
64
|
+
this.loginAndRegistration.init = true;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.error('Failed to initialize auth store:', e);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
setLoginDialog(open: boolean) {
|
|
71
|
+
this.loginAndRegistration.loginDialogOpen = open;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
setRegDialog(open: boolean) {
|
|
75
|
+
this.loginAndRegistration.regDialogOpen = open;
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
getters: {
|
|
80
|
+
currentUser: async () => getCurrentUser(),
|
|
81
|
+
isLoggedIn: (state) => state.loginAndRegistration.loggedIn,
|
|
82
|
+
isInitialized: (state) => state.loginAndRegistration.init,
|
|
83
|
+
status: (state) => {
|
|
84
|
+
return {
|
|
85
|
+
loggedIn: state.loginAndRegistration.loggedIn,
|
|
86
|
+
init: state.loginAndRegistration.init,
|
|
87
|
+
user: state._user,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
})();
|
|
92
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// common-ui/src/stores/useCardPreviewModeStore.ts
|
|
2
|
+
import { defineStore, setActivePinia } from 'pinia';
|
|
3
|
+
import { getPinia } from '../plugins/pinia';
|
|
4
|
+
|
|
5
|
+
export interface PreviewModeState {
|
|
6
|
+
previewMode: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const useCardPreviewModeStore = () => {
|
|
10
|
+
// Get the Pinia instance from the plugin
|
|
11
|
+
const pinia = getPinia();
|
|
12
|
+
if (pinia) {
|
|
13
|
+
setActivePinia(pinia);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Return the store
|
|
17
|
+
return defineStore('previewMode', {
|
|
18
|
+
state: (): PreviewModeState => ({
|
|
19
|
+
previewMode: false,
|
|
20
|
+
}),
|
|
21
|
+
actions: {
|
|
22
|
+
setPreviewMode(mode: boolean) {
|
|
23
|
+
this.previewMode = mode;
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
getters: {
|
|
27
|
+
isPreviewMode(): boolean {
|
|
28
|
+
return this.previewMode;
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
})();
|
|
32
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// stores/useConfigStore.ts
|
|
2
|
+
import { defineStore, setActivePinia } from 'pinia';
|
|
3
|
+
import { getCurrentUser } from './useAuthStore';
|
|
4
|
+
import { UserConfig } from '@vue-skuilder/db';
|
|
5
|
+
import { getPinia } from '../plugins/pinia';
|
|
6
|
+
|
|
7
|
+
export const useConfigStore = () => {
|
|
8
|
+
// Get the Pinia instance from the plugin
|
|
9
|
+
const pinia = getPinia();
|
|
10
|
+
if (pinia) {
|
|
11
|
+
setActivePinia(pinia);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Return the store
|
|
15
|
+
return defineStore('config', {
|
|
16
|
+
state: () => ({
|
|
17
|
+
config: {
|
|
18
|
+
darkMode: false,
|
|
19
|
+
likesConfetti: false,
|
|
20
|
+
} as UserConfig,
|
|
21
|
+
}),
|
|
22
|
+
|
|
23
|
+
actions: {
|
|
24
|
+
updateConfig(newConfig: UserConfig) {
|
|
25
|
+
this.config = newConfig;
|
|
26
|
+
},
|
|
27
|
+
async updateDarkMode(darkMode: boolean) {
|
|
28
|
+
this.config.darkMode = darkMode;
|
|
29
|
+
const user = await getCurrentUser();
|
|
30
|
+
await user.setConfig({
|
|
31
|
+
darkMode,
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async updateLikesConfetti(likesConfetti: boolean) {
|
|
36
|
+
this.config.likesConfetti = likesConfetti;
|
|
37
|
+
const user = await getCurrentUser();
|
|
38
|
+
await user.setConfig({
|
|
39
|
+
likesConfetti,
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async hydrate() {
|
|
44
|
+
const user = await getCurrentUser();
|
|
45
|
+
const cfg = await user.getConfig();
|
|
46
|
+
console.log(`user config: ${JSON.stringify(cfg)}`);
|
|
47
|
+
this.updateConfig(cfg);
|
|
48
|
+
},
|
|
49
|
+
async init() {
|
|
50
|
+
await this.hydrate();
|
|
51
|
+
},
|
|
52
|
+
resetDefaults() {
|
|
53
|
+
this.config = {
|
|
54
|
+
darkMode: false,
|
|
55
|
+
likesConfetti: false,
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
})();
|
|
60
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Mousetrap from 'mousetrap';
|
|
2
|
+
import 'mousetrap/plugins/global-bind/mousetrap-global-bind.js';
|
|
3
|
+
import { ExtendedKeyboardEvent, MousetrapInstance } from 'mousetrap';
|
|
4
|
+
|
|
5
|
+
export interface HotKey extends HotKeyMetaData {
|
|
6
|
+
callback: (e: ExtendedKeyboardEvent, combo: string) => any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface HotKeyMetaData {
|
|
10
|
+
command: string;
|
|
11
|
+
hotkey: string | string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if focus is currently on an input element where hotkeys should be ignored
|
|
16
|
+
* @returns true if hotkeys should be ignored, false otherwise
|
|
17
|
+
*/
|
|
18
|
+
function inputElementIsFocused(): boolean {
|
|
19
|
+
const activeElement = document.activeElement;
|
|
20
|
+
|
|
21
|
+
// Special handling for checkbox and radio inputs
|
|
22
|
+
if (
|
|
23
|
+
activeElement instanceof HTMLInputElement &&
|
|
24
|
+
(activeElement.type === 'checkbox' || activeElement.type === 'radio')
|
|
25
|
+
) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
activeElement instanceof HTMLElement &&
|
|
31
|
+
(activeElement.tagName === 'INPUT' ||
|
|
32
|
+
activeElement.tagName === 'TEXTAREA' ||
|
|
33
|
+
activeElement.tagName === 'SELECT' ||
|
|
34
|
+
activeElement.isContentEditable)
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class SkldrMouseTrap {
|
|
39
|
+
private static _instance: SkldrMouseTrap;
|
|
40
|
+
|
|
41
|
+
private mouseTrap: MousetrapInstance;
|
|
42
|
+
private hotkeys: HotKey[];
|
|
43
|
+
|
|
44
|
+
private constructor() {
|
|
45
|
+
this.mouseTrap = new Mousetrap();
|
|
46
|
+
this.hotkeys = [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public static get commands(): HotKeyMetaData[] {
|
|
50
|
+
return SkldrMouseTrap.instance().hotkeys.map((hk) => {
|
|
51
|
+
return {
|
|
52
|
+
command: hk.command,
|
|
53
|
+
hotkey: hk.hotkey,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add keyboard bindings without resetting existing ones
|
|
60
|
+
* @param hk Single hotkey or array of hotkeys to bind
|
|
61
|
+
*/
|
|
62
|
+
public static addBinding(hk: HotKey | HotKey[]) {
|
|
63
|
+
const hotkeys = Array.isArray(hk) ? hk : [hk];
|
|
64
|
+
const instance = SkldrMouseTrap.instance();
|
|
65
|
+
|
|
66
|
+
// Add to internal registry
|
|
67
|
+
instance.hotkeys = [...instance.hotkeys, ...hotkeys];
|
|
68
|
+
|
|
69
|
+
// Bind each hotkey
|
|
70
|
+
hotkeys.forEach((k) => {
|
|
71
|
+
Mousetrap.bindGlobal(k.hotkey, (a, b) => {
|
|
72
|
+
// Skip execution if focus is on input elements
|
|
73
|
+
if (inputElementIsFocused()) {
|
|
74
|
+
console.log(`Ignoring hotkey ${k.hotkey} while input element is focused`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// console.log(`Running ${k.hotkey}`);
|
|
79
|
+
k.callback(a, b);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Remove specific keyboard binding(s) without affecting others
|
|
86
|
+
* @param hotkey Single hotkey or array of hotkeys to remove
|
|
87
|
+
*/
|
|
88
|
+
public static removeBinding(hotkey: string | string[]) {
|
|
89
|
+
const instance = SkldrMouseTrap.instance();
|
|
90
|
+
const currentHotkeys = [...instance.hotkeys];
|
|
91
|
+
|
|
92
|
+
if (
|
|
93
|
+
Array.isArray(hotkey) &&
|
|
94
|
+
!hotkey.every((k) => typeof k === 'string' || typeof k === 'number')
|
|
95
|
+
) {
|
|
96
|
+
// If it's an array of hotkey specifiers
|
|
97
|
+
hotkey.forEach((key) => {
|
|
98
|
+
// Remove from internal registry
|
|
99
|
+
instance.hotkeys = instance.hotkeys.filter((k) => {
|
|
100
|
+
return JSON.stringify(k.hotkey) !== JSON.stringify(key);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Unbind from Mousetrap
|
|
104
|
+
Mousetrap.unbind(key);
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
// Single hotkey removal (original implementation)
|
|
108
|
+
// Remove from internal registry
|
|
109
|
+
instance.hotkeys = currentHotkeys.filter((k) => {
|
|
110
|
+
// Convert both to JSON for comparison to handle arrays correctly
|
|
111
|
+
return JSON.stringify(k.hotkey) !== JSON.stringify(hotkey);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Unbind from Mousetrap
|
|
115
|
+
Mousetrap.unbind(hotkey);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reset all keyboard bindings
|
|
121
|
+
* @warning Consider using removeBinding() for targeted cleanup instead to avoid affecting other components
|
|
122
|
+
*/
|
|
123
|
+
public static reset() {
|
|
124
|
+
console.warn(
|
|
125
|
+
'SkldrMouseTrap.reset() may affect hotkeys registered by other components. ' +
|
|
126
|
+
'Consider using removeBinding() with specific hotkeys for better component isolation.'
|
|
127
|
+
);
|
|
128
|
+
Mousetrap.reset();
|
|
129
|
+
SkldrMouseTrap.instance().mouseTrap.reset();
|
|
130
|
+
SkldrMouseTrap.instance().hotkeys = [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private static instance(): SkldrMouseTrap {
|
|
134
|
+
if (SkldrMouseTrap._instance) {
|
|
135
|
+
return SkldrMouseTrap._instance;
|
|
136
|
+
} else {
|
|
137
|
+
SkldrMouseTrap._instance = new SkldrMouseTrap();
|
|
138
|
+
return SkldrMouseTrap._instance;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|