Add TS config and I18n

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2024-06-14 19:39:54 +02:00
parent da0e0742f7
commit 55ab9ddf5d
Signed by: luzifer
SSH key fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
17 changed files with 322 additions and 1137 deletions

1107
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,13 +5,16 @@
"codejar": "^4.2.0", "codejar": "^4.2.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"vue": "^3.4.27", "vue": "^3.4.28",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.3" "vue-router": "^4.3.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.24.7", "@babel/eslint-parser": "^7.24.7",
"@types/bootstrap": "^5.2.10",
"@typescript-eslint/eslint-plugin": "^7.13.0", "@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0", "@typescript-eslint/parser": "^7.13.0",
"@vue/tsconfig": "^0.5.1",
"esbuild": "^0.21.5", "esbuild": "^0.21.5",
"esbuild-plugin-vue3": "^0.4.2", "esbuild-plugin-vue3": "^0.4.2",
"esbuild-sass-plugin": "^3.3.1", "esbuild-sass-plugin": "^3.3.1",

View file

@ -47,7 +47,7 @@
class="dropdown-item" class="dropdown-item"
href="#" href="#"
@click.prevent="logout" @click.prevent="logout"
>Sign-out</a> >{{ $t('nav.signOut') }}</a>
</li> </li>
</ul> </ul>
</li> </li>
@ -64,7 +64,7 @@ import { Dropdown } from 'bootstrap'
export default defineComponent({ export default defineComponent({
computed: { computed: {
profileImage(): string { profileImage(): string {
return this.$root.userInfo?.profile_image_url || '' return this.$root?.userInfo?.profile_image_url || ''
}, },
}, },
@ -76,7 +76,7 @@ export default defineComponent({
mounted() { mounted() {
if (this.isLoggedIn) { if (this.isLoggedIn) {
new Dropdown(this.$refs.userMenuToggle) new Dropdown(this.$refs.userMenuToggle as Element)
} }
}, },

View file

@ -0,0 +1,87 @@
<template>
<div class="nav flex-grow-1">
<template
v-for="section in navigation"
:key="section.header"
>
<div class="navHeading">
{{ section.header }}
</div>
<RouterLink
v-for="link in section.links"
:key="link.target"
:to="{name: link.target}"
class="nav-link"
>
<i :class="`${link.icon} fa-fw me-1`" />
{{ link.name }}
</RouterLink>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { RouterLink } from 'vue-router'
export default defineComponent({
components: { RouterLink },
data() {
return {
navigation: [
{
header: this.$t('menu.headers.core'),
links: [
{ icon: 'fas fa-chart-area', name: this.$t('menu.dashboard'), target: 'dashboard' },
{ icon: 'fas fa-cog', name: this.$t('menu.generalSettings'), target: 'generalSettings' },
],
},
{
header: this.$t('menu.headers.chatInteraction'),
links: [
{ icon: 'fas fa-envelope-open-text', name: this.$t('menu.autoMessages'), target: 'autoMessagesList' },
{ icon: 'fas fa-inbox', name: this.$t('menu.rules'), target: 'rulesList' },
],
},
{
header: this.$t('menu.headers.modules'),
links: [{ icon: 'fas fa-dice', name: this.$t('menu.raffles'), target: 'rafflesList' }],
},
],
}
},
name: 'TwitchBotEditorSideNav',
})
</script>
<style scoped>
.nav {
flex-direction: column;
flex-wrap: nowrap;
overflow-y: auto;
}
.nav>.nav-link {
align-items: center;
color: inherit;
display: flex;
padding-bottom: 0.75rem;
padding-left: 1.5rem;
padding-top: 0.75rem;
position: relative;
}
.nav>.nav-link.disabled {
color: var(--bs-nav-link-disabled-color);
}
.navHeading {
color: color-mix(in srgb, var(--bs-body-color) 50%, transparent);
font-size: 0.75rem;
font-weight: bold;
padding: 1.75rem 1rem 0.75rem;
text-transform: uppercase;
}
</style>

View file

@ -0,0 +1,11 @@
<template>
<!---->
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'TwitchBotEditor...',
})
</script>

View file

@ -22,7 +22,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue' import { defineComponent, type PropType } from 'vue'
import { Toast } from 'bootstrap' import { Toast } from 'bootstrap'
export type ToastContent = { export type ToastContent = {
@ -36,7 +36,7 @@ export type ToastContent = {
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
hdl: null, hdl: null as Toast | null,
} }
}, },
@ -72,11 +72,15 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.$refs.toast.addEventListener('hidden.bs.toast', () => this.$emit('hidden')) const t: Element = this.$refs.toast as Element
this.hdl = new Toast(this.$refs.toast, {
t.addEventListener('hidden.bs.toast', () => this.$emit('hidden'))
this.hdl = new Toast(t, {
autohide: this.toast.autoHide !== false, autohide: this.toast.autoHide !== false,
delay: this.toast.delay || 5000, delay: this.toast.delay || 5000,
}) })
this.hdl.show() this.hdl.show()
}, },

View file

@ -10,7 +10,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import Toast, { ToastContent } from './_toast.vue' import Toast, { type ToastContent } from './_toast.vue'
import BusEventTypes from '../helpers/busevents' import BusEventTypes from '../helpers/busevents'
import { defineComponent } from 'vue' import { defineComponent } from 'vue'
@ -30,9 +30,9 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.bus.on(BusEventTypes.Toast, (toast: ToastContent) => this.toasts.push({ this.bus.on(BusEventTypes.Toast, (toast: unknown) => this.toasts.push({
...toast, ...toast as ToastContent,
id: toast.id || crypto.randomUUID(), id: (toast as ToastContent).id || crypto.randomUUID(),
})) }))
}, },

