@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.
Files changed (68) hide show
  1. package/dist/assets/index.css +10 -0
  2. package/dist/common-ui.es.js +16404 -0
  3. package/dist/common-ui.es.js.map +1 -0
  4. package/dist/common-ui.umd.js +9 -0
  5. package/dist/common-ui.umd.js.map +1 -0
  6. package/dist/components/HeatMap.types.d.ts +13 -0
  7. package/dist/components/PaginatingToolbar.types.d.ts +40 -0
  8. package/dist/components/SkMouseTrap.types.d.ts +3 -0
  9. package/dist/components/SkMouseTrapToolTip.types.d.ts +35 -0
  10. package/dist/components/SnackbarService.d.ts +11 -0
  11. package/dist/components/StudySession.types.d.ts +6 -0
  12. package/dist/components/auth/index.d.ts +4 -0
  13. package/dist/components/cardRendering/MarkdownRendererHelpers.d.ts +22 -0
  14. package/dist/components/studentInputs/BaseUserInput.d.ts +16 -0
  15. package/dist/components/studentInputs/RadioMultipleChoice.types.d.ts +5 -0
  16. package/dist/composables/CompositionViewable.d.ts +33 -0
  17. package/dist/composables/Displayable.d.ts +47 -0
  18. package/dist/composables/index.d.ts +2 -0
  19. package/dist/index.d.ts +36 -0
  20. package/dist/plugins/pinia.d.ts +5 -0
  21. package/dist/stores/useAuthStore.d.ts +225 -0
  22. package/dist/stores/useCardPreviewModeStore.d.ts +8 -0
  23. package/dist/stores/useConfigStore.d.ts +11 -0
  24. package/dist/utils/SkldrMouseTrap.d.ts +32 -0
  25. package/package.json +67 -0
  26. package/src/components/HeatMap.types.ts +15 -0
  27. package/src/components/HeatMap.vue +354 -0
  28. package/src/components/PaginatingToolbar.types.ts +48 -0
  29. package/src/components/PaginatingToolbar.vue +75 -0
  30. package/src/components/SkMouseTrap.types.ts +3 -0
  31. package/src/components/SkMouseTrap.vue +70 -0
  32. package/src/components/SkMouseTrapToolTip.types.ts +41 -0
  33. package/src/components/SkMouseTrapToolTip.vue +316 -0
  34. package/src/components/SnackbarService.ts +39 -0
  35. package/src/components/SnackbarService.vue +71 -0
  36. package/src/components/StudySession.types.ts +6 -0
  37. package/src/components/StudySession.vue +670 -0
  38. package/src/components/StudySessionTimer.vue +121 -0
  39. package/src/components/auth/UserChip.vue +106 -0
  40. package/src/components/auth/UserLogin.vue +141 -0
  41. package/src/components/auth/UserLoginAndRegistrationContainer.vue +85 -0
  42. package/src/components/auth/UserRegistration.vue +181 -0
  43. package/src/components/auth/index.ts +4 -0
  44. package/src/components/cardRendering/AudioAutoPlayer.vue +131 -0
  45. package/src/components/cardRendering/CardLoader.vue +123 -0
  46. package/src/components/cardRendering/CardViewer.vue +101 -0
  47. package/src/components/cardRendering/CodeBlockRenderer.vue +81 -0
  48. package/src/components/cardRendering/MarkdownRenderer.vue +46 -0
  49. package/src/components/cardRendering/MarkdownRendererHelpers.ts +114 -0
  50. package/src/components/cardRendering/MdTokenRenderer.vue +244 -0
  51. package/src/components/studentInputs/BaseUserInput.ts +71 -0
  52. package/src/components/studentInputs/MultipleChoiceOption.vue +127 -0
  53. package/src/components/studentInputs/RadioMultipleChoice.types.ts +6 -0
  54. package/src/components/studentInputs/RadioMultipleChoice.vue +168 -0
  55. package/src/components/studentInputs/TrueFalse.vue +27 -0
  56. package/src/components/studentInputs/UserInputNumber.vue +63 -0
  57. package/src/components/studentInputs/UserInputString.vue +89 -0
  58. package/src/components/studentInputs/fillInInput.vue +71 -0
  59. package/src/composables/CompositionViewable.ts +180 -0
  60. package/src/composables/Displayable.ts +133 -0
  61. package/src/composables/index.ts +2 -0
  62. package/src/index.ts +79 -0
  63. package/src/plugins/pinia.ts +24 -0
  64. package/src/stores/useAuthStore.ts +92 -0
  65. package/src/stores/useCardPreviewModeStore.ts +32 -0
  66. package/src/stores/useConfigStore.ts +60 -0
  67. package/src/utils/SkldrMouseTrap.ts +141 -0
  68. 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
+ }
@@ -0,0 +1,5 @@
1
+ declare module '*.vue' {
2
+ import type { DefineComponent } from 'vue';
3
+ const component: DefineComponent<{}, {}, any>;
4
+ export default component;
5
+ }