работаем бля работаем
This commit is contained in:
20
new-client/src/app/App.vue
Normal file
20
new-client/src/app/App.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<Component :is="layoutComponent">
|
||||
<RouterView />
|
||||
</Component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DefaultLayout from '@shared/layouts/Default.vue'
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const layoutComponent = computed(() => {
|
||||
return route.meta.layout ?? DefaultLayout
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
35
new-client/src/app/Error.vue
Normal file
35
new-client/src/app/Error.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="error-app">
|
||||
<img class="error-app__image" src="/sad-pepe.png" alt="Oops!" draggable="false">
|
||||
|
||||
<p class="error-app__message">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
message: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.error-app {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
&__image {
|
||||
width: 120px;
|
||||
margin-bottom: 32px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&__message {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
new-client/src/app/Preloader.vue
Normal file
25
new-client/src/app/Preloader.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="preloader-app">
|
||||
<p>
|
||||
Loading...
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.preloader-app {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
> p {
|
||||
margin: auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
new-client/src/app/Updater.vue
Normal file
30
new-client/src/app/Updater.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="updater-app">
|
||||
<p v-if="checking">
|
||||
Checking updates...
|
||||
</p>
|
||||
<p v-else-if="!!lastUpdate">
|
||||
Update available: {{ lastUpdate.version }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUpdater } from '@shared/composables/use-updater'
|
||||
|
||||
const { checking, lastUpdate } = useUpdater()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.updater-app {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
> p {
|
||||
margin: auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 16px;
|
||||
background-color: var(--bg-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
21
new-client/src/app/bootstrap/app.ts
Normal file
21
new-client/src/app/bootstrap/app.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { useAuth } from '@shared/composables/use-auth'
|
||||
import { createApp } from 'vue'
|
||||
import App from '../App.vue'
|
||||
import routerPlugin, { router } from '../plugins/router'
|
||||
|
||||
const mountPoint = '#app'
|
||||
|
||||
export default async function () {
|
||||
const { authorized } = useAuth()
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(routerPlugin)
|
||||
|
||||
await router.isReady()
|
||||
|
||||
if (!authorized.value && router.currentRoute.value.meta.auth === undefined) {
|
||||
router.push('/auth/login')
|
||||
}
|
||||
|
||||
app.mount(mountPoint)
|
||||
}
|
||||
17
new-client/src/app/bootstrap/authorize.ts
Normal file
17
new-client/src/app/bootstrap/authorize.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import api from '@shared/api/client'
|
||||
import { useAuth } from '@shared/composables/use-auth'
|
||||
|
||||
export default async function () {
|
||||
const { setMe } = useAuth()
|
||||
|
||||
try {
|
||||
const response = await api.chad.authMe()
|
||||
|
||||
setMe(response.data)
|
||||
}
|
||||
catch (error) {
|
||||
if (error.error?.statusCode !== 401) {
|
||||
throw new Error('Authorization failed')
|
||||
}
|
||||
}
|
||||
}
|
||||
10
new-client/src/app/bootstrap/error.ts
Normal file
10
new-client/src/app/bootstrap/error.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import Error from '../Error.vue'
|
||||
|
||||
const mountPoint = '#app'
|
||||
|
||||
export default async function (message: string) {
|
||||
const app = createApp(Error, { message })
|
||||
|
||||
app.mount(mountPoint)
|
||||
}
|
||||
19
new-client/src/app/bootstrap/preloader.ts
Normal file
19
new-client/src/app/bootstrap/preloader.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { App } from 'vue'
|
||||
import { createApp } from 'vue'
|
||||
import Preloader from '../Preloader.vue'
|
||||
|
||||
const mountPoint = '#preloader'
|
||||
|
||||
let preloaderApp: App | undefined
|
||||
|
||||
export default {
|
||||
show() {
|
||||
preloaderApp = createApp(Preloader)
|
||||
|
||||
preloaderApp.mount(mountPoint)
|
||||
},
|
||||
hide() {
|
||||
preloaderApp?.unmount()
|
||||
preloaderApp = undefined
|
||||
},
|
||||
}
|
||||
23
new-client/src/app/bootstrap/updater.ts
Normal file
23
new-client/src/app/bootstrap/updater.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createApp } from 'vue'
|
||||
import { useUpdater } from '@/shared/composables/use-updater'
|
||||
import Updater from '../Updater.vue'
|
||||
|
||||
const mountPoint = '#updater'
|
||||
|
||||
export default async function () {
|
||||
const { lastUpdate, checkForUpdates } = useUpdater()
|
||||
|
||||
const updater = createApp(Updater)
|
||||
|
||||
updater.mount(mountPoint)
|
||||
|
||||
await checkForUpdates()
|
||||
|
||||
if (!lastUpdate.value) {
|
||||
updater.unmount()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
await lastUpdate.value.downloadAndInstall()
|
||||
}
|
||||
30
new-client/src/app/entry.ts
Normal file
30
new-client/src/app/entry.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import initializeApp from './bootstrap/app'
|
||||
import authorize from './bootstrap/authorize'
|
||||
import showError from './bootstrap/error'
|
||||
import preloader from './bootstrap/preloader'
|
||||
import checkUpdates from './bootstrap/updater'
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
await checkUpdates()
|
||||
|
||||
preloader.show()
|
||||
|
||||
await authorize()
|
||||
|
||||
initializeApp()
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error)
|
||||
|
||||
if (error instanceof Error && error.message) {
|
||||
showError(error.message)
|
||||
}
|
||||
else {
|
||||
showError('Something went wrong')
|
||||
}
|
||||
}
|
||||
finally {
|
||||
preloader.hide()
|
||||
}
|
||||
})()
|
||||
11
new-client/src/app/plugins/primevue.ts
Normal file
11
new-client/src/app/plugins/primevue.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { PrimeVueConfiguration } from 'primevue/config'
|
||||
import type { FunctionPlugin, Plugin } from 'vue'
|
||||
import PrimeVue from 'primevue/config'
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.use(PrimeVue as Plugin<PrimeVueConfiguration>, {
|
||||
unstyled: true,
|
||||
})
|
||||
},
|
||||
} as FunctionPlugin
|
||||
21
new-client/src/app/plugins/router.ts
Normal file
21
new-client/src/app/plugins/router.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { Component, FunctionPlugin } from 'vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { routes } from 'vue-router/auto-routes'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
layout?: Component
|
||||
auth?: false | 'guest'
|
||||
}
|
||||
}
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
export default {
|
||||
install(app) {
|
||||
app.use(router)
|
||||
},
|
||||
} as FunctionPlugin
|
||||
14
new-client/src/pages/auth.vue
Normal file
14
new-client/src/pages/auth.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AuthLayout from '@shared/layouts/Auth.vue'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
auth: 'guest',
|
||||
layout: AuthLayout,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
35
new-client/src/pages/auth/login.vue
Normal file
35
new-client/src/pages/auth/login.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<form class="login-page__form" @submit.prevent="">
|
||||
<div class="login-page__fields">
|
||||
<ChadInput placeholder="Login" error="Jopa" />
|
||||
<ChadInput placeholder="Password" />
|
||||
<ChadPasswordInput placeholder="Password" label="Test" />
|
||||
</div>
|
||||
|
||||
<ChadButton class="login-page__submit" full type="submit">
|
||||
Let's go
|
||||
</ChadButton>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ChadButton from '@shared/components/ui/Button.vue'
|
||||
import ChadInput from '@shared/components/ui/Input.vue'
|
||||
import ChadPasswordInput from '@shared/components/ui/PasswordInput.vue'
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.login-page {
|
||||
&__fields {
|
||||
> *:not(:last-child) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__submit {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
6
new-client/src/pages/auth/register.vue
Normal file
6
new-client/src/pages/auth/register.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
Register page
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
5
new-client/src/pages/index.vue
Normal file
5
new-client/src/pages/index.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
Index page
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
11
new-client/src/shared/api/client.ts
Normal file
11
new-client/src/shared/api/client.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Api } from './generated-chad-api'
|
||||
|
||||
const api = new Api({
|
||||
baseUrl: 'http://localhost:4000',
|
||||
})
|
||||
|
||||
function isChadResponseError(error) {
|
||||
|
||||
}
|
||||
|
||||
export default api
|
||||
755
new-client/src/shared/api/generated-chad-api.ts
Normal file
755
new-client/src/shared/api/generated-chad-api.ts
Normal file
@@ -0,0 +1,755 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* Attachment
|
||||
* Attachment
|
||||
*/
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
name: string;
|
||||
mimetype: string;
|
||||
/** @min 0 */
|
||||
size: number;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Channel
|
||||
* Channel
|
||||
*/
|
||||
export interface Channel {
|
||||
id: string;
|
||||
ownerId: string | null;
|
||||
name: string;
|
||||
persistent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ChatMessage
|
||||
* ChatMessage
|
||||
*/
|
||||
export interface ChatMessage {
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateChannelPayload
|
||||
* CreateChannelPayload
|
||||
*/
|
||||
export interface CreateChannelPayload {
|
||||
name: string;
|
||||
persistent: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateUser
|
||||
* CreateUser
|
||||
*/
|
||||
export interface CreateUser {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 6 */
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetAttachmentParams
|
||||
* GetAttachmentParams
|
||||
*/
|
||||
export interface GetAttachmentParams {
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetUserQuery
|
||||
* GetUserQuery
|
||||
*/
|
||||
export interface GetUserQuery {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Login
|
||||
* Login
|
||||
*/
|
||||
export interface Login {
|
||||
/** @minLength 1 */
|
||||
username: string;
|
||||
/** @minLength 1 */
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* NewChatMessagePayload
|
||||
* NewChatMessagePayload
|
||||
*/
|
||||
export interface NewChatMessagePayload {
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
attachments?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply
|
||||
* Reply
|
||||
*/
|
||||
export interface Reply {
|
||||
/** @format uuid */
|
||||
messageId: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ResponseError
|
||||
* ResponseError
|
||||
*/
|
||||
export interface ResponseError {
|
||||
statusCode: number;
|
||||
error: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateUserPayload
|
||||
* UpdateUserPayload
|
||||
*/
|
||||
export interface UpdateUserPayload {
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateUserPreferencesPayload
|
||||
* UpdateUserPreferencesPayload
|
||||
*/
|
||||
export interface UpdateUserPreferencesPayload {
|
||||
toggleInputHotkey?: string;
|
||||
toggleOutputHotkey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* UserPreferences
|
||||
* UserPreferences
|
||||
*/
|
||||
export interface UserPreferences {
|
||||
toggleInputHotkey: string;
|
||||
toggleOutputHotkey: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* User
|
||||
* User
|
||||
*/
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
displayName: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export type QueryParamsType = Record<string | number, any>;
|
||||
export type ResponseFormat = keyof Omit<Body, "body" | "bodyUsed">;
|
||||
|
||||
export interface FullRequestParams extends Omit<RequestInit, "body"> {
|
||||
/** set parameter to `true` for call `securityWorker` for this request */
|
||||
secure?: boolean;
|
||||
/** request path */
|
||||
path: string;
|
||||
/** content type of request body */
|
||||
type?: ContentType;
|
||||
/** query params */
|
||||
query?: QueryParamsType;
|
||||
/** format of response (i.e. response.json() -> format: "json") */
|
||||
format?: ResponseFormat;
|
||||
/** request body */
|
||||
body?: unknown;
|
||||
/** base url */
|
||||
baseUrl?: string;
|
||||
/** request cancellation token */
|
||||
cancelToken?: CancelToken;
|
||||
}
|
||||
|
||||
export type RequestParams = Omit<
|
||||
FullRequestParams,
|
||||
"body" | "method" | "query" | "path"
|
||||
>;
|
||||
|
||||
export interface ApiConfig<SecurityDataType = unknown> {
|
||||
baseUrl?: string;
|
||||
baseApiParams?: Omit<RequestParams, "baseUrl" | "cancelToken" | "signal">;
|
||||
securityWorker?: (
|
||||
securityData: SecurityDataType | null,
|
||||
) => Promise<RequestParams | void> | RequestParams | void;
|
||||
customFetch?: typeof fetch;
|
||||
}
|
||||
|
||||
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
|
||||
extends Response {
|
||||
data: D;
|
||||
error: E;
|
||||
}
|
||||
|
||||
type CancelToken = Symbol | string | number;
|
||||
|
||||
export enum ContentType {
|
||||
Json = "application/json",
|
||||
JsonApi = "application/vnd.api+json",
|
||||
FormData = "multipart/form-data",
|
||||
UrlEncoded = "application/x-www-form-urlencoded",
|
||||
Text = "text/plain",
|
||||
}
|
||||
|
||||
export class HttpClient<SecurityDataType = unknown> {
|
||||
public baseUrl: string = "";
|
||||
private securityData: SecurityDataType | null = null;
|
||||
private securityWorker?: ApiConfig<SecurityDataType>["securityWorker"];
|
||||
private abortControllers = new Map<CancelToken, AbortController>();
|
||||
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
|
||||
fetch(...fetchParams);
|
||||
|
||||
private baseApiParams: RequestParams = {
|
||||
credentials: "same-origin",
|
||||
headers: {},
|
||||
redirect: "follow",
|
||||
referrerPolicy: "no-referrer",
|
||||
};
|
||||
|
||||
constructor(apiConfig: ApiConfig<SecurityDataType> = {}) {
|
||||
Object.assign(this, apiConfig);
|
||||
}
|
||||
|
||||
public setSecurityData = (data: SecurityDataType | null) => {
|
||||
this.securityData = data;
|
||||
};
|
||||
|
||||
protected encodeQueryParam(key: string, value: any) {
|
||||
const encodedKey = encodeURIComponent(key);
|
||||
return `${encodedKey}=${encodeURIComponent(typeof value === "number" ? value : `${value}`)}`;
|
||||
}
|
||||
|
||||
protected addQueryParam(query: QueryParamsType, key: string) {
|
||||
return this.encodeQueryParam(key, query[key]);
|
||||
}
|
||||
|
||||
protected addArrayQueryParam(query: QueryParamsType, key: string) {
|
||||
const value = query[key];
|
||||
return value.map((v: any) => this.encodeQueryParam(key, v)).join("&");
|
||||
}
|
||||
|
||||
protected toQueryString(rawQuery?: QueryParamsType): string {
|
||||
const query = rawQuery || {};
|
||||
const keys = Object.keys(query).filter(
|
||||
(key) => "undefined" !== typeof query[key],
|
||||
);
|
||||
return keys
|
||||
.map((key) =>
|
||||
Array.isArray(query[key])
|
||||
? this.addArrayQueryParam(query, key)
|
||||
: this.addQueryParam(query, key),
|
||||
)
|
||||
.join("&");
|
||||
}
|
||||
|
||||
protected addQueryParams(rawQuery?: QueryParamsType): string {
|
||||
const queryString = this.toQueryString(rawQuery);
|
||||
return queryString ? `?${queryString}` : "";
|
||||
}
|
||||
|
||||
private contentFormatters: Record<ContentType, (input: any) => any> = {
|
||||
[ContentType.Json]: (input: any) =>
|
||||
input !== null && (typeof input === "object" || typeof input === "string")
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.JsonApi]: (input: any) =>
|
||||
input !== null && (typeof input === "object" || typeof input === "string")
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.Text]: (input: any) =>
|
||||
input !== null && typeof input !== "string"
|
||||
? JSON.stringify(input)
|
||||
: input,
|
||||
[ContentType.FormData]: (input: any) => {
|
||||
if (input instanceof FormData) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return Object.keys(input || {}).reduce((formData, key) => {
|
||||
const property = input[key];
|
||||
formData.append(
|
||||
key,
|
||||
property instanceof Blob
|
||||
? property
|
||||
: typeof property === "object" && property !== null
|
||||
? JSON.stringify(property)
|
||||
: `${property}`,
|
||||
);
|
||||
return formData;
|
||||
}, new FormData());
|
||||
},
|
||||
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
|
||||
};
|
||||
|
||||
protected mergeRequestParams(
|
||||
params1: RequestParams,
|
||||
params2?: RequestParams,
|
||||
): RequestParams {
|
||||
return {
|
||||
...this.baseApiParams,
|
||||
...params1,
|
||||
...(params2 || {}),
|
||||
headers: {
|
||||
...(this.baseApiParams.headers || {}),
|
||||
...(params1.headers || {}),
|
||||
...((params2 && params2.headers) || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected createAbortSignal = (
|
||||
cancelToken: CancelToken,
|
||||
): AbortSignal | undefined => {
|
||||
if (this.abortControllers.has(cancelToken)) {
|
||||
const abortController = this.abortControllers.get(cancelToken);
|
||||
if (abortController) {
|
||||
return abortController.signal;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
this.abortControllers.set(cancelToken, abortController);
|
||||
return abortController.signal;
|
||||
};
|
||||
|
||||
public abortRequest = (cancelToken: CancelToken) => {
|
||||
const abortController = this.abortControllers.get(cancelToken);
|
||||
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
this.abortControllers.delete(cancelToken);
|
||||
}
|
||||
};
|
||||
|
||||
public request = async <T = any, E = any>({
|
||||
body,
|
||||
secure,
|
||||
path,
|
||||
type,
|
||||
query,
|
||||
format,
|
||||
baseUrl,
|
||||
cancelToken,
|
||||
...params
|
||||
}: FullRequestParams): Promise<HttpResponse<T, E>> => {
|
||||
const secureParams =
|
||||
((typeof secure === "boolean" ? secure : this.baseApiParams.secure) &&
|
||||
this.securityWorker &&
|
||||
(await this.securityWorker(this.securityData))) ||
|
||||
{};
|
||||
const requestParams = this.mergeRequestParams(params, secureParams);
|
||||
const queryString = query && this.toQueryString(query);
|
||||
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
|
||||
const responseFormat = format || requestParams.format;
|
||||
|
||||
return this.customFetch(
|
||||
`${baseUrl || this.baseUrl || ""}${path}${queryString ? `?${queryString}` : ""}`,
|
||||
{
|
||||
...requestParams,
|
||||
headers: {
|
||||
...(requestParams.headers || {}),
|
||||
...(type && type !== ContentType.FormData
|
||||
? { "Content-Type": type }
|
||||
: {}),
|
||||
},
|
||||
signal:
|
||||
(cancelToken
|
||||
? this.createAbortSignal(cancelToken)
|
||||
: requestParams.signal) || null,
|
||||
body:
|
||||
typeof body === "undefined" || body === null
|
||||
? null
|
||||
: payloadFormatter(body),
|
||||
},
|
||||
).then(async (response) => {
|
||||
const r = response as HttpResponse<T, E>;
|
||||
r.data = null as unknown as T;
|
||||
r.error = null as unknown as E;
|
||||
|
||||
const responseToParse = responseFormat ? response.clone() : response;
|
||||
const data = !responseFormat
|
||||
? r
|
||||
: await responseToParse[responseFormat]()
|
||||
.then((data) => {
|
||||
if (r.ok) {
|
||||
r.data = data;
|
||||
} else {
|
||||
r.error = data;
|
||||
}
|
||||
return r;
|
||||
})
|
||||
.catch((e) => {
|
||||
r.error = e;
|
||||
return r;
|
||||
});
|
||||
|
||||
if (cancelToken) {
|
||||
this.abortControllers.delete(cancelToken);
|
||||
}
|
||||
|
||||
if (!response.ok) throw data;
|
||||
return data;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @title Chad API
|
||||
* @version 1.0.0
|
||||
*/
|
||||
export class Api<
|
||||
SecurityDataType extends unknown,
|
||||
> extends HttpClient<SecurityDataType> {
|
||||
chad = {
|
||||
/**
|
||||
* @description Pass file to multipart/form-data
|
||||
*
|
||||
* @tags Attachment
|
||||
* @name AttachmentUpload
|
||||
* @summary Upload attachment
|
||||
* @request POST:/chad/attachment/upload
|
||||
*/
|
||||
attachmentUpload: (params: RequestParams = {}) =>
|
||||
this.request<string, ResponseError>({
|
||||
path: `/chad/attachment/upload`,
|
||||
method: "POST",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Attachment
|
||||
* @name AttachmentGet
|
||||
* @summary Get attachment
|
||||
* @request GET:/chad/attachment/{id}
|
||||
*/
|
||||
attachmentGet: (id: string, params: RequestParams = {}) =>
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/attachment/${id}`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Auth
|
||||
* @name AuthRegister
|
||||
* @summary Register
|
||||
* @request POST:/chad/auth/register
|
||||
*/
|
||||
authRegister: (data: CreateUser, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/register`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Auth
|
||||
* @name AuthLogin
|
||||
* @summary Login
|
||||
* @request POST:/chad/auth/login
|
||||
*/
|
||||
authLogin: (data: Login, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/login`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Auth
|
||||
* @name AuthMe
|
||||
* @summary Me
|
||||
* @request GET:/chad/auth/me
|
||||
*/
|
||||
authMe: (params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/auth/me`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Auth
|
||||
* @name AuthLogout
|
||||
* @summary Logout
|
||||
* @request POST:/chad/auth/logout
|
||||
*/
|
||||
authLogout: (params: RequestParams = {}) =>
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/auth/logout`,
|
||||
method: "POST",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelList
|
||||
* @summary Get channel list
|
||||
* @request GET:/chad/channels
|
||||
*/
|
||||
channelList: (params: RequestParams = {}) =>
|
||||
this.request<Channel[], ResponseError>({
|
||||
path: `/chad/channels`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelCreate
|
||||
* @summary Create channel
|
||||
* @request POST:/chad/channels
|
||||
*/
|
||||
channelCreate: (data: CreateChannelPayload, params: RequestParams = {}) =>
|
||||
this.request<Channel, ResponseError>({
|
||||
path: `/chad/channels`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Channel
|
||||
* @name ChannelDelete
|
||||
* @summary Delete channel
|
||||
* @request DELETE:/chad/channels/{id}
|
||||
*/
|
||||
channelDelete: (id: string, params: RequestParams = {}) =>
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/channels/${id}`,
|
||||
method: "DELETE",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Chat
|
||||
* @name ChatSend
|
||||
* @summary Send message
|
||||
* @request POST:/chad/chat/send
|
||||
*/
|
||||
chatSend: (
|
||||
data: {
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
attachments?: string[];
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
},
|
||||
ResponseError
|
||||
>({
|
||||
path: `/chad/chat/send`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags Chat
|
||||
* @name ChatMessages
|
||||
* @summary Get messages
|
||||
* @request GET:/chad/chat
|
||||
*/
|
||||
chatMessages: (
|
||||
query: {
|
||||
/**
|
||||
* Cursor to message
|
||||
* @format uuid
|
||||
*/
|
||||
cursor?: string;
|
||||
/**
|
||||
* @min 1
|
||||
* @max 100
|
||||
* @default 10
|
||||
*/
|
||||
limit: number;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<
|
||||
{
|
||||
messages: {
|
||||
/** @format uuid */
|
||||
id: string;
|
||||
/** @format uuid */
|
||||
senderId: string;
|
||||
/** @minLength 1 */
|
||||
text: string;
|
||||
/** @format date-time */
|
||||
createdAt: string;
|
||||
/** @format date-time */
|
||||
updatedAt: string;
|
||||
attachments: string[];
|
||||
}[];
|
||||
/**
|
||||
* Cursor to last message
|
||||
* @format uuid
|
||||
*/
|
||||
nextCursor?: string;
|
||||
},
|
||||
ResponseError
|
||||
>({
|
||||
path: `/chad/chat`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags User
|
||||
* @name UserGet
|
||||
* @summary Get user
|
||||
* @request GET:/chad/user
|
||||
*/
|
||||
userGet: (
|
||||
query?: {
|
||||
username?: string;
|
||||
},
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/user`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags User
|
||||
* @name UserGetPreferences
|
||||
* @summary Get preferences
|
||||
* @request GET:/chad/user/preferences
|
||||
*/
|
||||
userGetPreferences: (params: RequestParams = {}) =>
|
||||
this.request<UserPreferences, ResponseError>({
|
||||
path: `/chad/user/preferences`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags User
|
||||
* @name UserUpdatePreferences
|
||||
* @summary Update preferences
|
||||
* @request PATCH:/chad/user/preferences
|
||||
*/
|
||||
userUpdatePreferences: (
|
||||
data: UpdateUserPreferencesPayload,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
this.request<any, ResponseError>({
|
||||
path: `/chad/user/preferences`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
...params,
|
||||
}),
|
||||
|
||||
/**
|
||||
* No description
|
||||
*
|
||||
* @tags User
|
||||
* @name UserUpdateProfile
|
||||
* @summary Update profile
|
||||
* @request PATCH:/chad/profile
|
||||
*/
|
||||
userUpdateProfile: (data: UpdateUserPayload, params: RequestParams = {}) =>
|
||||
this.request<User, ResponseError>({
|
||||
path: `/chad/profile`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
}),
|
||||
};
|
||||
}
|
||||
31
new-client/src/shared/components/AppLogo.vue
Normal file
31
new-client/src/shared/components/AppLogo.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="app-logo">
|
||||
<p class="app-logo__title">
|
||||
Chad
|
||||
</p>
|
||||
<p class="app-logo__version">
|
||||
{{ version }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useApp } from '@shared/composables/use-app'
|
||||
|
||||
const { version } = useApp()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-logo {
|
||||
&__title {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__version {
|
||||
font-size: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
new-client/src/shared/components/ui/Button.vue
Normal file
59
new-client/src/shared/components/ui/Button.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<button
|
||||
class="chad-button"
|
||||
:data-loading="loading || undefined"
|
||||
:disabled="disabled"
|
||||
:type="type"
|
||||
:data-full="full || undefined"
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'ChadButton',
|
||||
})
|
||||
|
||||
withDefaults(
|
||||
defineProps<Props>(),
|
||||
{
|
||||
type: 'button',
|
||||
},
|
||||
)
|
||||
|
||||
interface Props {
|
||||
loading?: boolean
|
||||
disabled?: boolean
|
||||
type?: 'button' | 'submit'
|
||||
full?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.chad-button {
|
||||
border: none;
|
||||
color: var(--bg-dark);
|
||||
border-radius: 12px;
|
||||
padding-inline: 12px;
|
||||
height: 44px;
|
||||
background-color: var(--primary);
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition-property: color, background-color;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease-out;
|
||||
outline: none;
|
||||
|
||||
&[data-full] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
111
new-client/src/shared/components/ui/Input.vue
Normal file
111
new-client/src/shared/components/ui/Input.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<FieldRoot class="chad-input" :invalid="!!error">
|
||||
<FieldLabel v-if="label" class="chad-input__label">
|
||||
{{ label }}
|
||||
</FieldLabel>
|
||||
|
||||
<div class="chad-input__control">
|
||||
<FieldInput v-model="modelValue" class="chad-input__input" :placeholder="placeholder" type="text" />
|
||||
</div>
|
||||
|
||||
<FieldHelperText v-if="helper" class="chad-input__helper">
|
||||
{{ helper }}
|
||||
</FieldHelperText>
|
||||
<FieldErrorText v-if="error" class="chad-input__error">
|
||||
{{ error }}
|
||||
</FieldErrorText>
|
||||
</FieldRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FieldInputProps, FieldRootProps } from '@ark-ui/vue/field'
|
||||
import { FieldErrorText, FieldHelperText, FieldInput, FieldLabel, FieldRoot } from '@ark-ui/vue/field'
|
||||
|
||||
defineOptions({
|
||||
name: 'ChadInput',
|
||||
})
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const modelValue = defineModel<FieldInputProps['modelValue']>('modelValue')
|
||||
|
||||
interface Props extends Omit<FieldRootProps, 'ids' | 'invalid'> {
|
||||
label?: string
|
||||
placeholder?: string
|
||||
helper?: string
|
||||
error?: string
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.chad-input {
|
||||
$self: &;
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&__control {
|
||||
border-radius: 12px;
|
||||
padding-inline: 12px;
|
||||
height: 44px;
|
||||
background-color: var(--bg-light);
|
||||
border: none;
|
||||
outline: 1px solid var(--border);
|
||||
outline-offset: -1px;
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
outline-color: var(--primary);
|
||||
}
|
||||
|
||||
#{$self}[data-invalid] & {
|
||||
outline-color: var(--danger);
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: var(--text);
|
||||
caret-color: var(--secondary);
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&:-webkit-autofill,
|
||||
&:-webkit-autofill:enabled,
|
||||
&:-webkit-autofill:hover,
|
||||
&:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: var(--text);
|
||||
-webkit-box-shadow: 0 0 0px 1000px var(--bg) inset;
|
||||
box-shadow: 0 0 0px 1000px var(--bg) inset;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
}
|
||||
|
||||
&__helper,
|
||||
&__error {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
&__helper {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
&__error {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
126
new-client/src/shared/components/ui/PasswordInput.vue
Normal file
126
new-client/src/shared/components/ui/PasswordInput.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<FieldRoot class="chad-password-input" :invalid="!!error">
|
||||
<FieldLabel v-if="label" class="chad-password-input__label">
|
||||
{{ label }}
|
||||
</FieldLabel>
|
||||
|
||||
<PasswordInputRoot>
|
||||
<PasswordInputControl class="chad-password-input__control">
|
||||
<PasswordInputInput v-model="modelValue" class="chad-password-input__input" :placeholder="placeholder" />
|
||||
<PasswordInputVisibilityTrigger class="chad-password-input__visibility-trigger">
|
||||
<PasswordInputIndicator>
|
||||
Z
|
||||
<template #fallback>
|
||||
X
|
||||
</template>
|
||||
</PasswordInputIndicator>
|
||||
</PasswordInputVisibilityTrigger>
|
||||
</PasswordInputControl>
|
||||
</PasswordInputRoot>
|
||||
|
||||
<FieldHelperText v-if="helper" class="chad-password-input__helper">
|
||||
{{ helper }}
|
||||
</FieldHelperText>
|
||||
<FieldErrorText v-if="error" class="chad-password-input__error">
|
||||
{{ error }}
|
||||
</FieldErrorText>
|
||||
</FieldRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FieldInputProps, FieldRootProps } from '@ark-ui/vue/field'
|
||||
import { FieldErrorText, FieldHelperText, FieldLabel, FieldRoot } from '@ark-ui/vue/field'
|
||||
import { PasswordInputControl, PasswordInputIndicator, PasswordInputInput, PasswordInputRoot, PasswordInputVisibilityTrigger } from '@ark-ui/vue/password-input'
|
||||
|
||||
defineOptions({
|
||||
name: 'ChadPasswordInput',
|
||||
})
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const modelValue = defineModel<FieldInputProps['modelValue']>('modelValue')
|
||||
|
||||
interface Props extends Omit<FieldRootProps, 'ids' | 'invalid'> {
|
||||
label?: string
|
||||
placeholder?: string
|
||||
helper?: string
|
||||
error?: string
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.chad-password-input {
|
||||
&__label {
|
||||
display: block;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
&__control {
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
padding-inline: 12px;
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
background-color: var(--bg-light);
|
||||
border: none;
|
||||
outline: 1px solid var(--border);
|
||||
outline-offset: -1px;
|
||||
color: var(--text);
|
||||
caret-color: var(--secondary);
|
||||
font-size: 1rem;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline-color: var(--primary);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&[data-invalid] {
|
||||
outline-color: var(--danger);
|
||||
}
|
||||
|
||||
&:-webkit-autofill,
|
||||
&:-webkit-autofill:enabled,
|
||||
&:-webkit-autofill:hover,
|
||||
&:-webkit-autofill:focus {
|
||||
-webkit-text-fill-color: var(--text);
|
||||
-webkit-box-shadow: 0 0 0px 1000px var(--bg) inset;
|
||||
box-shadow: 0 0 0px 1000px var(--bg) inset;
|
||||
background-color: var(--bg);
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&__visibility-trigger {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&__helper,
|
||||
&__error {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
&__helper {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
&__error {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
new-client/src/shared/composables/use-app.ts
Normal file
25
new-client/src/shared/composables/use-app.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getVersion as getTauriVersion } from '@tauri-apps/api/app'
|
||||
import { computedAsync } from '@vueuse/core'
|
||||
|
||||
export function useApp() {
|
||||
const isTauri = '__TAURI_INTERNALS__' in window
|
||||
const commitSha = __COMMIT_SHA__
|
||||
|
||||
const version = computedAsync(() => {
|
||||
if (import.meta.dev) {
|
||||
return 'dev'
|
||||
}
|
||||
else if (isTauri) {
|
||||
return getTauriVersion()
|
||||
}
|
||||
else {
|
||||
return 'web'
|
||||
}
|
||||
}, '-')
|
||||
|
||||
return {
|
||||
isTauri,
|
||||
commitSha,
|
||||
version,
|
||||
}
|
||||
}
|
||||
58
new-client/src/shared/composables/use-auth.ts
Normal file
58
new-client/src/shared/composables/use-auth.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { User } from '../api/generated-chad-api'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { computed, readonly, shallowRef } from 'vue'
|
||||
import api from '../api/client'
|
||||
|
||||
export const useAuth = createGlobalState(() => {
|
||||
const me = shallowRef<User>()
|
||||
|
||||
const authorized = computed(() => !!me.value)
|
||||
|
||||
function setMe(value: User | undefined): void {
|
||||
me.value = value
|
||||
}
|
||||
|
||||
async function login(username: string, password: string): Promise<void> {
|
||||
try {
|
||||
const response = await api.chad.authLogin({
|
||||
username,
|
||||
password,
|
||||
})
|
||||
|
||||
setMe(response.data)
|
||||
}
|
||||
catch {
|
||||
setMe(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
async function register(username: string, password: string): Promise<void> {
|
||||
try {
|
||||
const response = await api.chad.authRegister({
|
||||
username,
|
||||
password,
|
||||
})
|
||||
|
||||
setMe(response.data)
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
try {
|
||||
await api.chad.authLogout()
|
||||
|
||||
setMe(undefined)
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
return {
|
||||
authorized,
|
||||
me: readonly(me),
|
||||
setMe,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
}
|
||||
})
|
||||
38
new-client/src/shared/composables/use-updater.ts
Normal file
38
new-client/src/shared/composables/use-updater.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Update } from '@tauri-apps/plugin-updater'
|
||||
import { check } from '@tauri-apps/plugin-updater'
|
||||
import { createGlobalState } from '@vueuse/core'
|
||||
import { ref, shallowRef } from 'vue'
|
||||
import { useApp } from './use-app'
|
||||
|
||||
export const useUpdater = createGlobalState(() => {
|
||||
const { isTauri } = useApp()
|
||||
|
||||
const lastUpdate = shallowRef<Update>()
|
||||
|
||||
const checking = ref(false)
|
||||
|
||||
async function checkForUpdates() {
|
||||
if (!isTauri)
|
||||
return
|
||||
|
||||
try {
|
||||
checking.value = true
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
lastUpdate.value = (await check()) ?? undefined
|
||||
}
|
||||
catch { }
|
||||
finally {
|
||||
checking.value = false
|
||||
}
|
||||
|
||||
return lastUpdate.value
|
||||
}
|
||||
|
||||
return {
|
||||
lastUpdate,
|
||||
checking,
|
||||
checkForUpdates,
|
||||
}
|
||||
})
|
||||
2
new-client/src/shared/globals.d.ts
vendored
Normal file
2
new-client/src/shared/globals.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
declare const __API_BASE_URL__: string
|
||||
declare const __COMMIT_SHA__: string
|
||||
44
new-client/src/shared/layouts/Auth.vue
Normal file
44
new-client/src/shared/layouts/Auth.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="auth-layout">
|
||||
<div class="auth-layout__container">
|
||||
<AppLogo class="auth-layout__logo" />
|
||||
|
||||
<div class="auth-layout__content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppLogo from '@shared/components/AppLogo.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'AuthLayout',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.auth-layout {
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
|
||||
&__container {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 24px;
|
||||
background-color: var(--bg);
|
||||
border-radius: 36px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
new-client/src/shared/layouts/Default.vue
Normal file
82
new-client/src/shared/layouts/Default.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="default-layout">
|
||||
<header class="app-header">
|
||||
<div class="app-header__sidebar">
|
||||
<AppLogo />
|
||||
</div>
|
||||
<div class="app-header__content">
|
||||
Header
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<aside class="app-sidebar">
|
||||
Sidebar
|
||||
</aside>
|
||||
|
||||
<div class="default-layout__content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import AppLogo from '@shared/components/AppLogo.vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'DefaultLayout',
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.default-layout {
|
||||
--sidebar-width: 320px;
|
||||
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-areas: 'header header' 'sidebar content';
|
||||
|
||||
&__content {
|
||||
grid-area: content;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
grid-area: header;
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
align-items: stretch;
|
||||
background-color: var(--bg);
|
||||
border-bottom: 1px solid var(--border-muted);
|
||||
height: 52px;
|
||||
|
||||
&__sidebar {
|
||||
border-right: 1px solid var(--border-muted);
|
||||
}
|
||||
|
||||
&__sidebar,
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__version {
|
||||
margin-left: 4px;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
grid-area: sidebar;
|
||||
background-color: var(--bg);
|
||||
border-right: 1px solid var(--border-muted);
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
new-client/src/shared/styles/main.scss
Normal file
29
new-client/src/shared/styles/main.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
@use 'tokens.scss';
|
||||
|
||||
html,
|
||||
body,
|
||||
[data-mount-point]:not(:empty) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-mount-point]:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bg-dark);
|
||||
color: var(--text);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-variation-settings:
|
||||
'wdth' 100,
|
||||
'YTLC' 500;
|
||||
}
|
||||
128
new-client/src/shared/styles/reset.css
Normal file
128
new-client/src/shared/styles/reset.css
Normal file
@@ -0,0 +1,128 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
566
new-client/src/shared/styles/sanitize.css
Normal file
566
new-client/src/shared/styles/sanitize.css
Normal file
@@ -0,0 +1,566 @@
|
||||
/* Document
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Add border box sizing in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add text decoration inheritance in all browsers (opinionated).
|
||||
* 2. Add vertical alignment inheritance in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
::before,
|
||||
::after {
|
||||
text-decoration: inherit; /* 1 */
|
||||
vertical-align: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Use the default cursor in all browsers (opinionated).
|
||||
* 2. Change the line height in all browsers (opinionated).
|
||||
* 3. Use a 4-space tab width in all browsers (opinionated).
|
||||
* 4. Remove the grey highlight on links in iOS (opinionated).
|
||||
* 5. Prevent adjustments of font size after orientation changes in
|
||||
* IE on Windows Phone and in iOS.
|
||||
* 6. Breaks words to prevent overflow in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
html {
|
||||
cursor: default; /* 1 */
|
||||
line-height: 1.5; /* 2 */
|
||||
-moz-tab-size: 4; /* 3 */
|
||||
tab-size: 4; /* 3 */
|
||||
-webkit-tap-highlight-color: transparent /* 4 */;
|
||||
-ms-text-size-adjust: 100%; /* 5 */
|
||||
-webkit-text-size-adjust: 100%; /* 5 */
|
||||
word-break: break-word; /* 6 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Edge, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin on nested lists in Chrome, Edge, IE, and Safari.
|
||||
*/
|
||||
|
||||
dl dl,
|
||||
dl ol,
|
||||
dl ul,
|
||||
ol dl,
|
||||
ul dl {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the margin on nested lists in Edge 18- and IE.
|
||||
*/
|
||||
|
||||
ol ol,
|
||||
ol ul,
|
||||
ul ol,
|
||||
ul ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct sizing in Firefox.
|
||||
* 2. Show the overflow in Edge 18- and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the list style on navigation lists in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
nav ol,
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the gray background on active links in IE 10.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct text decoration in Edge 18-, IE, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
* ========================================================================== */
|
||||
|
||||
/*
|
||||
* Change the alignment on media elements in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
audio,
|
||||
canvas,
|
||||
iframe,
|
||||
img,
|
||||
svg,
|
||||
video {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on iframes in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
iframe {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images within links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the fill color to match the text color in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
svg:not([fill]) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Tabular data
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Collapse border spacing in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin on controls in Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
select {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Show the overflow in IE.
|
||||
* 2. Remove the inheritance of text transform in Edge 18-, Firefox, and IE.
|
||||
*/
|
||||
|
||||
button {
|
||||
overflow: visible; /* 1 */
|
||||
text-transform: none; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the inability to style buttons in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Change the inconsistent appearance in all browsers (opinionated).
|
||||
* 2. Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #a0a0a0; /* 1 */
|
||||
padding: 0.35em 0.75em 0.625em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in Edge 18- and IE.
|
||||
*/
|
||||
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge 18- and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
*/
|
||||
|
||||
legend {
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in Edge 18- and IE.
|
||||
* 2. Add the correct vertical alignment in Chrome, Edge, and Firefox.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the margin in Firefox and Safari.
|
||||
* 2. Remove the default vertical scrollbar in IE.
|
||||
* 3. Change the resize direction in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
textarea {
|
||||
margin: 0; /* 1 */
|
||||
overflow: auto; /* 2 */
|
||||
resize: vertical; /* 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome, Edge, and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-inner-spin-button,
|
||||
::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the text style of placeholders in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: 0.54;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding in Chrome, Edge, and Safari on macOS.
|
||||
*/
|
||||
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style upload buttons in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding of focus outlines in Firefox.
|
||||
*/
|
||||
|
||||
::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus outline styles unset by the previous rule in Firefox.
|
||||
*/
|
||||
|
||||
:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the additional :invalid styles in Firefox.
|
||||
*/
|
||||
|
||||
:-moz-ui-invalid {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
* ========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in Edge 18- and IE.
|
||||
*/
|
||||
|
||||
details {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct styles in Edge 18-, IE, and Safari.
|
||||
*/
|
||||
|
||||
dialog {
|
||||
background-color: white;
|
||||
border: solid;
|
||||
color: black;
|
||||
display: block;
|
||||
height: -moz-fit-content;
|
||||
height: -webkit-fit-content;
|
||||
height: fit-content;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: -moz-fit-content;
|
||||
width: -webkit-fit-content;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
dialog:not([open]) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* User interaction
|
||||
* ========================================================================== */
|
||||
|
||||
/*
|
||||
* 1. Remove the tapping delay in IE 10.
|
||||
* 2. Remove the tapping delay on clickable elements
|
||||
in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
a,
|
||||
area,
|
||||
button,
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
summary,
|
||||
textarea,
|
||||
[tabindex] {
|
||||
-ms-touch-action: manipulation; /* 1 */
|
||||
touch-action: manipulation; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Accessibility
|
||||
* ========================================================================== */
|
||||
|
||||
/**
|
||||
* Change the cursor on busy elements in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
[aria-busy="true"] {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the cursor on control elements in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
[aria-controls] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the cursor on disabled, not-editable, or otherwise
|
||||
* inoperable elements in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
[aria-disabled="true"],
|
||||
[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the display on visually hidden accessible elements
|
||||
* in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
[aria-hidden="false"][hidden] {
|
||||
display: initial;
|
||||
}
|
||||
|
||||
[aria-hidden="false"][hidden]:not(:focus) {
|
||||
clip: rect(0, 0, 0, 0);
|
||||
position: absolute;
|
||||
}
|
||||
64
new-client/src/shared/styles/tokens.scss
Normal file
64
new-client/src/shared/styles/tokens.scss
Normal file
@@ -0,0 +1,64 @@
|
||||
:root {
|
||||
/* hsl (fallback color) */
|
||||
--bg-dark: hsl(208 100% 2%);
|
||||
--bg: hsl(201 86% 4%);
|
||||
--bg-light: hsl(198 56% 8%);
|
||||
--text: hsl(199 100% 93%);
|
||||
--text-muted: hsl(199 36% 68%);
|
||||
--highlight: hsl(199 28% 37%);
|
||||
--border: hsl(198 40% 26%);
|
||||
--border-muted: hsl(197 70% 15%);
|
||||
--primary: hsl(198 69% 65%);
|
||||
--secondary: hsl(20 68% 69%);
|
||||
--danger: hsl(9 26% 64%);
|
||||
--warning: hsl(52 19% 57%);
|
||||
--success: hsl(146 17% 59%);
|
||||
--info: hsl(217 28% 65%);
|
||||
/* oklch */
|
||||
--bg-dark: oklch(0.1 0.025 228);
|
||||
--bg: oklch(0.15 0.025 228);
|
||||
--bg-light: oklch(0.2 0.025 228);
|
||||
--text: oklch(0.96 0.05 228);
|
||||
--text-muted: oklch(0.76 0.05 228);
|
||||
--highlight: oklch(0.5 0.05 228);
|
||||
--border: oklch(0.4 0.05 228);
|
||||
--border-muted: oklch(0.3 0.05 228);
|
||||
--primary: oklch(0.76 0.1 228);
|
||||
--secondary: oklch(0.76 0.1 48);
|
||||
--danger: oklch(0.7 0.05 30);
|
||||
--warning: oklch(0.7 0.05 100);
|
||||
--success: oklch(0.7 0.05 160);
|
||||
--info: oklch(0.7 0.05 260);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
/* hsl (fallback color) */
|
||||
--bg-dark: hsl(199 53% 89%);
|
||||
--bg: hsl(199 100% 94%);
|
||||
--bg-light: hsl(199 100% 99%);
|
||||
--text: hsl(204 100% 4%);
|
||||
--text-muted: hsl(198 40% 26%);
|
||||
--highlight: hsl(199 100% 99%);
|
||||
--border: hsl(199 22% 49%);
|
||||
--border-muted: hsl(199 28% 61%);
|
||||
--primary: hsl(193 100% 16%);
|
||||
--secondary: hsl(23 81% 25%);
|
||||
--danger: hsl(9 21% 41%);
|
||||
--warning: hsl(52 23% 34%);
|
||||
--success: hsl(147 19% 36%);
|
||||
--info: hsl(217 22% 41%);
|
||||
/* oklch */
|
||||
--bg-dark: oklch(0.92 0.025 228);
|
||||
--bg: oklch(0.96 0.025 228);
|
||||
--bg-light: oklch(1 0.025 228);
|
||||
--text: oklch(0.15 0.05 228);
|
||||
--text-muted: oklch(0.4 0.05 228);
|
||||
--highlight: oklch(1 0.05 228);
|
||||
--border: oklch(0.6 0.05 228);
|
||||
--border-muted: oklch(0.7 0.05 228);
|
||||
--primary: oklch(0.4 0.1 228);
|
||||
--secondary: oklch(0.4 0.1 48);
|
||||
--danger: oklch(0.5 0.05 30);
|
||||
--warning: oklch(0.5 0.05 100);
|
||||
--success: oklch(0.5 0.05 160);
|
||||
--info: oklch(0.5 0.05 260);
|
||||
}
|
||||
Reference in New Issue
Block a user