View file

@ -4,47 +4,7 @@
<div class="layout"> <div class="layout">
<div class="layoutNav bg-body-tertiary d-flex"> <div class="layoutNav bg-body-tertiary d-flex">
<div class="nav flex-grow-1"> <SideNav />
<div class="layoutNavHeading">
Core
</div>
<a
href="#"
class="nav-link"
>
<i class="fas fa-cog fa-fw me-1" />
General Settings
</a>
<div class="layoutNavHeading">
Chat Interaction
</div>
<a
href="#"
class="nav-link"
>
<i class="fas fa-envelope-open-text fa-fw me-1" />
Auto-Messages
</a>
<a
href="#"
class="nav-link"
>
<i class="fas fa-inbox fa-fw me-1" />
Rules
</a>
<div class="layoutNavHeading">
Modules
</div>
<a
href="#"
class="nav-link"
>
<i class="fas fa-dice fa-fw me-1" />
Raffle
</a>
</div>
</div> </div>
<div class="layoutContent"> <div class="layoutContent">
<router-view /> <router-view />
@ -55,17 +15,10 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'
import HeadNav from './_headNav.vue' import HeadNav from './_headNav.vue'
import SideNav from './_sideNav.vue'
import Toaster from './_toaster.vue' import Toaster from './_toaster.vue'
export default defineComponent({
components: { HeadNav, Toaster },
name: 'TwitchBotEditorApp',
})
</script> </script>
<style scoped> <style scoped>
@ -93,32 +46,4 @@ export default defineComponent({
width: 225px; width: 225px;
z-index: 900; z-index: 900;
} }
.layoutNav>.nav {
flex-direction: column;
flex-wrap: nowrap;
overflow-y: auto;
}
.layoutNav>.nav>.nav-link {
align-items: center;
color: inherit;
display: flex;
padding-bottom: 0.75rem;
padding-left: 1.5rem;
padding-top: 0.75rem;
position: relative;
}
.layoutNav>.nav>.nav-link.disabled {
color: var(--bs-nav-link-disabled-color);
}
.layoutNavHeading {
color: color-mix(in srgb, var(--bs-body-color) 50%, transparent);
font-size: 0.75rem;
font-weight: bold;
padding: 1.75rem 1rem 0.75rem;
text-transform: uppercase;
}
</style> </style>

View file

@ -0,0 +1,13 @@
<template>
<div class="container">
<!---->
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'TwitchBotEditorDashboard',
})
</script>

View file

