@lilaquadrat/design-core 0.1.0
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/README.md +236 -0
- package/html/index.html +25 -0
- package/html/index.mail.html +15 -0
- package/html/index.server.html +29 -0
- package/package.json +148 -0
- package/src/client-entry.ts +28 -0
- package/src/components/partials/action.partial.vue +30 -0
- package/src/components/partials/client-only.partial.vue +11 -0
- package/src/components/partials/error.partial.vue +73 -0
- package/src/components/partials/main-components.partial.vue +167 -0
- package/src/components/partials/mediadetection.partial.vue +71 -0
- package/src/components/partials/qrcode.partial.vue +35 -0
- package/src/customs.ts +12 -0
- package/src/env.d.ts +1 -0
- package/src/functions/PaymenyProviderFactory.ts +27 -0
- package/src/functions/lila-dom.ts +100 -0
- package/src/functions/shopify.provider.ts +378 -0
- package/src/functions/stripe.provider.ts +181 -0
- package/src/globals.d.ts +19 -0
- package/src/index.ts +47 -0
- package/src/interfaces/AppState.interface.ts +4 -0
- package/src/interfaces/EditorConfiguration.interface.ts +15 -0
- package/src/interfaces/EventDeclaration.interface.ts +19 -0
- package/src/interfaces/FrontendConfig.interface.ts +42 -0
- package/src/interfaces/GenericEvents.interface.ts +6 -0
- package/src/interfaces/GenericState.interface.ts +5 -0
- package/src/interfaces/IconsPartial.ts +9 -0
- package/src/interfaces/IdTokenExtended.interface.ts +7 -0
- package/src/interfaces/ModuleBaseProps.interface.ts +8 -0
- package/src/interfaces/PaymentProvider.interface.ts +13 -0
- package/src/libs/ActionNotice.ts +235 -0
- package/src/libs/Models.class.ts +482 -0
- package/src/main.ts +84 -0
- package/src/mixins/createCookieString.ts +78 -0
- package/src/mixins/createModelDeclaration.ts +29 -0
- package/src/mixins/createRouter.ts +30 -0
- package/src/mixins/date.ts +23 -0
- package/src/mixins/formatSize.ts +12 -0
- package/src/mixins/getAnchor.ts +14 -0
- package/src/mixins/getRoutes.ts +50 -0
- package/src/mixins/hasSlotContent.ts +32 -0
- package/src/mixins/hooks.ts +104 -0
- package/src/mixins/loadComponents.ts +107 -0
- package/src/mixins/logger.ts +32 -0
- package/src/mixins/replaceVariables.ts +19 -0
- package/src/mixins/scroll.ts +83 -0
- package/src/models/Address.model.ts +24 -0
- package/src/models/Contact.model.ts +22 -0
- package/src/models.ts +2 -0
- package/src/plugins/auth.ts +121 -0
- package/src/plugins/currency.ts +26 -0
- package/src/plugins/events.ts +156 -0
- package/src/plugins/filters.ts +43 -0
- package/src/plugins/inview.ts +351 -0
- package/src/plugins/replacer.ts +18 -0
- package/src/plugins/resize.ts +128 -0
- package/src/plugins/signupFlow.ts +89 -0
- package/src/plugins/traceable.ts +53 -0
- package/src/plugins/translations.ts +29 -0
- package/src/plugins/youtube.ts +80 -0
- package/src/routes.ts +132 -0
- package/src/server-entry.ts +113 -0
- package/src/stores/calls.store.ts +33 -0
- package/src/stores/cart.store.ts +186 -0
- package/src/stores/content.store.ts +83 -0
- package/src/stores/editor.store.ts +27 -0
- package/src/stores/files.store.ts +166 -0
- package/src/stores/main.store.ts +184 -0
- package/src/stores/user.store.ts +124 -0
- package/src/translations/de.ts +138 -0
- package/src/views/content.view.vue +219 -0
- package/src/views/download.view.vue +143 -0
- package/src/views/editor.view.vue +243 -0
- package/src/views/login.view.vue +82 -0
- package/src/views/signup-account.view.vue +64 -0
- package/tooling/cypress/config.ts +80 -0
- package/tooling/cypress/generate-manifest.js +5 -0
- package/tooling/cypress/manifest-utils.mjs +40 -0
- package/tooling/cypress/run-parallel.mjs +77 -0
- package/tooling/cypress/support/commands.ts +436 -0
- package/tooling/cypress/support/e2e.ts +17 -0
- package/tooling/eslint.config.js +223 -0
- package/tooling/preview.plugin.ts +134 -0
- package/tooling/ssr-test/index.mjs +391 -0
- package/tooling/stylelint.config.mjs +24 -0
- package/tooling/vite.ts +246 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { computed } from 'vue';
|
|
2
|
+
import { useRoute } from 'vue-router';
|
|
3
|
+
import useUserStore from '../stores/user.store';
|
|
4
|
+
import { auth } from './auth';
|
|
5
|
+
|
|
6
|
+
export type SignupStepKey = 'register' | 'account' | 'connect' | 'verify';
|
|
7
|
+
export type StepStatus = 'done' | 'current' | 'upcoming';
|
|
8
|
+
|
|
9
|
+
export interface SignupStep {
|
|
10
|
+
key: SignupStepKey
|
|
11
|
+
label: string
|
|
12
|
+
status: StepStatus
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const STEP_ORDER: SignupStepKey[] = ['register', 'account', 'connect', 'verify'];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* maps a lock state to the route the user has to complete next.
|
|
19
|
+
* keep this the single place that knows about the lock string values.
|
|
20
|
+
*/
|
|
21
|
+
function routeForLock (locked?: string): { name: string } {
|
|
22
|
+
|
|
23
|
+
if (locked === 'user-connect') return { name: 'signup-connect' };
|
|
24
|
+
if (locked === 'email-verified') return { name: 'signup-verify' };
|
|
25
|
+
|
|
26
|
+
return { name: 'members' };
|
|
27
|
+
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function useSignupFlow () {
|
|
31
|
+
|
|
32
|
+
const userStore = useUserStore();
|
|
33
|
+
const route = useRoute();
|
|
34
|
+
// DEBUG: ?step=<1|2|3|4> forces the active step (dev only)
|
|
35
|
+
const debugStep = computed<SignupStepKey | undefined>(() => {
|
|
36
|
+
|
|
37
|
+
const step = route?.query?.step;
|
|
38
|
+
|
|
39
|
+
if (import.meta.env.DEV && typeof step === 'string') {
|
|
40
|
+
|
|
41
|
+
const index = Number(step) - 1;
|
|
42
|
+
|
|
43
|
+
if (index >= 0 && index < STEP_ORDER.length) return STEP_ORDER[index];
|
|
44
|
+
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return undefined;
|
|
48
|
+
|
|
49
|
+
});
|
|
50
|
+
// the furthest step the user has reached, derived from auth + lock state.
|
|
51
|
+
// being authenticated implies register + account are done, even when there
|
|
52
|
+
// is no local customer (e.g. connecting from a different browser).
|
|
53
|
+
const currentKey = computed<SignupStepKey>(() => {
|
|
54
|
+
|
|
55
|
+
if (debugStep.value) return debugStep.value;
|
|
56
|
+
|
|
57
|
+
if (!auth.isAuth.value) {
|
|
58
|
+
return (userStore.customer || userStore.userData) ? 'account' : 'register';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (userStore.locked === 'user-connect') return 'connect';
|
|
62
|
+
if (userStore.locked === 'email-verified') return 'verify';
|
|
63
|
+
|
|
64
|
+
return 'verify';
|
|
65
|
+
|
|
66
|
+
});
|
|
67
|
+
const isComplete = computed(() => !debugStep.value && auth.isAuth.value && !userStore.locked);
|
|
68
|
+
// steps are monotonic: everything before the current step counts as done
|
|
69
|
+
const steps = computed<SignupStep[]>(() => {
|
|
70
|
+
|
|
71
|
+
const currentIndex = STEP_ORDER.indexOf(currentKey.value);
|
|
72
|
+
|
|
73
|
+
return STEP_ORDER.map((key, index) => ({
|
|
74
|
+
key,
|
|
75
|
+
label : `signup-step-${key}`,
|
|
76
|
+
status: isComplete.value || index < currentIndex
|
|
77
|
+
? 'done'
|
|
78
|
+
: index === currentIndex
|
|
79
|
+
? 'current'
|
|
80
|
+
: 'upcoming',
|
|
81
|
+
}));
|
|
82
|
+
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return { steps, currentKey, isComplete, routeForLock };
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { useSignupFlow, routeForLock };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import logger from '../mixins/logger';
|
|
2
|
+
import useCallStore from '../stores/calls.store';
|
|
3
|
+
import type { Ref } from 'vue';
|
|
4
|
+
|
|
5
|
+
const plugin = {
|
|
6
|
+
install: (): void => {
|
|
7
|
+
logger.plugins('traceable installed')
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
const useTraceable = () => {
|
|
11
|
+
|
|
12
|
+
const { addCall, updateCall } = useCallStore();
|
|
13
|
+
|
|
14
|
+
async function traceable<T> (promise: Promise<T>, traceId?: Ref<string|undefined>, options: {time: number} = {time: 3000}) {
|
|
15
|
+
|
|
16
|
+
// add the new call to vuex and fetch the id
|
|
17
|
+
const id = addCall();
|
|
18
|
+
|
|
19
|
+
if(traceId) traceId.value = id;
|
|
20
|
+
|
|
21
|
+
const minimum = new Promise<void>((resolve) => {
|
|
22
|
+
|
|
23
|
+
setTimeout(() => resolve(), options.time);
|
|
24
|
+
|
|
25
|
+
});
|
|
26
|
+
// the minimum promise will hold the execution of allsettled
|
|
27
|
+
const results = await Promise.allSettled([minimum, promise]);
|
|
28
|
+
|
|
29
|
+
// no error will be thrown, we need to check the result of the given promise
|
|
30
|
+
if (results[1].status === 'rejected') {
|
|
31
|
+
|
|
32
|
+
updateCall(id, 'rejected');
|
|
33
|
+
throw results[1].reason;
|
|
34
|
+
|
|
35
|
+
} else {
|
|
36
|
+
|
|
37
|
+
updateCall(id, 'resolved');
|
|
38
|
+
return results[1].value;
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
traceable
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default plugin;
|
|
51
|
+
export {
|
|
52
|
+
useTraceable
|
|
53
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createI18n } from 'vue-i18n';
|
|
2
|
+
import de from '../translations/de';
|
|
3
|
+
|
|
4
|
+
const i18n = createI18n({
|
|
5
|
+
legacy : false,
|
|
6
|
+
locale : 'de',
|
|
7
|
+
fallbackLocale: 'de',
|
|
8
|
+
missingWarn : false,
|
|
9
|
+
fallbackWarn : false,
|
|
10
|
+
missing : (_locale, key) => key,
|
|
11
|
+
messages : { de },
|
|
12
|
+
});
|
|
13
|
+
const { t } = i18n.global;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* merges project owned messages on top of the framework base.
|
|
17
|
+
*
|
|
18
|
+
* the design ships the wording for its own modules, the package only carries
|
|
19
|
+
* the strings it renders itself - so a project calls this once at startup with
|
|
20
|
+
* its own translation file.
|
|
21
|
+
*/
|
|
22
|
+
function addMessages (locale: string, messages: Record<string, unknown>) {
|
|
23
|
+
|
|
24
|
+
i18n.global.mergeLocaleMessage(locale, messages);
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default i18n;
|
|
29
|
+
export { t, addMessages };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import logger from '../mixins/logger';
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
class Youtube {
|
|
5
|
+
|
|
6
|
+
youtubeApiState = ref<boolean>(false);
|
|
7
|
+
|
|
8
|
+
loadState = ref<boolean>(false);
|
|
9
|
+
|
|
10
|
+
loadApi () {
|
|
11
|
+
|
|
12
|
+
if(this.loadState.value) return Promise.resolve();
|
|
13
|
+
|
|
14
|
+
return new Promise<void>((resolve) => {
|
|
15
|
+
|
|
16
|
+
const script = document.createElement('script');
|
|
17
|
+
|
|
18
|
+
script.src = 'https://www.youtube.com/iframe_api';
|
|
19
|
+
|
|
20
|
+
script.onload = () => {
|
|
21
|
+
resolve();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
document.body.appendChild(script);
|
|
25
|
+
this.loadState.value = true;
|
|
26
|
+
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* load the youtube iframe api and changes the youtubeApiState to true when all is ready
|
|
32
|
+
*/
|
|
33
|
+
addYoutube () {
|
|
34
|
+
|
|
35
|
+
if(!this.youtubeApiState.value) {
|
|
36
|
+
|
|
37
|
+
this.loadApi()
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const youtubePlugin = new Youtube();
|
|
46
|
+
const plugin = {
|
|
47
|
+
install: (): void => {
|
|
48
|
+
// this exists only for initialization purposes
|
|
49
|
+
logger.plugins('youtube installed');
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if(typeof window !== 'undefined') {
|
|
54
|
+
|
|
55
|
+
window.onYouTubeIframeAPIReady = () => {
|
|
56
|
+
|
|
57
|
+
youtubePlugin.youtubeApiState.value = true;
|
|
58
|
+
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function useYoutube () {
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
youtubeApiState: youtubePlugin.youtubeApiState,
|
|
67
|
+
/**
|
|
68
|
+
* loads the youtube iframe api and changes the youtubeApiState to true when all is ready
|
|
69
|
+
*/
|
|
70
|
+
addYoutube : () => youtubePlugin.addYoutube()
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export default plugin;
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
useYoutube
|
|
79
|
+
};
|
|
80
|
+
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { RouteRecordRaw } from 'vue-router';
|
|
2
|
+
import type { Component } from 'vue';
|
|
3
|
+
import ContentView from './views/content.view.vue';
|
|
4
|
+
import EditorView from './views/editor.view.vue';
|
|
5
|
+
import LoginView from './views/login.view.vue';
|
|
6
|
+
import SignupAccountView from './views/signup-account.view.vue';
|
|
7
|
+
import DownloadView from './views/download.view.vue';
|
|
8
|
+
|
|
9
|
+
const dynamicRoutes: readonly RouteRecordRaw[] = [
|
|
10
|
+
{
|
|
11
|
+
path : '/m/:pathMatch(.*)?',
|
|
12
|
+
name : 'members',
|
|
13
|
+
component: ContentView,
|
|
14
|
+
meta : {
|
|
15
|
+
contentType: 'members'
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
path : '/login-callback',
|
|
20
|
+
name : 'login-callback',
|
|
21
|
+
component: LoginView
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path : '/signup/account',
|
|
25
|
+
name : 'signup-account',
|
|
26
|
+
component: SignupAccountView
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path : '/signup',
|
|
30
|
+
name : 'signup',
|
|
31
|
+
component: ContentView,
|
|
32
|
+
meta : {
|
|
33
|
+
contentType: 'public'
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path : '/signup/connect',
|
|
38
|
+
name : 'signup-connect',
|
|
39
|
+
component: ContentView,
|
|
40
|
+
meta : {
|
|
41
|
+
contentType: 'public'
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path : '/signup/verify',
|
|
46
|
+
name : 'signup-verify',
|
|
47
|
+
component: ContentView,
|
|
48
|
+
meta : {
|
|
49
|
+
contentType: 'public'
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path : '/signup/done',
|
|
54
|
+
name : 'signup-done',
|
|
55
|
+
component: ContentView,
|
|
56
|
+
meta : {
|
|
57
|
+
contentType: 'public'
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
path : '/download/:app/:filename',
|
|
62
|
+
name : 'download',
|
|
63
|
+
component: DownloadView,
|
|
64
|
+
meta : {
|
|
65
|
+
contentType: 'members'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path : '',
|
|
70
|
+
name : 'public-home',
|
|
71
|
+
component: ContentView,
|
|
72
|
+
meta : {
|
|
73
|
+
contentType: 'public'
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
path : '/:pathMatch(.*)*',
|
|
78
|
+
name : 'public-content',
|
|
79
|
+
component: ContentView,
|
|
80
|
+
meta : {
|
|
81
|
+
contentType: 'public'
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
const editorRoutes: readonly RouteRecordRaw[] = [
|
|
86
|
+
{
|
|
87
|
+
path : '/:pathMatch(.*)*',
|
|
88
|
+
name : 'editor',
|
|
89
|
+
component: EditorView
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* the module gallery is the design itself, so the project provides the views.
|
|
95
|
+
* testView is dev only and is left out of a production build.
|
|
96
|
+
*/
|
|
97
|
+
function createPreviewRoutes (previewView: Component, testView?: Component): readonly RouteRecordRaw[] {
|
|
98
|
+
|
|
99
|
+
const testRoutes: RouteRecordRaw[] = testView && import.meta.env.DEV
|
|
100
|
+
? [
|
|
101
|
+
{
|
|
102
|
+
path : '/test/:name',
|
|
103
|
+
name : 'test-view',
|
|
104
|
+
component: testView,
|
|
105
|
+
meta : {
|
|
106
|
+
testOnly: true,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
]
|
|
110
|
+
: [];
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
...testRoutes,
|
|
114
|
+
{
|
|
115
|
+
path : '/:pathMatch(.*)*',
|
|
116
|
+
name : 'preview',
|
|
117
|
+
component: previewView
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
path : '',
|
|
121
|
+
name : 'preview-home',
|
|
122
|
+
component: previewView
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
dynamicRoutes,
|
|
130
|
+
editorRoutes,
|
|
131
|
+
createPreviewRoutes
|
|
132
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { renderToString } from 'vue/server-renderer';
|
|
2
|
+
import { getAppInstance, type AppComponents } from './main';
|
|
3
|
+
|
|
4
|
+
import { basename } from 'path';
|
|
5
|
+
import type { Content, Customers, List } from '@lilaquadrat/interfaces';
|
|
6
|
+
import type { RouteRecordRaw } from 'vue-router';
|
|
7
|
+
import useContentStore from './stores/content.store';
|
|
8
|
+
import { dynamicRoutes } from './routes';
|
|
9
|
+
import hooks from './mixins/hooks';
|
|
10
|
+
|
|
11
|
+
export async function render (url: string, context: any, contextData: Content[], recipientData: Customers, list: List, manifest: Record<string, string[]>, options: {cdnUrl: string, components: AppComponents, routes?: readonly RouteRecordRaw[]}) {
|
|
12
|
+
const { app, router, pinia } = getAppInstance(context, options.routes ?? dynamicRoutes, false, options.components);
|
|
13
|
+
|
|
14
|
+
hooks(router, { initAuth: false });
|
|
15
|
+
|
|
16
|
+
await router.push(url);
|
|
17
|
+
await router.isReady();
|
|
18
|
+
|
|
19
|
+
const contentStore = useContentStore();
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* for pdfs all images must be preloaded
|
|
23
|
+
* because there will be no hydration
|
|
24
|
+
*/
|
|
25
|
+
if(context.renderTarget === 'pdf') {
|
|
26
|
+
|
|
27
|
+
context.settings.preloadImages = true;
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if(contextData) contentStore.addMulti(contextData);
|
|
32
|
+
if(recipientData) contentStore.recipient = recipientData;
|
|
33
|
+
if(list) contentStore.list = list;
|
|
34
|
+
|
|
35
|
+
// passing SSR context object which will be available via useSSRContext()
|
|
36
|
+
// @vitejs/plugin-vue injects code into a component's setup() that registers
|
|
37
|
+
// itself on ctx.modules. After the render, ctx.modules would contain all the
|
|
38
|
+
// components that have been instantiated during this render call.
|
|
39
|
+
const ctx: { modules?: Set<string> } = {};
|
|
40
|
+
const html = await renderToString(app, ctx);
|
|
41
|
+
const initialState = JSON.stringify(pinia.state.value).replace(/</g, '\\u003c');
|
|
42
|
+
// the SSR manifest generated by Vite contains module -> chunk/asset mapping
|
|
43
|
+
// which we can then use to determine what files need to be preloaded for this
|
|
44
|
+
// request.
|
|
45
|
+
const preloadLinks = renderPreloadLinks(ctx.modules as Set<string>, manifest, options);
|
|
46
|
+
|
|
47
|
+
return { html, preloadLinks, initialState, url };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function renderPreloadLinks (modulesSet: Set<string>, manifest: Record<string, string[]>, options: {cdnUrl: string}) {
|
|
51
|
+
let links = '';
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
|
|
54
|
+
modulesSet?.forEach((id) => {
|
|
55
|
+
|
|
56
|
+
const files = manifest[id];
|
|
57
|
+
|
|
58
|
+
if (files) {
|
|
59
|
+
|
|
60
|
+
files.forEach((file: string) => {
|
|
61
|
+
|
|
62
|
+
if (!seen.has(file)) {
|
|
63
|
+
seen.add(file);
|
|
64
|
+
|
|
65
|
+
const filename = basename(file);
|
|
66
|
+
|
|
67
|
+
if (manifest[filename]) {
|
|
68
|
+
|
|
69
|
+
for (const depFile of manifest[filename]) {
|
|
70
|
+
links += renderPreloadLink(depFile, options.cdnUrl);
|
|
71
|
+
seen.add(depFile);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
links += renderPreloadLink(file, options.cdnUrl);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return links;
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function renderPreloadLink (file: string, base: string) {
|
|
89
|
+
if (!file) return '';
|
|
90
|
+
|
|
91
|
+
const fileExtMap: Record<string, { rel: string, as?: string, type?: string, crossorigin?: boolean }> = {
|
|
92
|
+
'.js' : { rel: 'modulepreload', crossorigin: true },
|
|
93
|
+
'.css' : { rel: 'stylesheet' },
|
|
94
|
+
'.woff' : { rel: 'preload', as: 'font', type: 'font/woff', crossorigin: true },
|
|
95
|
+
'.woff2': { rel: 'preload', as: 'font', type: 'font/woff2', crossorigin: true },
|
|
96
|
+
'.gif' : { rel: 'preload', as: 'image', type: 'image/gif' },
|
|
97
|
+
'.jpg' : { rel: 'preload', as: 'image', type: 'image/jpeg' },
|
|
98
|
+
'.jpeg' : { rel: 'preload', as: 'image', type: 'image/jpeg' },
|
|
99
|
+
'.png' : { rel: 'preload', as: 'image', type: 'image/png' }
|
|
100
|
+
};
|
|
101
|
+
const ext = Object.keys(fileExtMap).find(ext => file.endsWith(ext));
|
|
102
|
+
|
|
103
|
+
if (!ext) return '';
|
|
104
|
+
|
|
105
|
+
const { rel, as, type, crossorigin } = fileExtMap[ext];
|
|
106
|
+
let link = `<link rel="${rel}" href="${base}${file}"`;
|
|
107
|
+
|
|
108
|
+
if (as) link += ` as="${as}"`;
|
|
109
|
+
if (type) link += ` type="${type}"`;
|
|
110
|
+
if (crossorigin) link += ' crossorigin';
|
|
111
|
+
|
|
112
|
+
return `${link}>`;
|
|
113
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
|
|
5
|
+
export const useCallStore = defineStore('calls', () => {
|
|
6
|
+
|
|
7
|
+
const calls = ref<Record<string, 'pending' | 'resolved' | 'rejected'>>({});
|
|
8
|
+
|
|
9
|
+
function addCall () {
|
|
10
|
+
|
|
11
|
+
const id = uuid();
|
|
12
|
+
|
|
13
|
+
calls.value[id] = 'pending';
|
|
14
|
+
|
|
15
|
+
return id;
|
|
16
|
+
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function updateCall (id: string, state: 'pending' | 'resolved' | 'rejected') {
|
|
20
|
+
|
|
21
|
+
calls.value[id] = state;
|
|
22
|
+
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
calls,
|
|
27
|
+
addCall,
|
|
28
|
+
updateCall
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export default useCallStore;
|