appsalutely 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/.editorconfig +9 -0
- package/.prettierrc.json +6 -0
- package/eslint.config.ts +22 -0
- package/package.json +45 -0
- package/src/components/AppBase.vue +139 -0
- package/src/components/DashboardPage.vue +22 -0
- package/src/components/FooterSection.vue +6 -0
- package/src/components/LoginForm.vue +56 -0
- package/src/components/NavDrawerLink.vue +18 -0
- package/src/components/OTPForm.vue +54 -0
- package/src/index.ts +22 -0
- package/src/stores/colorMode.ts +16 -0
- package/src/stores/footer.ts +41 -0
- package/src/stores/notify.ts +51 -0
- package/tsconfig.json +23 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +26 -0
package/.editorconfig
ADDED
package/.prettierrc.json
ADDED
package/eslint.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { globalIgnores } from 'eslint/config'
|
|
2
|
+
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
|
|
3
|
+
import pluginVue from 'eslint-plugin-vue'
|
|
4
|
+
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
|
|
5
|
+
|
|
6
|
+
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
|
|
7
|
+
// import { configureVueProject } from '@vue/eslint-config-typescript'
|
|
8
|
+
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
|
|
9
|
+
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
|
|
10
|
+
|
|
11
|
+
export default defineConfigWithVueTs(
|
|
12
|
+
{
|
|
13
|
+
name: 'app/files-to-lint',
|
|
14
|
+
files: ['**/*.{ts,mts,tsx,vue}'],
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']),
|
|
18
|
+
|
|
19
|
+
pluginVue.configs['flat/essential'],
|
|
20
|
+
vueTsConfigs.recommended,
|
|
21
|
+
skipFormatting,
|
|
22
|
+
)
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appsalutely",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"module": "./dist/index.js",
|
|
6
|
+
"main": "./dist/index.umd.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.umd.js"
|
|
13
|
+
},
|
|
14
|
+
"./index.css": "./dist/index.css"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "vite build && vue-tsc",
|
|
18
|
+
"type-check": "vue-tsc --build",
|
|
19
|
+
"lint": "eslint . --fix",
|
|
20
|
+
"format": "prettier --write src/"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"pinia": "^3.0.1",
|
|
24
|
+
"vue": "^3.5.13",
|
|
25
|
+
"vue-router": "^4.5.1",
|
|
26
|
+
"vuetify": "^3.8.5"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tsconfig/node22": "^22.0.1",
|
|
30
|
+
"@types/node": "^22.14.0",
|
|
31
|
+
"@vitejs/plugin-vue": "^5.2.4",
|
|
32
|
+
"@vue/eslint-config-prettier": "^10.2.0",
|
|
33
|
+
"@vue/eslint-config-typescript": "^14.5.0",
|
|
34
|
+
"@vue/tsconfig": "^0.7.0",
|
|
35
|
+
"eslint": "^9.22.0",
|
|
36
|
+
"eslint-plugin-vue": "~10.0.0",
|
|
37
|
+
"jiti": "^2.4.2",
|
|
38
|
+
"npm-run-all2": "^7.0.2",
|
|
39
|
+
"prettier": "3.5.3",
|
|
40
|
+
"typescript": "~5.8.0",
|
|
41
|
+
"vite": "^6.2.4",
|
|
42
|
+
"vite-plugin-vue-devtools": "^7.7.2",
|
|
43
|
+
"vue-tsc": "^2.2.8"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, onBeforeMount, watch, useSlots } from 'vue'
|
|
3
|
+
import { useRouter, RouterView } from 'vue-router'
|
|
4
|
+
import {
|
|
5
|
+
VApp,
|
|
6
|
+
VProgressLinear,
|
|
7
|
+
VAppBar,
|
|
8
|
+
VNavigationDrawer,
|
|
9
|
+
VList,
|
|
10
|
+
VMain,
|
|
11
|
+
VFooter,
|
|
12
|
+
VBtn,
|
|
13
|
+
VSnackbar,
|
|
14
|
+
} from 'vuetify/components'
|
|
15
|
+
import useColorMode from '../stores/colorMode'
|
|
16
|
+
import useFooter from '../stores/footer'
|
|
17
|
+
import useNotify from '../stores/notify'
|
|
18
|
+
|
|
19
|
+
const colorMode = useColorMode()
|
|
20
|
+
const drawerOpen = defineModel<boolean>('drawerOpen', { default: false })
|
|
21
|
+
const drawerDocked = ref(false)
|
|
22
|
+
const footer = useFooter()
|
|
23
|
+
const navigating = ref(false)
|
|
24
|
+
const navigatingTimeout = ref<number | null>(null)
|
|
25
|
+
const notify = useNotify()
|
|
26
|
+
const router = useRouter()
|
|
27
|
+
const slots = useSlots()
|
|
28
|
+
|
|
29
|
+
watch(drawerDocked, (newValue) => {
|
|
30
|
+
localStorage.setItem('drawerDocked', String(newValue))
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
onBeforeMount(() => {
|
|
34
|
+
router.beforeEach(() => {
|
|
35
|
+
navigatingTimeout.value = setTimeout(() => {
|
|
36
|
+
navigating.value = true
|
|
37
|
+
}, 300)
|
|
38
|
+
})
|
|
39
|
+
router.afterEach(() => {
|
|
40
|
+
if (navigatingTimeout.value) {
|
|
41
|
+
clearTimeout(navigatingTimeout.value)
|
|
42
|
+
navigatingTimeout.value = null
|
|
43
|
+
}
|
|
44
|
+
navigating.value = false
|
|
45
|
+
})
|
|
46
|
+
drawerDocked.value = localStorage.getItem('drawerDocked') === 'true'
|
|
47
|
+
drawerOpen.value = drawerDocked.value
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
50
|
+
<template>
|
|
51
|
+
<VApp :theme="colorMode.mode">
|
|
52
|
+
<VProgressLinear
|
|
53
|
+
indeterminate
|
|
54
|
+
v-if="navigating"
|
|
55
|
+
class="mb-n1"
|
|
56
|
+
style="z-index: 100000; opacity: 0.5"
|
|
57
|
+
height="1"
|
|
58
|
+
/>
|
|
59
|
+
<VAppBar height="64">
|
|
60
|
+
<slot name="header" />
|
|
61
|
+
</VAppBar>
|
|
62
|
+
<VMain class="bg-surface-light">
|
|
63
|
+
<div class="d-flex flex-column pa-4 h-100">
|
|
64
|
+
<RouterView />
|
|
65
|
+
</div>
|
|
66
|
+
</VMain>
|
|
67
|
+
<VAppBar v-if="slots.footer" height="40" location="bottom">
|
|
68
|
+
<VFooter class="py-0 w-100 d-flex align-center">
|
|
69
|
+
<span
|
|
70
|
+
v-if="footer.current"
|
|
71
|
+
v-text="footer.current.text"
|
|
72
|
+
:class="footer.current.type ? {} : { ['text-' + footer.current.type]: true }"
|
|
73
|
+
style="text-overflow: ellipsis"
|
|
74
|
+
class="overflow-hidden text-no-wrap"
|
|
75
|
+
/>
|
|
76
|
+
<template v-if="footer.current && footer.current.actions">
|
|
77
|
+
<VBtn
|
|
78
|
+
v-for="(action, index) of Object.entries(footer.current.actions)"
|
|
79
|
+
:text="action[0]"
|
|
80
|
+
@click="action[1]"
|
|
81
|
+
:key="index"
|
|
82
|
+
density="comfortable"
|
|
83
|
+
variant="tonal"
|
|
84
|
+
class="mx-2 px-2 text-none"
|
|
85
|
+
:color="footer.current.type"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
<div class="me-auto" />
|
|
89
|
+
<slot name="footer" />
|
|
90
|
+
</VFooter>
|
|
91
|
+
</VAppBar>
|
|
92
|
+
<VNavigationDrawer
|
|
93
|
+
v-if="slots.drawer"
|
|
94
|
+
v-model="drawerOpen"
|
|
95
|
+
width="250"
|
|
96
|
+
rail-width="58"
|
|
97
|
+
:rail="drawerDocked"
|
|
98
|
+
:mobile="drawerDocked ? false : undefined"
|
|
99
|
+
>
|
|
100
|
+
<VList class="h-100 d-flex flex-column">
|
|
101
|
+
<slot name="drawer" />
|
|
102
|
+
<VBtn
|
|
103
|
+
@click="drawerDocked = !drawerDocked"
|
|
104
|
+
icon="mdi-pin-outline"
|
|
105
|
+
rounded="rounded"
|
|
106
|
+
density="comfortable"
|
|
107
|
+
:title="drawerDocked ? 'Undock sidebar' : 'Dock sidebar'"
|
|
108
|
+
class="my-1 mx-3 ms-auto"
|
|
109
|
+
:variant="drawerDocked ? 'tonal' : 'text'"
|
|
110
|
+
/>
|
|
111
|
+
</VList>
|
|
112
|
+
</VNavigationDrawer>
|
|
113
|
+
<VSnackbar
|
|
114
|
+
v-for="(message, index) of notify.messages"
|
|
115
|
+
:key="index"
|
|
116
|
+
:model-value="true"
|
|
117
|
+
:text="message.text"
|
|
118
|
+
:color="message.type"
|
|
119
|
+
location="bottom end"
|
|
120
|
+
:style="{ bottom: `${index * 56}px` }"
|
|
121
|
+
:timeout="-1"
|
|
122
|
+
>
|
|
123
|
+
<template #actions>
|
|
124
|
+
<VBtn icon="mdi-close" @click="notify.remove(message.id!)" />
|
|
125
|
+
</template>
|
|
126
|
+
</VSnackbar>
|
|
127
|
+
</VApp>
|
|
128
|
+
</template>
|
|
129
|
+
<style>
|
|
130
|
+
html {
|
|
131
|
+
overflow: auto;
|
|
132
|
+
}
|
|
133
|
+
html[data-theme='dark'] {
|
|
134
|
+
color-scheme: dark;
|
|
135
|
+
}
|
|
136
|
+
html[data-theme='light'] {
|
|
137
|
+
color-scheme: light;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { VBreadcrumbs, VContainer } from 'vuetify/components'
|
|
3
|
+
import { defineProps, withDefaults } from 'vue'
|
|
4
|
+
|
|
5
|
+
withDefaults(
|
|
6
|
+
defineProps<{
|
|
7
|
+
breadcrumbs?: VBreadcrumbs['$props']['items']
|
|
8
|
+
fluid?: boolean
|
|
9
|
+
title?: string
|
|
10
|
+
}>(),
|
|
11
|
+
{
|
|
12
|
+
fluid: true,
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
</script>
|
|
16
|
+
<template>
|
|
17
|
+
<VContainer :fluid="fluid" class="h-100 d-flex flex-column py-0 h-100">
|
|
18
|
+
<VBreadcrumbs :items="breadcrumbs" density="comfortable" class="pa-0" v-if="breadcrumbs" />
|
|
19
|
+
<h1 v-if="title" v-text="title" class="text-h5 font-bold ma-1" />
|
|
20
|
+
<slot />
|
|
21
|
+
</VContainer>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { defineProps, withDefaults, defineModel, defineEmits, ref } from 'vue'
|
|
3
|
+
import { VBtn, VCard, VCardText, VForm, VTextField } from 'vuetify/components'
|
|
4
|
+
|
|
5
|
+
const username = defineModel<string>('username', { default: '' })
|
|
6
|
+
const password = defineModel<string>('password', { default: '' })
|
|
7
|
+
const emit = defineEmits<{
|
|
8
|
+
submit: [string, string]
|
|
9
|
+
}>()
|
|
10
|
+
withDefaults(
|
|
11
|
+
defineProps<{
|
|
12
|
+
title?: string
|
|
13
|
+
submitButtonText?: string
|
|
14
|
+
minWidth?: string | number
|
|
15
|
+
maxWidth?: string | number
|
|
16
|
+
loading?: boolean
|
|
17
|
+
}>(),
|
|
18
|
+
{
|
|
19
|
+
title: 'Login',
|
|
20
|
+
submitButtonText: 'Login',
|
|
21
|
+
},
|
|
22
|
+
)
|
|
23
|
+
const valid = ref(false)
|
|
24
|
+
</script>
|
|
25
|
+
<template>
|
|
26
|
+
<VCard :title="title" :min-width="minWidth" :max-width="maxWidth">
|
|
27
|
+
<VForm @submit.prevent="emit('submit', username, password)" v-model="valid">
|
|
28
|
+
<VCardText>
|
|
29
|
+
<VTextField
|
|
30
|
+
v-model="username"
|
|
31
|
+
label="Username"
|
|
32
|
+
:rules="[(v) => !!v || 'Username is required']"
|
|
33
|
+
persistent-placeholder
|
|
34
|
+
autofocus
|
|
35
|
+
/>
|
|
36
|
+
<VTextField
|
|
37
|
+
v-model="password"
|
|
38
|
+
label="Password"
|
|
39
|
+
type="password"
|
|
40
|
+
:rules="[(v) => !!v || 'Password is required']"
|
|
41
|
+
persistent-placeholder
|
|
42
|
+
/>
|
|
43
|
+
<div class="d-flex justify-center">
|
|
44
|
+
<VBtn
|
|
45
|
+
:disabled="!valid"
|
|
46
|
+
type="submit"
|
|
47
|
+
color="primary"
|
|
48
|
+
variant="flat"
|
|
49
|
+
:text="submitButtonText"
|
|
50
|
+
:loading="loading"
|
|
51
|
+
/>
|
|
52
|
+
</div>
|
|
53
|
+
</VCardText>
|
|
54
|
+
</VForm>
|
|
55
|
+
</VCard>
|
|
56
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
3
|
+
import { VListItem, VIcon } from 'vuetify/components'
|
|
4
|
+
|
|
5
|
+
defineProps<{
|
|
6
|
+
to: RouteLocationRaw
|
|
7
|
+
icon: string
|
|
8
|
+
text: string
|
|
9
|
+
}>()
|
|
10
|
+
</script>
|
|
11
|
+
<template>
|
|
12
|
+
<VListItem role="option" :to="to" slim>
|
|
13
|
+
<template #prepend>
|
|
14
|
+
<VIcon :icon="icon" />
|
|
15
|
+
</template>
|
|
16
|
+
<span class="text-no-wrap overflow-hidden">{{ text }}</span>
|
|
17
|
+
</VListItem>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { defineProps, withDefaults, defineModel, defineEmits, computed, watch } from 'vue'
|
|
3
|
+
import { VBtn, VCard, VCardText, VForm, VOtpInput } from 'vuetify/components'
|
|
4
|
+
|
|
5
|
+
const otp = defineModel<string>('otp', { default: '' })
|
|
6
|
+
const emit = defineEmits<{
|
|
7
|
+
submit: [string]
|
|
8
|
+
}>()
|
|
9
|
+
const props = withDefaults(
|
|
10
|
+
defineProps<{
|
|
11
|
+
title?: string
|
|
12
|
+
subtitle?: string
|
|
13
|
+
submitButtonText?: string
|
|
14
|
+
minWidth?: string | number
|
|
15
|
+
maxWidth?: string | number
|
|
16
|
+
loading?: boolean
|
|
17
|
+
length?: number
|
|
18
|
+
autosubmit?: boolean
|
|
19
|
+
}>(),
|
|
20
|
+
{
|
|
21
|
+
title: '2FA Verification',
|
|
22
|
+
subtitle: 'Enter the code from your authenticator app',
|
|
23
|
+
submitButtonText: 'Verify',
|
|
24
|
+
length: 6,
|
|
25
|
+
autosubmit: true,
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
const valid = computed(() => otp.value.length === props.length)
|
|
29
|
+
|
|
30
|
+
watch(otp, () => {
|
|
31
|
+
if (props.autosubmit && otp.value.length === props.length) {
|
|
32
|
+
emit('submit', otp.value)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
36
|
+
<template>
|
|
37
|
+
<VCard :title="title" :subtitle="subtitle" :min-width="minWidth" :max-width="maxWidth">
|
|
38
|
+
<VForm @submit.prevent="emit('submit', otp)" :model-value="valid">
|
|
39
|
+
<VCardText>
|
|
40
|
+
<VOtpInput v-model="otp" label="OTP" persistent-placeholder autofocus :length="length" />
|
|
41
|
+
<div class="d-flex justify-center">
|
|
42
|
+
<VBtn
|
|
43
|
+
:disabled="!valid"
|
|
44
|
+
type="submit"
|
|
45
|
+
color="primary"
|
|
46
|
+
variant="flat"
|
|
47
|
+
:text="submitButtonText"
|
|
48
|
+
:loading="loading"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
</VCardText>
|
|
52
|
+
</VForm>
|
|
53
|
+
</VCard>
|
|
54
|
+
</template>
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import AppBase from './components/AppBase.vue'
|
|
2
|
+
import DashboardPage from './components/DashboardPage.vue'
|
|
3
|
+
import FooterSection from './components/FooterSection.vue'
|
|
4
|
+
import LoginForm from './components/LoginForm.vue'
|
|
5
|
+
import NavDrawerLink from './components/NavDrawerLink.vue'
|
|
6
|
+
import OTPForm from './components/OTPForm.vue'
|
|
7
|
+
|
|
8
|
+
import useColorMode from './stores/colorMode'
|
|
9
|
+
import useFooter from './stores/footer'
|
|
10
|
+
import useNotify from './stores/notify'
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
AppBase,
|
|
14
|
+
DashboardPage,
|
|
15
|
+
FooterSection,
|
|
16
|
+
LoginForm,
|
|
17
|
+
NavDrawerLink,
|
|
18
|
+
OTPForm,
|
|
19
|
+
useColorMode,
|
|
20
|
+
useFooter,
|
|
21
|
+
useNotify,
|
|
22
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ref, watch } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
|
|
4
|
+
export default defineStore('colorMode', () => {
|
|
5
|
+
const mode = ref<'light' | 'dark'>(localStorage.getItem('colorMode') == 'dark' ? 'dark' : 'light')
|
|
6
|
+
function toggle() {
|
|
7
|
+
mode.value = mode.value === 'dark' ? 'light' : 'dark'
|
|
8
|
+
}
|
|
9
|
+
function update(newMode: 'light' | 'dark') {
|
|
10
|
+
localStorage.setItem('colorMode', newMode)
|
|
11
|
+
document.documentElement.setAttribute('data-theme', newMode)
|
|
12
|
+
}
|
|
13
|
+
watch(mode, (newMode) => update(newMode))
|
|
14
|
+
update(mode.value)
|
|
15
|
+
return { mode, toggle }
|
|
16
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ref, computed } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
|
|
4
|
+
export interface FooterMessage {
|
|
5
|
+
id?: string
|
|
6
|
+
text: string
|
|
7
|
+
type?: 'info' | 'success' | 'warning' | 'error'
|
|
8
|
+
timeout?: number
|
|
9
|
+
actions?: {
|
|
10
|
+
[key: string]: () => void
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default defineStore('footer', () => {
|
|
15
|
+
const messages = ref<FooterMessage[]>([])
|
|
16
|
+
const current = computed(() => {
|
|
17
|
+
if (messages.value.length > 0) {
|
|
18
|
+
return messages.value[messages.value.length - 1]
|
|
19
|
+
}
|
|
20
|
+
return null
|
|
21
|
+
})
|
|
22
|
+
function addMessage(message: FooterMessage) {
|
|
23
|
+
if (!message.id) {
|
|
24
|
+
message.id = new Date().getTime().toString()
|
|
25
|
+
}
|
|
26
|
+
if (message.timeout === undefined) {
|
|
27
|
+
message.timeout = 5000
|
|
28
|
+
}
|
|
29
|
+
messages.value.push(message)
|
|
30
|
+
if (message.timeout) {
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
removeMessage(message.id!)
|
|
33
|
+
}, message.timeout)
|
|
34
|
+
}
|
|
35
|
+
return message.id!
|
|
36
|
+
}
|
|
37
|
+
function removeMessage(id: string) {
|
|
38
|
+
messages.value = messages.value.filter((message) => message.id !== id)
|
|
39
|
+
}
|
|
40
|
+
return { messages, current, addMessage, removeMessage }
|
|
41
|
+
})
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
2
|
+
import { defineStore } from 'pinia'
|
|
3
|
+
|
|
4
|
+
export interface NotifyMessage {
|
|
5
|
+
id?: string
|
|
6
|
+
text: string
|
|
7
|
+
type?: 'info' | 'success' | 'warning' | 'error'
|
|
8
|
+
timeout?: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineStore('notify', () => {
|
|
12
|
+
const messages = ref<NotifyMessage[]>([])
|
|
13
|
+
const timeouts = ref<Record<string, number>>({})
|
|
14
|
+
function add(message: NotifyMessage) {
|
|
15
|
+
if (!message.id) {
|
|
16
|
+
message.id = new Date().getTime().toString()
|
|
17
|
+
} else {
|
|
18
|
+
remove(message.id)
|
|
19
|
+
}
|
|
20
|
+
if (message.timeout === undefined) {
|
|
21
|
+
message.timeout = 5000
|
|
22
|
+
}
|
|
23
|
+
messages.value.push(message)
|
|
24
|
+
if (message.timeout) {
|
|
25
|
+
timeouts.value[message.id] = setTimeout(() => {
|
|
26
|
+
remove(message.id!)
|
|
27
|
+
}, message.timeout)
|
|
28
|
+
}
|
|
29
|
+
return message.id!
|
|
30
|
+
}
|
|
31
|
+
function remove(id: string) {
|
|
32
|
+
messages.value = messages.value.filter((message) => message.id !== id)
|
|
33
|
+
if (timeouts.value[id]) {
|
|
34
|
+
clearTimeout(timeouts.value[id])
|
|
35
|
+
delete timeouts.value[id]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function info(text: string) {
|
|
39
|
+
return add({ text, type: 'info' })
|
|
40
|
+
}
|
|
41
|
+
function success(text: string) {
|
|
42
|
+
return add({ text, type: 'success' })
|
|
43
|
+
}
|
|
44
|
+
function warning(text: string) {
|
|
45
|
+
return add({ text, type: 'warning' })
|
|
46
|
+
}
|
|
47
|
+
function error(text: string) {
|
|
48
|
+
return add({ text, type: 'error' })
|
|
49
|
+
}
|
|
50
|
+
return { messages, add, remove, info, success, warning, error }
|
|
51
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
3
|
+
"include": ["src/**/*.ts", "src/**/*.vue"],
|
|
4
|
+
"exclude": ["src/**/__tests__/*"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"noEmit": false,
|
|
9
|
+
"emitDeclarationOnly": true,
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"skipLibCheck": true,
|
|
16
|
+
"strict": true
|
|
17
|
+
},
|
|
18
|
+
"references": [
|
|
19
|
+
{
|
|
20
|
+
"path": "./tsconfig.node.json"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig, type UserConfig } from 'vite'
|
|
2
|
+
import Vue from '@vitejs/plugin-vue'
|
|
3
|
+
|
|
4
|
+
const config: UserConfig = {
|
|
5
|
+
build: {
|
|
6
|
+
lib: {
|
|
7
|
+
entry: 'src/index.ts',
|
|
8
|
+
name: 'appsalutely',
|
|
9
|
+
fileName: 'index',
|
|
10
|
+
},
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
external: ['vue', 'vue-router', 'pinia', 'vuetify', 'vuetify/components'],
|
|
13
|
+
output: {
|
|
14
|
+
globals: {
|
|
15
|
+
vue: 'Vue',
|
|
16
|
+
'vue-router': 'VueRouter',
|
|
17
|
+
pinia: 'Pinia',
|
|
18
|
+
vuetify: 'Vuetify',
|
|
19
|
+
'vuetify/components': 'VuetifyComponents',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
plugins: [Vue()],
|
|
25
|
+
}
|
|
26
|
+
export default defineConfig(config)
|