@ -27,10 +27,10 @@ export default defineComponent({
computed: { computed: {
authURL() { authURL() {
const scopes = [] const scopes: string[] = []
const params = new URLSearchParams() const params = new URLSearchParams()
params.set('client_id', this.$root.vars.TwitchClientID) params.set('client_id', this.$root?.vars?.TwitchClientID || '')
params.set('redirect_uri', window.location.href.split('#')[0].split('?')[0]) params.set('redirect_uri', window.location.href.split('#')[0].split('?')[0])
params.set('response_type', 'token') params.set('response_type', 'token')
params.set('scope', scopes.join(' ')) params.set('scope', scopes.join(' '))
@ -52,8 +52,8 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.bus.on(BusEventTypes.LoginProcessing, (loading: boolean) => { this.bus.on(BusEventTypes.LoginProcessing as string, (loading: unknown) => {
this.loading = loading this.loading = loading as boolean
}) })
}, },

31
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,31 @@
/* eslint-disable no-unused-vars */
import { Emitter, EventType } from 'mitt'
type EditorVars = {
DefaultBotScopes: string[]
IRCBadges: string[]
KnownEvents: string[]
TemplateFunctions: string[]
TwitchClientID: string
Version: string
}
type UserInfo = {
display_name: string
id: string
login: string
profile_image_url: string
}
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
bus: Emitter<Record<EventType, unknown>>
// On the $root
userInfo: UserInfo | null
vars: EditorVars | null
}
}
export {} // Important! See note.

View file

@ -1,4 +1,4 @@
import { ToastContent } from '../components/_toast.vue' import { type ToastContent } from '../components/_toast.vue'
/** /**
* Create the content of an error-toast * Create the content of an error-toast

16
src/i18n.ts Normal file
View file

@ -0,0 +1,16 @@
import { createI18n } from 'vue-i18n'
import en from './langs/en.json'
const cookieSet = Object.fromEntries(document.cookie.split('; ')
.map(el => el.split('=')
.map(el => decodeURIComponent(el))))
export default createI18n({
fallbackLocale: 'en',
globalInjection: true,
legacy: false,
locale: cookieSet.lang || navigator?.language || 'en',
messages: {
en,
},
})

17
src/langs/en.json Normal file
View file

@ -0,0 +1,17 @@
{
"menu": {
"headers": {
"chatInteraction": "Chat Interaction",
"core": "Core",
"modules": "Modules"
},
"autoMessages": "Auto-Messages",
"dashboard": "Dashboard",
"generalSettings": "General Settings",
"raffles": "Raffles",
"rules": "Rules"
},
"nav": {
"signOut": "Sign-Out"
}
}

View file

@ -12,6 +12,7 @@ import BusEventTypes from './helpers/busevents'
import ConfigNotifyListener from './helpers/configNotify' import ConfigNotifyListener from './helpers/configNotify'
import { errorToast } from './helpers/toasts' import { errorToast } from './helpers/toasts'
import i18n from './i18n'
import router from './router' import router from './router'
import App from './components/app.vue' import App from './components/app.vue'
import Login from './components/login.vue' import Login from './components/login.vue'
@ -188,5 +189,7 @@ const app = createApp({
}) })
app.config.globalProperties.bus = mitt() app.config.globalProperties.bus = mitt()
app.config.globalProperties.$foo = 'bare'
app.use(i18n)
app.use(router) app.use(router)
app.mount('#app') app.mount('#app')

View file

@ -1,28 +1,31 @@
import { createRouter, createMemoryHistory } from 'vue-router' import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router'
//import AuthView from './components/auth.vue' import Dashboard from './components/dashboard.vue'
//import ChatView from './components/chatview.vue'
const Root = {}
const routes = [ const routes = [
// { { component: Dashboard, name: 'dashboard', path: '/' },
// component: AuthView,
// path: '/', // General settings
// }, { component: {}, name: 'generalSettings', path: '/general-settings' },
// {
// component: ChatView, // Auto-Messages
// path: '/chat', { component: {}, name: 'autoMessagesList', path: '/auto-messages' },
// }, { component: {}, name: 'autoMessageEdit', path: '/auto-messages/edit/{id}' },
{ { component: {}, name: 'autoMessageNew', path: '/auto-messages/new' },
component: Root,
name: 'root', // Rules
path: '/', { component: {}, name: 'rulesList', path: '/rules' },
} { component: {}, name: 'rulesEdit', path: '/rules/edit/{id}' },
] { component: {}, name: 'rulesNew', path: '/rules/new' },
// Raffles
{ component: {}, name: 'rafflesList', path: '/raffles' },
{ component: {}, name: 'rafflesEdit', path: '/raffles/edit/{id}' },
{ component: {}, name: 'rafflesNew', path: '/raffles/new' },
] as RouteRecordRaw[]
const router = createRouter({ const router = createRouter({
history: createMemoryHistory(), history: createWebHashHistory(),
routes, routes,
}) })

9
tsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": [
"src/**/*"
],
"exclude": [
"src/components/_template.vue"
]
}