mirror of
https://github.com/HangarMC/Hangar.git
synced 2025-02-23 15:12:52 +08:00
Start a POC for a new frontend!
This commit is contained in:
parent
40d611757d
commit
d629e51f9d
222
frontend-new/.eslintrc-auto-import.json
Normal file
222
frontend-new/.eslintrc-auto-import.json
Normal file
@ -0,0 +1,222 @@
|
||||
{
|
||||
"globals": {
|
||||
"acceptHMRUpdate": true,
|
||||
"asyncComputed": true,
|
||||
"autoResetRef": true,
|
||||
"axios": true,
|
||||
"biSyncRef": true,
|
||||
"computed": true,
|
||||
"computedInject": true,
|
||||
"controlledComputed": true,
|
||||
"controlledRef": true,
|
||||
"createApp": true,
|
||||
"createEventHook": true,
|
||||
"createGlobalState": true,
|
||||
"createPinia": true,
|
||||
"createReactiveFn": true,
|
||||
"createSharedComposable": true,
|
||||
"createUnrefFn": true,
|
||||
"customRef": true,
|
||||
"debouncedRef": true,
|
||||
"debouncedWatch": true,
|
||||
"defineAsyncComponent": true,
|
||||
"defineComponent": true,
|
||||
"defineStore": true,
|
||||
"eagerComputed": true,
|
||||
"effectScope": true,
|
||||
"EffectScope": true,
|
||||
"extendRef": true,
|
||||
"getActivePinia": true,
|
||||
"getCurrentInstance": true,
|
||||
"getCurrentScope": true,
|
||||
"h": true,
|
||||
"ignorableWatch": true,
|
||||
"inject": true,
|
||||
"isDefined": true,
|
||||
"isReadonly": true,
|
||||
"isRef": true,
|
||||
"makeDestructurable": true,
|
||||
"mapActions": true,
|
||||
"mapGetters": true,
|
||||
"mapState": true,
|
||||
"mapStores": true,
|
||||
"mapWritableState": true,
|
||||
"markRaw": true,
|
||||
"nextTick": true,
|
||||
"onActivated": true,
|
||||
"onBeforeMount": true,
|
||||
"onBeforeUnmount": true,
|
||||
"onBeforeUpdate": true,
|
||||
"onClickOutside": true,
|
||||
"onDeactivated": true,
|
||||
"onErrorCaptured": true,
|
||||
"onKeyStroke": true,
|
||||
"onMounted": true,
|
||||
"onRenderTracked": true,
|
||||
"onRenderTriggered": true,
|
||||
"onScopeDispose": true,
|
||||
"onServerPrefetch": true,
|
||||
"onStartTyping": true,
|
||||
"onUnmounted": true,
|
||||
"onUpdated": true,
|
||||
"pausableWatch": true,
|
||||
"provide": true,
|
||||
"reactify": true,
|
||||
"reactifyObject": true,
|
||||
"reactive": true,
|
||||
"reactivePick": true,
|
||||
"readonly": true,
|
||||
"ref": true,
|
||||
"refDefault": true,
|
||||
"resolveComponent": true,
|
||||
"setActivePinia": true,
|
||||
"setMapStoreSuffix": true,
|
||||
"shallowReactive": true,
|
||||
"shallowReadonly": true,
|
||||
"shallowRef": true,
|
||||
"storeToRefs": true,
|
||||
"syncRef": true,
|
||||
"templateRef": true,
|
||||
"throttledRef": true,
|
||||
"throttledWatch": true,
|
||||
"toRaw": true,
|
||||
"toReactive": true,
|
||||
"toRef": true,
|
||||
"toRefs": true,
|
||||
"triggerRef": true,
|
||||
"tryOnBeforeUnmount": true,
|
||||
"tryOnMounted": true,
|
||||
"tryOnScopeDispose": true,
|
||||
"tryOnUnmounted": true,
|
||||
"unref": true,
|
||||
"unrefElement": true,
|
||||
"until": true,
|
||||
"useActiveElement": true,
|
||||
"useAsyncQueue": true,
|
||||
"useAsyncState": true,
|
||||
"useAttrs": true,
|
||||
"useBase64": true,
|
||||
"useBattery": true,
|
||||
"useBreakpoints": true,
|
||||
"useBroadcastChannel": true,
|
||||
"useBrowserLocation": true,
|
||||
"useClamp": true,
|
||||
"useClipboard": true,
|
||||
"useColorMode": true,
|
||||
"useConfirmDialog": true,
|
||||
"useCounter": true,
|
||||
"useCssModule": true,
|
||||
"useCssVar": true,
|
||||
"useCssVars": true,
|
||||
"useCycleList": true,
|
||||
"useDark": true,
|
||||
"useDebounce": true,
|
||||
"useDebouncedRefHistory": true,
|
||||
"useDebounceFn": true,
|
||||
"useDeviceMotion": true,
|
||||
"useDeviceOrientation": true,
|
||||
"useDevicePixelRatio": true,
|
||||
"useDevicesList": true,
|
||||
"useDisplayMedia": true,
|
||||
"useDocumentVisibility": true,
|
||||
"useDraggable": true,
|
||||
"useElementBounding": true,
|
||||
"useElementByPoint": true,
|
||||
"useElementHover": true,
|
||||
"useElementSize": true,
|
||||
"useElementVisibility": true,
|
||||
"useEventBus": true,
|
||||
"useEventListener": true,
|
||||
"useEventSource": true,
|
||||
"useEyeDropper": true,
|
||||
"useFavicon": true,
|
||||
"useFetch": true,
|
||||
"useFocus": true,
|
||||
"useFocusWithin": true,
|
||||
"useFps": true,
|
||||
"useFullscreen": true,
|
||||
"useGeolocation": true,
|
||||
"useHead": true,
|
||||
"useI18n": true,
|
||||
"useIdle": true,
|
||||
"useIntersectionObserver": true,
|
||||
"useInterval": true,
|
||||
"useIntervalFn": true,
|
||||
"useKeyModifier": true,
|
||||
"useLastChanged": true,
|
||||
"useLocalStorage": true,
|
||||
"useMagicKeys": true,
|
||||
"useManualRefHistory": true,
|
||||
"useMediaControls": true,
|
||||
"useMediaQuery": true,
|
||||
"useMemoize": true,
|
||||
"useMemory": true,
|
||||
"useMounted": true,
|
||||
"useMouse": true,
|
||||
"useMouseInElement": true,
|
||||
"useMousePressed": true,
|
||||
"useMutationObserver": true,
|
||||
"useNavigatorLanguage": true,
|
||||
"useNetwork": true,
|
||||
"useNow": true,
|
||||
"useOnline": true,
|
||||
"usePageLeave": true,
|
||||
"useParallax": true,
|
||||
"usePermission": true,
|
||||
"usePointer": true,
|
||||
"usePointerSwipe": true,
|
||||
"usePreferredColorScheme": true,
|
||||
"usePreferredDark": true,
|
||||
"usePreferredLanguages": true,
|
||||
"useRafFn": true,
|
||||
"useRefHistory": true,
|
||||
"useResizeObserver": true,
|
||||
"useRoute": true,
|
||||
"useRouter": true,
|
||||
"useScreenSafeArea": true,
|
||||
"useScriptTag": true,
|
||||
"useScroll": true,
|
||||
"useScrollLock": true,
|
||||
"useSessionStorage": true,
|
||||
"useShare": true,
|
||||
"useSlots": true,
|
||||
"useSpeechRecognition": true,
|
||||
"useSpeechSynthesis": true,
|
||||
"useStorage": true,
|
||||
"useStorageAsync": true,
|
||||
"useStyleTag": true,
|
||||
"useSwipe": true,
|
||||
"useTemplateRefsList": true,
|
||||
"useTextSelection": true,
|
||||
"useThrottle": true,
|
||||
"useThrottledRefHistory": true,
|
||||
"useThrottleFn": true,
|
||||
"useTimeAgo": true,
|
||||
"useTimeout": true,
|
||||
"useTimeoutFn": true,
|
||||
"useTimestamp": true,
|
||||
"useTitle": true,
|
||||
"useToggle": true,
|
||||
"useTransition": true,
|
||||
"useUrlSearchParams": true,
|
||||
"useUserMedia": true,
|
||||
"useVibrate": true,
|
||||
"useVirtualList": true,
|
||||
"useVModel": true,
|
||||
"useVModels": true,
|
||||
"useWakeLock": true,
|
||||
"useWebNotification": true,
|
||||
"useWebSocket": true,
|
||||
"useWebWorker": true,
|
||||
"useWebWorkerFn": true,
|
||||
"useWindowFocus": true,
|
||||
"useWindowScroll": true,
|
||||
"useWindowSize": true,
|
||||
"watch": true,
|
||||
"watchAtMost": true,
|
||||
"watchEffect": true,
|
||||
"watchOnce": true,
|
||||
"watchWithFilter": true,
|
||||
"whenever": true
|
||||
}
|
||||
}
|
6
frontend-new/.gitignore
vendored
Normal file
6
frontend-new/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
*.local
|
||||
dist
|
||||
dist-ssr
|
||||
node_modules
|
||||
**/_*
|
1
frontend-new/.npmrc
Normal file
1
frontend-new/.npmrc
Normal file
@ -0,0 +1 @@
|
||||
shamefully-hoist=true
|
5
frontend-new/.prettierignore
Normal file
5
frontend-new/.prettierignore
Normal file
@ -0,0 +1,5 @@
|
||||
/node_modules/**
|
||||
/.cache/**
|
||||
/dist/**
|
||||
/tests/unit/coverage/**
|
||||
**/.temp/**
|
7
frontend-new/.prettierrc
Normal file
7
frontend-new/.prettierrc
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"printWidth": 160,
|
||||
"arrowParens": "always"
|
||||
}
|
14
frontend-new/TODO.md
Normal file
14
frontend-new/TODO.md
Normal file
@ -0,0 +1,14 @@
|
||||
Stuff that needs to be done before I consider this a successful POC
|
||||
|
||||
- [x] initial setup (pages, layout, autoimport, basically the vitesse stuff)
|
||||
- [x] windi/tailwind
|
||||
- [x] store (with ssr)
|
||||
- [x] async data replacement
|
||||
- [ ] i18n
|
||||
- [x] api calls
|
||||
- [x] seo
|
||||
- [ ] route perms
|
||||
- [ ] error pages
|
||||
- [ ] snackbar
|
||||
- [ ] some basic pages
|
||||
- [ ] maybe deployment alongside the existing frontend?
|
24
frontend-new/index.html
Normal file
24
frontend-new/index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/pwa-192x192.png">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#00aba9">
|
||||
<meta name="msapplication-TileColor" content="#00aba9">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
(function() {
|
||||
const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const setting = localStorage.getItem('color-schema') || 'auto'
|
||||
if (setting === 'dark' || (prefersDark && setting !== 'light'))
|
||||
document.documentElement.classList.toggle('dark', true)
|
||||
})()
|
||||
</script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
76
frontend-new/package.json
Normal file
76
frontend-new/package.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite-ssr --port 3333",
|
||||
"dev:spa": "vite --port 3333",
|
||||
"build": "cross-env NODE_ENV=production vite-ssr build",
|
||||
"lint": "eslint --ext \".js,.vue,.ts,.json\" --ignore-path .gitignore --fix .",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^7.5.5",
|
||||
"@vueuse/head": "^0.7.5",
|
||||
"@vueuse/integrations": "^7.5.5",
|
||||
"axios": "^0.25.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.11",
|
||||
"prism-theme-vars": "^0.2.2",
|
||||
"qs": "^6.10.3",
|
||||
"universal-cookie": "^4.0.4",
|
||||
"vite-ssr": "^0.15.0",
|
||||
"vue": "^3.2.29",
|
||||
"vue-i18n": "^9.1.6",
|
||||
"vue-router": "^4.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^0.16.1",
|
||||
"@iconify/json": "^2.0.33",
|
||||
"@intlify/vite-plugin-vue-i18n": "^3.2.1",
|
||||
"@types/markdown-it-link-attributes": "^3.0.1",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@vitejs/plugin-vue": "^2.1.0",
|
||||
"@vue/compiler-sfc": "^3.2.29",
|
||||
"@vue/server-renderer": "^3.2.29",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-vue": "^8.4.1",
|
||||
"markdown-it-link-attributes": "^4.0.0",
|
||||
"markdown-it-prism": "^2.2.2",
|
||||
"node-fetch": "^3.2.0",
|
||||
"pnpm": "^6.29.1",
|
||||
"prettier": "2.5.1",
|
||||
"typescript": "^4.5.5",
|
||||
"unplugin-auto-import": "^0.5.11",
|
||||
"unplugin-icons": "^0.13.0",
|
||||
"unplugin-vue-components": "^0.17.16",
|
||||
"vite": "^2.7.13",
|
||||
"vite-plugin-eslint": "^1.3.0",
|
||||
"vite-plugin-md": "^0.11.7",
|
||||
"vite-plugin-pages": "^0.20.1",
|
||||
"vite-plugin-pwa": "^0.11.13",
|
||||
"vite-plugin-vue-layouts": "^0.6.0",
|
||||
"vite-plugin-windicss": "^1.6.3"
|
||||
},
|
||||
"//": "TODO steal from work",
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"@antfu/eslint-config",
|
||||
"prettier"
|
||||
],
|
||||
"env": {
|
||||
"shared-node-browser": true
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"no-console": "off"
|
||||
}
|
||||
}
|
||||
}
|
5920
frontend-new/pnpm-lock.yaml
Normal file
5920
frontend-new/pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
17
frontend-new/src/App.vue
Normal file
17
frontend-new/src/App.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { useHead } from '@vueuse/head';
|
||||
import { useSeo } from '~/composables/useSeo';
|
||||
|
||||
const title = 'Hangar New Test';
|
||||
const description = 'IDK WTF am doing';
|
||||
|
||||
useHead(useSeo(title, description, useRoute(), null));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="slide">
|
||||
<component :is="Component" :key="route" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
12
frontend-new/src/components/Announcement.vue
Normal file
12
frontend-new/src/components/Announcement.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import type { Announcement } from 'hangar-api';
|
||||
|
||||
const props = defineProps<{ announcement: Announcement }>();
|
||||
const { announcement } = toRefs(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :style="'background-color:' + announcement.color" class="mb-2 p-2 text-center">
|
||||
{{ announcement.text }}
|
||||
</div>
|
||||
</template>
|
7
frontend-new/src/components/Footer.vue
Normal file
7
frontend-new/src/components/Footer.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Footer</div>
|
||||
</template>
|
14
frontend-new/src/components/Header.vue
Normal file
14
frontend-new/src/components/Header.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import type { Announcement as AnnouncementObject } from 'hangar-api';
|
||||
import { useInitialState } from '~/composables/useInitialState';
|
||||
import { useInternalApi } from '~/composables/useApi';
|
||||
|
||||
// not sure if they need to be part of the initial state, since we directly render them, would only save a request on page switch at most, but I guess its a good demonstration
|
||||
const announcements = await useInitialState<AnnouncementObject[]>("announcements", async () => await useInternalApi<AnnouncementObject[]>("data/announcements", false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="announcements">
|
||||
<Announcement v-for="(announcement, idx) in announcements" :key="idx" :announcement="announcement" />
|
||||
</template>
|
||||
</template>
|
101
frontend-new/src/composables/useApi.ts
Normal file
101
frontend-new/src/composables/useApi.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import type { HangarApiException } from 'hangar-api';
|
||||
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||
import qs from 'qs';
|
||||
import { useCookies } from '@vueuse/integrations/useCookies';
|
||||
import Cookies from 'universal-cookie';
|
||||
import { useApiToken } from '~/composables/useApiToken';
|
||||
import { useAxios } from '~/composables/useAxios';
|
||||
|
||||
interface StatCookie {
|
||||
// eslint-disable-next-line camelcase
|
||||
hangar_stats: string;
|
||||
Path: string;
|
||||
'Max-Age': string;
|
||||
Expires: string;
|
||||
SameSite: true | false | 'lax' | 'strict' | 'none' | undefined;
|
||||
Secure?: boolean;
|
||||
}
|
||||
|
||||
function request<T>(
|
||||
url: string,
|
||||
token: string | null,
|
||||
method: AxiosRequestConfig['method'],
|
||||
data: object,
|
||||
header: Record<string, string> = {}
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = token ? { ...header, Authorization: `HangarAuth ${token}` } : header;
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
return useAxios
|
||||
.request<T>({
|
||||
method,
|
||||
url: `/api/${url}`,
|
||||
headers,
|
||||
data: method?.toLowerCase() !== 'get' ? data : {},
|
||||
params: method?.toLowerCase() === 'get' ? data : {},
|
||||
paramsSerializer: (params) => {
|
||||
return qs.stringify(params, {
|
||||
arrayFormat: 'repeat'
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(({ data, headers }) => {
|
||||
// check for stats cookie
|
||||
if (headers['set-cookie']) {
|
||||
const statString = headers['set-cookie'].find((c: string) => c.startsWith('hangar_stats'));
|
||||
if (statString) {
|
||||
const statCookie: StatCookie = new Cookies("statString") as unknown as StatCookie;
|
||||
|
||||
const cookies = useCookies();
|
||||
cookies.set('hangar_stats', statCookie.hangar_stats, {
|
||||
path: statCookie.Path,
|
||||
expires: new Date(statCookie.Expires),
|
||||
sameSite: 'strict',
|
||||
secure: statCookie.Secure
|
||||
});
|
||||
}
|
||||
}
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function useApi<T>(
|
||||
url: string,
|
||||
authed = true,
|
||||
method: AxiosRequestConfig['method'] = 'get',
|
||||
data: object = {},
|
||||
headers: Record<string, string> = {}
|
||||
): Promise<T> {
|
||||
const token = await useApiToken(authed);
|
||||
return request(`v1/${url}`, token, method, data, headers)
|
||||
}
|
||||
|
||||
export async function useInternalApi<T>(
|
||||
url: string,
|
||||
authed = true,
|
||||
method: AxiosRequestConfig['method'] = 'get',
|
||||
data: object = {},
|
||||
headers: Record<string, string> = {}
|
||||
): Promise<T> {
|
||||
const token = await useApiToken(authed);
|
||||
if (authed && !token) {
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
return Promise.reject({
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
data: {
|
||||
isHangarApiException: true,
|
||||
httpError: {
|
||||
statusCode: 401,
|
||||
},
|
||||
message: 'You must be logged in',
|
||||
} as HangarApiException,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return request(`internal/${url}`, token, method, data, headers);
|
||||
}
|
||||
}
|
48
frontend-new/src/composables/useApiToken.ts
Normal file
48
frontend-new/src/composables/useApiToken.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { JwtPayload } from 'jwt-decode';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import { useCookies } from '@vueuse/integrations';
|
||||
import { useAuthStore } from '~/store/auth';
|
||||
import { useAxios } from '~/composables/useAxios';
|
||||
|
||||
function refreshToken(): Promise<string | null> {
|
||||
return new Promise<string | null>((resolve) => {
|
||||
return useAxios
|
||||
.get<{ token: string; refreshToken: string; cookieName: string; expiresIn: number }>('/refresh')
|
||||
.then((value) => {
|
||||
useAuthStore().$patch({ token: value.data.token });
|
||||
useCookies().set(value.data.cookieName, value.data.refreshToken, {
|
||||
path: '/',
|
||||
expires: new Date(Date.now() + value.data.expiresIn * 1000),
|
||||
sameSite: 'strict',
|
||||
secure: process.env.nodeEnv === 'production'
|
||||
});
|
||||
resolve(value.data.token);
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function validateToken(token: string): boolean {
|
||||
const decodedToken = jwtDecode<JwtPayload>(token);
|
||||
if (!decodedToken.exp) {
|
||||
return false;
|
||||
}
|
||||
return decodedToken.exp * 1000 > Date.now() - 10 * 1000; // check against 10 seconds earlier to mitigate tokens expiring mid-request
|
||||
}
|
||||
|
||||
export function useApiToken(forceFetch = true): Promise<string | null> {
|
||||
const store = useAuthStore();
|
||||
if (store.token) {
|
||||
if (validateToken(store.token)) {
|
||||
return Promise.resolve(store.token);
|
||||
} else {
|
||||
return refreshToken();
|
||||
}
|
||||
} else if (forceFetch) {
|
||||
return refreshToken();
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
6
frontend-new/src/composables/useAxios.ts
Normal file
6
frontend-new/src/composables/useAxios.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import axios from 'axios';
|
||||
|
||||
export const useAxios: AxiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:3333'
|
||||
});
|
39
frontend-new/src/composables/useInitialState.ts
Normal file
39
frontend-new/src/composables/useInitialState.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import type { Ref } from "vue";
|
||||
import { onDeactivated, onMounted, onUnmounted, ref } from "vue";
|
||||
import { useContext } from "vite-ssr/vue";
|
||||
|
||||
export async function useInitialState<T>(key: string, handler: (type: "server" | "client") => Promise<T>, blocking = false) {
|
||||
const { initialState } = useContext();
|
||||
const responseValue = ref(initialState[key] || null) as Ref<T | null>;
|
||||
|
||||
// remove data from initialState when component unmounts or deactivates
|
||||
const removeState = () => {
|
||||
if (!import.meta.env.SSR) {
|
||||
initialState[key] = null;
|
||||
}
|
||||
};
|
||||
onUnmounted(removeState);
|
||||
onDeactivated(removeState);
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
// if on server, block until data can be stored in initialState
|
||||
const data = await handler("server");
|
||||
initialState[key] = data;
|
||||
responseValue.value = data;
|
||||
} else {
|
||||
// if on server, check if we already have data and use that
|
||||
if (initialState[key]) {
|
||||
responseValue.value = initialState[key];
|
||||
} else {
|
||||
// else do the request ourselves, blocking if needed
|
||||
const fn = async () => await handler("client");
|
||||
if (blocking) {
|
||||
await fn();
|
||||
} else {
|
||||
onMounted(fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return responseValue;
|
||||
}
|
121
frontend-new/src/composables/useSeo.ts
Normal file
121
frontend-new/src/composables/useSeo.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import type { TranslateResult } from 'vue-i18n';
|
||||
import type { HeadObject } from '@vueuse/head';
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router';
|
||||
|
||||
|
||||
export function useSeo(title: string | TranslateResult, description: string | TranslateResult | null, route: RouteLocationNormalizedLoaded, image: string | null): HeadObject {
|
||||
description = description || 'Plugin repository for Paper plugins and more!';
|
||||
const canonical = baseUrl() + (route.fullPath.endsWith('/') ? route.fullPath : `${route.fullPath}/`);
|
||||
image = image || 'https://paper.readthedocs.io/en/latest/_images/papermc_logomark_500.png';
|
||||
image = image.startsWith('http') ? image : baseUrl() + image;
|
||||
const seo = {
|
||||
title,
|
||||
link: [{ rel: 'canonical', href: canonical }],
|
||||
meta: [
|
||||
{ hid: 'description', name: 'description', content: description },
|
||||
{
|
||||
property: 'og:description',
|
||||
name: 'og:description',
|
||||
vmid: 'og:description',
|
||||
hid: 'og:description',
|
||||
content: description
|
||||
},
|
||||
{
|
||||
property: 'twitter:description',
|
||||
name: 'twitter:description',
|
||||
vmid: 'twitter:description',
|
||||
hid: 'twitter:description',
|
||||
content: description
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
name: 'og:title',
|
||||
hid: 'og:title',
|
||||
template: (chunk: string) => (chunk ? `${chunk} | Hangar` : 'Hangar'),
|
||||
content: title
|
||||
},
|
||||
{
|
||||
property: 'twitter:title',
|
||||
name: 'twitter:title',
|
||||
hid: 'twitter:title',
|
||||
template: (chunk: string) => (chunk ? `${chunk} | Hangar` : 'Hangar'),
|
||||
content: title
|
||||
},
|
||||
{
|
||||
property: 'og:url',
|
||||
name: 'og:url',
|
||||
hid: 'og:url',
|
||||
content: canonical
|
||||
},
|
||||
{
|
||||
property: 'twitter:url',
|
||||
name: 'twitter:url',
|
||||
hid: 'twitter:url',
|
||||
content: canonical
|
||||
},
|
||||
{
|
||||
property: 'og:image',
|
||||
name: 'og:image',
|
||||
hid: 'og:image',
|
||||
content: image
|
||||
}
|
||||
],
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
json: {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: generateBreadcrumbs(route)
|
||||
}
|
||||
}
|
||||
] as any[]
|
||||
} as HeadObject;
|
||||
|
||||
// todo renenable crowdin integration
|
||||
// if (context.app.i18n.locale === 'dum') {
|
||||
// console.log('found crowdin language activated, lets inject the script');
|
||||
// seo.script = seo.script ? seo.script : [];
|
||||
// seo.script.push({
|
||||
// type: 'text/javascript',
|
||||
// innerHTML: 'var _jipt = []; _jipt.push([\'project\', \'0cbf58a3d76226e92659632533015495\']); _jipt.push([\'domain\', \'hangar\']);'
|
||||
// });
|
||||
// seo.script.push({
|
||||
// type: 'text/javascript',
|
||||
// src: 'https://cdn.crowdin.com/jipt/jipt.js'
|
||||
// });
|
||||
// seo.__dangerouslyDisableSanitizers = ['script'];
|
||||
// }
|
||||
|
||||
return seo;
|
||||
}
|
||||
|
||||
function generateBreadcrumbs(route: RouteLocationNormalizedLoaded) {
|
||||
const arr = [];
|
||||
const split = route.fullPath.split('/');
|
||||
let curr = '';
|
||||
for (let i = 0; i < split.length; i++) {
|
||||
curr = `${curr + split[i]}/`;
|
||||
arr.push({
|
||||
'@type': 'ListItem',
|
||||
position: i,
|
||||
name: guessTitle(split[i]),
|
||||
item: baseUrl() + curr
|
||||
});
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function guessTitle(segment: string): string {
|
||||
if (segment === '/' || segment === '') {
|
||||
return 'Hangar';
|
||||
} else {
|
||||
return segment;
|
||||
}
|
||||
}
|
||||
|
||||
function baseUrl(): string {
|
||||
// todo get this from somewhere
|
||||
return 'https://hangar.benndorf.dev';
|
||||
}
|
7
frontend-new/src/layouts/default.vue
Normal file
7
frontend-new/src/layouts/default.vue
Normal file
@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<main>
|
||||
<Header />
|
||||
<router-view v-bind="$attrs" />
|
||||
<Footer />
|
||||
</main>
|
||||
</template>
|
9
frontend-new/src/layouts/error.vue
Normal file
9
frontend-new/src/layouts/error.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
Error
|
||||
</main>
|
||||
</template>
|
38
frontend-new/src/main.ts
Normal file
38
frontend-new/src/main.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import 'windi.css';
|
||||
import './styles/main.css';
|
||||
import viteSSR, { ClientOnly } from 'vite-ssr';
|
||||
import { createHead } from '@vueuse/head';
|
||||
import generatedRoutes from 'virtual:generated-pages';
|
||||
import { setupLayouts } from 'virtual:generated-layouts';
|
||||
import { createPinia } from 'pinia';
|
||||
import App from '~/App.vue';
|
||||
|
||||
const routes = setupLayouts(generatedRoutes);
|
||||
|
||||
const options: Parameters<typeof viteSSR>['1'] = {
|
||||
routes
|
||||
};
|
||||
|
||||
export default viteSSR( App, options, async (ctx) => {
|
||||
// install all modules under `modules/`
|
||||
Object.values(import.meta.globEager('./modules/*.ts')).map(i =>
|
||||
i.install?.(ctx)
|
||||
);
|
||||
|
||||
const { app, initialState } = ctx;
|
||||
|
||||
app.component(ClientOnly.name, ClientOnly);
|
||||
|
||||
const head = createHead();
|
||||
const pinia = createPinia();
|
||||
app.use(pinia).use(head);
|
||||
|
||||
if (import.meta.env.SSR) {
|
||||
initialState.pinia = pinia.state.value;
|
||||
} else {
|
||||
pinia.state.value = initialState.pinia;
|
||||
}
|
||||
|
||||
return { head };
|
||||
}
|
||||
);
|
9
frontend-new/src/modules/nprogress.ts
Normal file
9
frontend-new/src/modules/nprogress.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import NProgress from 'nprogress'
|
||||
import type { UserModule } from '~/types'
|
||||
|
||||
export const install: UserModule = ({ isClient, router }) => {
|
||||
if (isClient) {
|
||||
router.beforeEach(() => { NProgress.start() })
|
||||
router.afterEach(() => { NProgress.done() })
|
||||
}
|
||||
}
|
12
frontend-new/src/pages/index.vue
Normal file
12
frontend-new/src/pages/index.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>Index Page</div>
|
||||
</template>
|
||||
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
layout: default
|
||||
</route>
|
12
frontend-new/src/shims.d.ts
vendored
Normal file
12
frontend-new/src/shims.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/* eslint-disable import/no-duplicates */
|
||||
|
||||
declare interface Window {
|
||||
// extend the window
|
||||
}
|
||||
|
||||
// with vite-plugin-md, markdowns can be treat as Vue components
|
||||
declare module '*.md' {
|
||||
import type { ComponentOptions } from 'vue'
|
||||
const component: ComponentOptions
|
||||
export default component
|
||||
}
|
12
frontend-new/src/store/auth.ts
Normal file
12
frontend-new/src/store/auth.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { HangarUser } from 'hangar-internal';
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => {
|
||||
return {
|
||||
authenticated: false,
|
||||
user: null as HangarUser | null,
|
||||
token: null as string | null,
|
||||
routePermissions: null as string | null
|
||||
};
|
||||
}
|
||||
});
|
18
frontend-new/src/styles/main.css
Normal file
18
frontend-new/src/styles/main.css
Normal file
@ -0,0 +1,18 @@
|
||||
@import './markdown.css';
|
||||
|
||||
/* TODO do we need this? */
|
||||
#nprogress {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#nprogress .bar {
|
||||
@apply bg-teal-600 opacity-75;
|
||||
|
||||
position: fixed;
|
||||
z-index: 1031;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
}
|
48
frontend-new/src/styles/markdown.css
Normal file
48
frontend-new/src/styles/markdown.css
Normal file
@ -0,0 +1,48 @@
|
||||
/* https://github.com/antfu/prism-theme-vars */
|
||||
@import 'prism-theme-vars/base.css';
|
||||
|
||||
:root {
|
||||
--prism-font-family: 'Input Mono', monospace;
|
||||
}
|
||||
|
||||
html:not(.dark) {
|
||||
--prism-foreground: #393a34;
|
||||
--prism-background: #fbfbfb;
|
||||
--prism-comment: #8e8f8e;
|
||||
--prism-string: #a1644c;
|
||||
--prism-literal: #3a9c9b;
|
||||
--prism-keyword: #248358;
|
||||
--prism-function: #7e8a42;
|
||||
--prism-deleted: #a14f55;
|
||||
--prism-class: #2b91af;
|
||||
--prism-builtin: #a52727;
|
||||
--prism-property: #ad502b;
|
||||
--prism-namespace: #c96880;
|
||||
--prism-punctuation: #8e8f8b;
|
||||
--prism-decorator: #bd8f8f;
|
||||
--prism-json-property: #698c96;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--prism-scheme: dark;
|
||||
--prism-foreground: #d4cfbf;
|
||||
--prism-background: #1e1e1e;
|
||||
--prism-comment: #758575;
|
||||
--prism-string: #ce9178;
|
||||
--prism-literal: #4fb09d;
|
||||
--prism-keyword: #4d9375;
|
||||
--prism-function: #c2c275;
|
||||
--prism-deleted: #a14f55;
|
||||
--prism-class: #5ebaa8;
|
||||
--prism-builtin: #cb7676;
|
||||
--prism-property: #dd8e6e;
|
||||
--prism-namespace: #c96880;
|
||||
--prism-punctuation: #d4d4d4;
|
||||
--prism-decorator: #bd8f8f;
|
||||
--prism-regex: #ab5e3f;
|
||||
--prism-json-property: #6b8b9e;
|
||||
--prism-line-number: #888888;
|
||||
--prism-line-number-gutter: #eeeeee;
|
||||
--prism-line-highlight-background: #444444;
|
||||
--prism-selection-background: #444444;
|
||||
}
|
9
frontend-new/src/types.ts
Normal file
9
frontend-new/src/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import type { Context } from 'vite-ssr/vue';
|
||||
import type { App } from 'vue'
|
||||
import type { RouteRecord, Router } from 'vue-router';
|
||||
|
||||
export type UserModule = (ctx: Context & {
|
||||
app: App
|
||||
router: Router
|
||||
initialRoute: RouteRecord
|
||||
}) => void;
|
42
frontend-new/src/types/api.d.ts
vendored
Normal file
42
frontend-new/src/types/api.d.ts
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
declare module 'hangar-api' {
|
||||
import type { Visibility } from '~/types/enums';
|
||||
|
||||
interface Model {
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Named {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TagColor {
|
||||
background: string;
|
||||
foreground: string;
|
||||
}
|
||||
|
||||
interface Pagination {
|
||||
limit: number;
|
||||
offset: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface PaginatedResult<T extends Model> {
|
||||
pagination: Pagination;
|
||||
result: T[];
|
||||
}
|
||||
|
||||
interface Announcement {
|
||||
text: String;
|
||||
color: String;
|
||||
}
|
||||
|
||||
interface Sponsor {
|
||||
image: String;
|
||||
name: String;
|
||||
link: String;
|
||||
}
|
||||
|
||||
interface Visible {
|
||||
visibility: Visibility;
|
||||
}
|
||||
}
|
0
frontend-new/src/types/api/.gitkeep
Normal file
0
frontend-new/src/types/api/.gitkeep
Normal file
42
frontend-new/src/types/api/exceptions.d.ts
vendored
Normal file
42
frontend-new/src/types/api/exceptions.d.ts
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
declare module 'hangar-api' {
|
||||
interface HangarException {
|
||||
message: string;
|
||||
messageArgs: any[];
|
||||
httpError: {
|
||||
statusCode: number;
|
||||
statusPhrase: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface HangarApiException extends HangarException {
|
||||
isHangarApiException: true;
|
||||
isMultiException: false;
|
||||
}
|
||||
|
||||
interface MultiHangarApiException {
|
||||
isMultiException: true;
|
||||
isHangarApiException: true;
|
||||
exceptions: HangarApiException[];
|
||||
}
|
||||
|
||||
interface ObjectError {
|
||||
code: string;
|
||||
errorMsg: string;
|
||||
}
|
||||
|
||||
interface GlobalError extends ObjectError {
|
||||
objectName: string;
|
||||
}
|
||||
|
||||
interface FieldError extends ObjectError {
|
||||
fieldName: string;
|
||||
rejectedValue: string;
|
||||
}
|
||||
|
||||
interface HangarValidationException extends HangarException {
|
||||
isHangarValidationException: true;
|
||||
object: string;
|
||||
globalErrors: GlobalError[];
|
||||
fieldErrors: FieldError[];
|
||||
}
|
||||
}
|
20
frontend-new/src/types/api/permissions.d.ts
vendored
Normal file
20
frontend-new/src/types/api/permissions.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
declare module 'hangar-api' {
|
||||
import { NamedPermission, PermissionType } from '~/types/enums';
|
||||
|
||||
interface IPermission {
|
||||
value: NamedPermission;
|
||||
frontendName: string;
|
||||
permission: bigint;
|
||||
}
|
||||
|
||||
interface PermissionCheck {
|
||||
type: PermissionType;
|
||||
result: boolean;
|
||||
}
|
||||
|
||||
interface UserPermissions {
|
||||
type: PermissionType;
|
||||
permissionBinString: string;
|
||||
permissions: IPermission[];
|
||||
}
|
||||
}
|
75
frontend-new/src/types/api/projects.d.ts
vendored
Normal file
75
frontend-new/src/types/api/projects.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
declare module 'hangar-api' {
|
||||
import { Model, Named, TagColor, Visible } from 'hangar-api';
|
||||
import { ProjectCategory } from '~/types/enums';
|
||||
|
||||
interface ProjectNamespace {
|
||||
owner: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface ProjectStats {
|
||||
views: number;
|
||||
downloads: number;
|
||||
recentViews: number;
|
||||
recentDownloads: number;
|
||||
stars: number;
|
||||
watchers: number;
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
starred: boolean;
|
||||
watching: boolean;
|
||||
flagged: boolean;
|
||||
}
|
||||
|
||||
interface License {
|
||||
name: string | null;
|
||||
url: string | null;
|
||||
type: string | null;
|
||||
}
|
||||
|
||||
interface ProjectSettings {
|
||||
homepage: string | null;
|
||||
issues: string | null;
|
||||
source: string | null;
|
||||
support: string | null;
|
||||
license: License;
|
||||
keywords: string[];
|
||||
forumSync: boolean;
|
||||
donation: {
|
||||
enable: false;
|
||||
email: string | null;
|
||||
defaultAmount: number;
|
||||
oneTimeAmounts: Array<number>;
|
||||
monthlyAmounts: Array<number>;
|
||||
};
|
||||
}
|
||||
|
||||
interface PromotedVersionTag extends Named {
|
||||
data: string;
|
||||
displayData: string;
|
||||
minecraftVersion: string;
|
||||
color: TagColor;
|
||||
}
|
||||
|
||||
interface PromotedVersion {
|
||||
version: string;
|
||||
tags: PromotedVersionTag[];
|
||||
}
|
||||
|
||||
interface ProjectCompact extends Model, Named, Visible {
|
||||
namespace: ProjectNamespace;
|
||||
stats: ProjectStats;
|
||||
category: ProjectCategory;
|
||||
}
|
||||
|
||||
interface Project extends ProjectCompact {
|
||||
description: string;
|
||||
lastUpdated: Date;
|
||||
userActions: UserActions;
|
||||
settings: ProjectSettings;
|
||||
postId: number;
|
||||
topicId: number;
|
||||
promotedVersions: PromotedVersion[];
|
||||
}
|
||||
}
|
30
frontend-new/src/types/api/users.d.ts
vendored
Normal file
30
frontend-new/src/types/api/users.d.ts
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
declare module 'hangar-api' {
|
||||
import { Model, Named } from 'hangar-api';
|
||||
import { NamedPermission, RoleCategory } from '~/types/enums';
|
||||
|
||||
interface Role {
|
||||
assignable: boolean;
|
||||
rank?: number | null;
|
||||
value: string;
|
||||
roleId: number;
|
||||
category: RoleCategory;
|
||||
permissions: string;
|
||||
title: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface User extends Model, Named {
|
||||
tagline: string | null;
|
||||
joinDate: string;
|
||||
roles: Role[];
|
||||
projectCount: number;
|
||||
isOrganization: boolean;
|
||||
locked: boolean;
|
||||
}
|
||||
|
||||
interface ApiKey extends Model, Named {
|
||||
token?: string;
|
||||
tokenIdentifier?: string;
|
||||
permissions: NamedPermission[];
|
||||
}
|
||||
}
|
41
frontend-new/src/types/api/versions.d.ts
vendored
Normal file
41
frontend-new/src/types/api/versions.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
declare module 'hangar-api' {
|
||||
import { Model, Named, ProjectNamespace, TagColor, Visible } from 'hangar-api';
|
||||
import { Platform, ReviewState } from '~/types/enums';
|
||||
|
||||
interface PluginDependency extends Named {
|
||||
required: boolean;
|
||||
namespace: ProjectNamespace | null;
|
||||
externalUrl: string | null;
|
||||
}
|
||||
|
||||
interface VersionStats {
|
||||
downloads: number;
|
||||
}
|
||||
|
||||
interface FileInfo {
|
||||
name: string;
|
||||
sizeBytes: number;
|
||||
md5Hash: string;
|
||||
}
|
||||
|
||||
interface Tag extends Named {
|
||||
data: string;
|
||||
color: TagColor;
|
||||
}
|
||||
|
||||
interface DependencyVersion {
|
||||
pluginDependencies: Record<Platform, PluginDependency[]>;
|
||||
}
|
||||
|
||||
interface Version extends Model, Named, DependencyVersion, Visible {
|
||||
platformDependencies: Record<Platform, string[]>;
|
||||
description: string;
|
||||
stats: VersionStats;
|
||||
fileInfo: FileInfo;
|
||||
externalUrl: string | null;
|
||||
author: String;
|
||||
reviewState: ReviewState;
|
||||
tags: Tag[];
|
||||
recommended: Platform[];
|
||||
}
|
||||
}
|
223
frontend-new/src/types/auto-imports.d.ts
vendored
Normal file
223
frontend-new/src/types/auto-imports.d.ts
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {
|
||||
const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']
|
||||
const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
|
||||
const autoResetRef: typeof import('@vueuse/core')['autoResetRef']
|
||||
const axios: typeof import('axios')['default']
|
||||
const biSyncRef: typeof import('@vueuse/core')['biSyncRef']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
const debouncedRef: typeof import('@vueuse/core')['debouncedRef']
|
||||
const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch']
|
||||
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
|
||||
const defineComponent: typeof import('vue')['defineComponent']
|
||||
const defineStore: typeof import('pinia')['defineStore']
|
||||
const eagerComputed: typeof import('@vueuse/core')['eagerComputed']
|
||||
const effectScope: typeof import('vue')['effectScope']
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const extendRef: typeof import('@vueuse/core')['extendRef']
|
||||
const getActivePinia: typeof import('pinia')['getActivePinia']
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const h: typeof import('vue')['h']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
const mapState: typeof import('pinia')['mapState']
|
||||
const mapStores: typeof import('pinia')['mapStores']
|
||||
const mapWritableState: typeof import('pinia')['mapWritableState']
|
||||
const markRaw: typeof import('vue')['markRaw']
|
||||
const nextTick: typeof import('vue')['nextTick']
|
||||
const onActivated: typeof import('vue')['onActivated']
|
||||
const onBeforeMount: typeof import('vue')['onBeforeMount']
|
||||
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
|
||||
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
|
||||
const onClickOutside: typeof import('@vueuse/core')['onClickOutside']
|
||||
const onDeactivated: typeof import('vue')['onDeactivated']
|
||||
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
|
||||
const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke']
|
||||
const onMounted: typeof import('vue')['onMounted']
|
||||
const onRenderTracked: typeof import('vue')['onRenderTracked']
|
||||
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
|
||||
const onScopeDispose: typeof import('vue')['onScopeDispose']
|
||||
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
|
||||
const onStartTyping: typeof import('@vueuse/core')['onStartTyping']
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
const reactive: typeof import('vue')['reactive']
|
||||
const reactivePick: typeof import('@vueuse/core')['reactivePick']
|
||||
const readonly: typeof import('vue')['readonly']
|
||||
const ref: typeof import('vue')['ref']
|
||||
const refDefault: typeof import('@vueuse/core')['refDefault']
|
||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
const shallowRef: typeof import('vue')['shallowRef']
|
||||
const storeToRefs: typeof import('pinia')['storeToRefs']
|
||||
const syncRef: typeof import('@vueuse/core')['syncRef']
|
||||
const templateRef: typeof import('@vueuse/core')['templateRef']
|
||||
const throttledRef: typeof import('@vueuse/core')['throttledRef']
|
||||
const throttledWatch: typeof import('@vueuse/core')['throttledWatch']
|
||||
const toRaw: typeof import('vue')['toRaw']
|
||||
const toReactive: typeof import('@vueuse/core')['toReactive']
|
||||
const toRef: typeof import('vue')['toRef']
|
||||
const toRefs: typeof import('vue')['toRefs']
|
||||
const triggerRef: typeof import('vue')['triggerRef']
|
||||
const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount']
|
||||
const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted']
|
||||
const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose']
|
||||
const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted']
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints']
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useClamp: typeof import('@vueuse/core')['useClamp']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
const useCssVar: typeof import('@vueuse/core')['useCssVar']
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useCycleList: typeof import('@vueuse/core')['useCycleList']
|
||||
const useDark: typeof import('@vueuse/core')['useDark']
|
||||
const useDebounce: typeof import('@vueuse/core')['useDebounce']
|
||||
const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory']
|
||||
const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn']
|
||||
const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion']
|
||||
const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation']
|
||||
const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio']
|
||||
const useDevicesList: typeof import('@vueuse/core')['useDevicesList']
|
||||
const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia']
|
||||
const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility']
|
||||
const useDraggable: typeof import('@vueuse/core')['useDraggable']
|
||||
const useElementBounding: typeof import('@vueuse/core')['useElementBounding']
|
||||
const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint']
|
||||
const useElementHover: typeof import('@vueuse/core')['useElementHover']
|
||||
const useElementSize: typeof import('@vueuse/core')['useElementSize']
|
||||
const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility']
|
||||
const useEventBus: typeof import('@vueuse/core')['useEventBus']
|
||||
const useEventListener: typeof import('@vueuse/core')['useEventListener']
|
||||
const useEventSource: typeof import('@vueuse/core')['useEventSource']
|
||||
const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper']
|
||||
const useFavicon: typeof import('@vueuse/core')['useFavicon']
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
const useFullscreen: typeof import('@vueuse/core')['useFullscreen']
|
||||
const useGeolocation: typeof import('@vueuse/core')['useGeolocation']
|
||||
const useHead: typeof import('@vueuse/head')['useHead']
|
||||
const useI18n: typeof import('vue-i18n')['useI18n']
|
||||
const useIdle: typeof import('@vueuse/core')['useIdle']
|
||||
const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver']
|
||||
const useInterval: typeof import('@vueuse/core')['useInterval']
|
||||
const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn']
|
||||
const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier']
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
const useMousePressed: typeof import('@vueuse/core')['useMousePressed']
|
||||
const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver']
|
||||
const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage']
|
||||
const useNetwork: typeof import('@vueuse/core')['useNetwork']
|
||||
const useNow: typeof import('@vueuse/core')['useNow']
|
||||
const useOnline: typeof import('@vueuse/core')['useOnline']
|
||||
const usePageLeave: typeof import('@vueuse/core')['usePageLeave']
|
||||
const useParallax: typeof import('@vueuse/core')['useParallax']
|
||||
const usePermission: typeof import('@vueuse/core')['usePermission']
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea']
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
const useSlots: typeof import('vue')['useSlots']
|
||||
const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition']
|
||||
const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis']
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
const useTextSelection: typeof import('@vueuse/core')['useTextSelection']
|
||||
const useThrottle: typeof import('@vueuse/core')['useThrottle']
|
||||
const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory']
|
||||
const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn']
|
||||
const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo']
|
||||
const useTimeout: typeof import('@vueuse/core')['useTimeout']
|
||||
const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn']
|
||||
const useTimestamp: typeof import('@vueuse/core')['useTimestamp']
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
const useWebWorker: typeof import('@vueuse/core')['useWebWorker']
|
||||
const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn']
|
||||
const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus']
|
||||
const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll']
|
||||
const useWindowSize: typeof import('@vueuse/core')['useWindowSize']
|
||||
const watch: typeof import('vue')['watch']
|
||||
const watchAtMost: typeof import('@vueuse/core')['watchAtMost']
|
||||
const watchEffect: typeof import('vue')['watchEffect']
|
||||
const watchOnce: typeof import('@vueuse/core')['watchOnce']
|
||||
const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter']
|
||||
const whenever: typeof import('@vueuse/core')['whenever']
|
||||
}
|
||||
export {}
|
13
frontend-new/src/types/components.d.ts
vendored
Normal file
13
frontend-new/src/types/components.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// generated by unplugin-vue-components
|
||||
// We suggest you to commit this file into source control
|
||||
// Read more: https://github.com/vuejs/vue-next/pull/3399
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Announcement: typeof import('./../components/Announcement.vue')['default']
|
||||
Footer: typeof import('./../components/Footer.vue')['default']
|
||||
Header: typeof import('./../components/Header.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export { }
|
108
frontend-new/src/types/enums.ts
Normal file
108
frontend-new/src/types/enums.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum RoleCategory {
|
||||
GLOBAL = 'global',
|
||||
PROJECT = 'project',
|
||||
ORGANIZATION = 'organization',
|
||||
}
|
||||
|
||||
export enum ProjectCategory {
|
||||
ADMIN_TOOLS = 'admin_tools',
|
||||
CHAT = 'chat',
|
||||
DEV_TOOLS = 'dev_tools',
|
||||
ECONOMY = 'economy',
|
||||
GAMEPLAY = 'gameplay',
|
||||
GAMES = 'games',
|
||||
PROTECTION = 'protection',
|
||||
ROLE_PLAYING = 'role_playing',
|
||||
WORLD_MANAGEMENT = 'world_management',
|
||||
MISC = 'misc',
|
||||
UNDEFINED = 'undefined',
|
||||
}
|
||||
|
||||
export enum Visibility {
|
||||
PUBLIC = 'public',
|
||||
NEW = 'new',
|
||||
NEEDS_CHANGES = 'needsChanges',
|
||||
NEEDS_APPROVAL = 'needsApproval',
|
||||
SOFT_DELETE = 'softDelete',
|
||||
}
|
||||
|
||||
export enum ReviewState {
|
||||
UNREVIEWED = 'unreviewed',
|
||||
REVIEWED = 'reviewed',
|
||||
UNDER_REVIEW = 'under_review',
|
||||
BACKLOG = 'backlog',
|
||||
PARTIALLY_REVIEWED = 'partially_reviewed',
|
||||
}
|
||||
|
||||
export enum PermissionType {
|
||||
GLOBAL = 'global',
|
||||
PROJECT = 'project',
|
||||
ORGANIZATION = 'organization',
|
||||
}
|
||||
|
||||
export enum NamedPermission {
|
||||
VIEW_PUBLIC_INFO = 'view_public_info',
|
||||
EDIT_OWN_USER_SETTINGS = 'edit_own_user_settings',
|
||||
EDIT_API_KEYS = 'edit_api_keys',
|
||||
|
||||
EDIT_SUBJECT_SETTINGS = 'edit_subject_settings',
|
||||
MANAGE_SUBJECT_MEMBERS = 'manage_subject_members',
|
||||
IS_SUBJECT_OWNER = 'is_subject_owner',
|
||||
IS_SUBJECT_MEMBER = 'is_subject_member',
|
||||
|
||||
CREATE_PROJECT = 'create_project',
|
||||
EDIT_PAGE = 'edit_page',
|
||||
DELETE_PROJECT = 'delete_project',
|
||||
|
||||
CREATE_VERSION = 'create_version',
|
||||
EDIT_VERSION = 'edit_version',
|
||||
DELETE_VERSION = 'delete_version',
|
||||
EDIT_TAGS = 'edit_tags',
|
||||
|
||||
CREATE_ORGANIZATION = 'create_organization',
|
||||
POST_AS_ORGANIZATION = 'post_as_organization',
|
||||
|
||||
MOD_NOTES_AND_FLAGS = 'mod_notes_and_flags',
|
||||
SEE_HIDDEN = 'see_hidden',
|
||||
IS_STAFF = 'is_staff',
|
||||
REVIEWER = 'reviewer',
|
||||
|
||||
VIEW_HEALTH = 'view_health',
|
||||
VIEW_IP = 'view_ip',
|
||||
VIEW_STATS = 'view_stats',
|
||||
VIEW_LOGS = 'view_logs',
|
||||
|
||||
MANUAL_VALUE_CHANGES = 'manual_value_changes',
|
||||
HARD_DELETE_PROJECT = 'hard_delete_project',
|
||||
HARD_DELETE_VERSION = 'hard_delete_version',
|
||||
EDIT_ALL_USER_SETTINGS = 'edit_all_user_settings',
|
||||
}
|
||||
|
||||
export enum Platform {
|
||||
PAPER = 'PAPER',
|
||||
WATERFALL = 'WATERFALL',
|
||||
VELOCITY = 'VELOCITY',
|
||||
}
|
||||
|
||||
export enum ReviewAction {
|
||||
START = 'START',
|
||||
MESSAGE = 'MESSAGE',
|
||||
STOP = 'STOP',
|
||||
REOPEN = 'REOPEN',
|
||||
APPROVE = 'APPROVE',
|
||||
PARTIALLY_APPROVE = 'PARTIALLY_APPROVE',
|
||||
UNDO_APPROVAL = 'UNDO_APPROVAL',
|
||||
}
|
||||
|
||||
export enum LogContext {
|
||||
PROJECT = 'PROJECT',
|
||||
VERSION = 'VERSION',
|
||||
PAGE = 'PAGE',
|
||||
USER = 'USER',
|
||||
ORGANIZATION = 'ORGANIZATION',
|
||||
}
|
||||
|
||||
export enum Prompt {
|
||||
CHANGE_AVATAR = 'CHANGE_AVATAR',
|
||||
}
|
87
frontend-new/src/types/internal/admin.d.ts
vendored
Normal file
87
frontend-new/src/types/internal/admin.d.ts
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { Model, ProjectNamespace } from 'hangar-api';
|
||||
import { LogContext, Platform, Visibility } from '~/types/enums';
|
||||
|
||||
interface LogProject {
|
||||
id: number;
|
||||
slug: string;
|
||||
owner: string;
|
||||
}
|
||||
|
||||
interface LogVersion {
|
||||
id: number;
|
||||
versionString: string;
|
||||
platforms: Platform[];
|
||||
}
|
||||
|
||||
interface LogPage {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface LogSubject {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface LoggedActionType {
|
||||
pgLoggedAction: string;
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface LoggedAction extends Model {
|
||||
userId: number | null;
|
||||
userName: string | null;
|
||||
address: string;
|
||||
action: LoggedActionType;
|
||||
contextType: LogContext;
|
||||
newState: string;
|
||||
oldState: string;
|
||||
project: LogProject | null;
|
||||
version: LogVersion | null;
|
||||
page: LogPage | null;
|
||||
subject: LogSubject | null;
|
||||
}
|
||||
|
||||
interface Job extends Model {
|
||||
jobType: string;
|
||||
state: string;
|
||||
lastError: string;
|
||||
lastErrorDescriptor: string;
|
||||
retryAt: string;
|
||||
lastUpdated: string;
|
||||
jobProperties: { [key: string]: string };
|
||||
}
|
||||
|
||||
interface MissingFile {
|
||||
platform: Platform;
|
||||
versionString: string;
|
||||
fileName: string;
|
||||
namespace: ProjectNamespace;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface UnhealthyProject {
|
||||
namespace: ProjectNamespace;
|
||||
topicId: number | null;
|
||||
postId: number | null;
|
||||
lastUpdated: string;
|
||||
visibility: Visibility;
|
||||
}
|
||||
|
||||
interface Activity {
|
||||
namespace: ProjectNamespace;
|
||||
}
|
||||
|
||||
interface FlagActivity extends Activity {
|
||||
resolvedAt: string;
|
||||
}
|
||||
|
||||
interface ReviewActivity extends Activity {
|
||||
endedAt: string;
|
||||
versionString: string;
|
||||
platforms: Platform[];
|
||||
}
|
||||
}
|
49
frontend-new/src/types/internal/data.d.ts
vendored
Normal file
49
frontend-new/src/types/internal/data.d.ts
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { Model, TagColor } from 'hangar-api';
|
||||
import { Platform, ProjectCategory, Prompt, Visibility } from '~/types/enums';
|
||||
|
||||
interface Table extends Model {
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface IProjectCategory {
|
||||
title: string;
|
||||
icon: string;
|
||||
apiName: ProjectCategory;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface FlagReason {
|
||||
type: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface Color {
|
||||
name: string;
|
||||
hex: string;
|
||||
}
|
||||
|
||||
interface IPlatform {
|
||||
name: string;
|
||||
enumName: Platform;
|
||||
category: 'Server' | 'Proxy';
|
||||
url: string;
|
||||
tagColor: TagColor;
|
||||
possibleVersions: string[];
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface IVisibility {
|
||||
name: Visibility;
|
||||
showModal: boolean;
|
||||
cssClass: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
interface IPrompt {
|
||||
ordinal: number;
|
||||
name: Prompt;
|
||||
titleKey: string;
|
||||
messageKey: string;
|
||||
}
|
||||
}
|
100
frontend-new/src/types/internal/projects.d.ts
vendored
Normal file
100
frontend-new/src/types/internal/projects.d.ts
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { Joinable, Table } from 'hangar-internal';
|
||||
import { Project, ProjectNamespace } from 'hangar-api';
|
||||
import { ProjectCategory, Visibility } from '~/types/enums';
|
||||
|
||||
interface ProjectOwner {
|
||||
id: number;
|
||||
name: string;
|
||||
userId: number;
|
||||
isOrganization: boolean;
|
||||
}
|
||||
|
||||
interface HangarProjectInfo {
|
||||
publicVersions: number;
|
||||
flagCount: number;
|
||||
noteCount: number;
|
||||
starCount: number;
|
||||
watcherCount: number;
|
||||
}
|
||||
|
||||
interface HangarProjectPage {
|
||||
id: number;
|
||||
name: string;
|
||||
slug: string;
|
||||
home: boolean;
|
||||
children: HangarProjectPage[];
|
||||
}
|
||||
|
||||
interface HangarProject extends Joinable, Project, Table {
|
||||
lastVisibilityChangeComment: string;
|
||||
lastVisibilityChangeUserName: string;
|
||||
info: HangarProjectInfo;
|
||||
pages: HangarProjectPage[];
|
||||
recommendedVersions: {
|
||||
[key: string]: string | null; // Platform: externalUrl (if any)
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectPage extends Table {
|
||||
name: string;
|
||||
slug: string;
|
||||
contents: string;
|
||||
deletable: boolean;
|
||||
isHome: boolean;
|
||||
}
|
||||
|
||||
interface Flag extends Table {
|
||||
userId: number; //
|
||||
reportedByName: string; //
|
||||
reason: string; //
|
||||
resolved: boolean; //
|
||||
comment: string; //
|
||||
resolvedAt: string | null; //
|
||||
resolvedBy: number | null; //
|
||||
resolvedByName: string | null; //
|
||||
projectId: number; //
|
||||
projectNamespace: ProjectNamespace;
|
||||
projectVisibility: Visibility;
|
||||
}
|
||||
|
||||
interface Note extends Table {
|
||||
projectId: number;
|
||||
message: string;
|
||||
userId: number;
|
||||
userName: string | null;
|
||||
}
|
||||
|
||||
interface ProjectSettingsForm {
|
||||
settings: {
|
||||
homepage: string | null;
|
||||
issues: string | null;
|
||||
source: string | null;
|
||||
support: string | null;
|
||||
keywords: string[];
|
||||
license: {
|
||||
type?: string | null;
|
||||
url: string | null;
|
||||
name: string | null;
|
||||
};
|
||||
donation: {
|
||||
enable: false;
|
||||
email: string | null;
|
||||
defaultAmount: number;
|
||||
oneTimeAmounts: Array<string>;
|
||||
monthlyAmounts: Array<string>;
|
||||
};
|
||||
forumSync: false;
|
||||
};
|
||||
category: ProjectCategory;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ProjectApproval {
|
||||
projectId: number;
|
||||
namespace: ProjectNamespace;
|
||||
visibility: Visibility;
|
||||
comment: string;
|
||||
changeRequester: string;
|
||||
}
|
||||
}
|
36
frontend-new/src/types/internal/reviews.d.ts
vendored
Normal file
36
frontend-new/src/types/internal/reviews.d.ts
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { Model, ProjectNamespace } from 'hangar-api';
|
||||
import { Platform, ReviewAction } from '~/types/enums';
|
||||
|
||||
interface HangarReviewMessage extends Model {
|
||||
message: string;
|
||||
args: Record<string, string>;
|
||||
action: ReviewAction;
|
||||
}
|
||||
|
||||
interface HangarReview extends Model {
|
||||
endedAt: string | null;
|
||||
userName: string;
|
||||
userId: number;
|
||||
messages: HangarReviewMessage[];
|
||||
}
|
||||
|
||||
interface Review {
|
||||
reviewerName: string;
|
||||
reviewStarted: string;
|
||||
reviewEnded: string | null;
|
||||
lastAction: ReviewAction;
|
||||
}
|
||||
|
||||
interface ReviewQueueEntry {
|
||||
namespace: ProjectNamespace;
|
||||
versionId: number;
|
||||
versionString: string;
|
||||
platforms: Platform[];
|
||||
versionCreatedAt: string;
|
||||
versionAuthor: string;
|
||||
channelName: string;
|
||||
channelColor: string;
|
||||
reviews: Review[];
|
||||
}
|
||||
}
|
75
frontend-new/src/types/internal/users.d.ts
vendored
Normal file
75
frontend-new/src/types/internal/users.d.ts
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { Named, Role, User } from 'hangar-api';
|
||||
import { ProjectOwner, Table } from 'hangar-internal';
|
||||
import { RoleCategory } from '~/types/enums';
|
||||
|
||||
interface HangarNotification {
|
||||
id: number;
|
||||
action: string;
|
||||
message: string[];
|
||||
read: boolean;
|
||||
originUserName: string | null;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface Invite {
|
||||
roleTableId: number;
|
||||
role: Role;
|
||||
type: 'project' | 'organization';
|
||||
name: string;
|
||||
url: string;
|
||||
accepted?: boolean;
|
||||
}
|
||||
|
||||
interface Invites {
|
||||
project: Invite[];
|
||||
organization: Invite[];
|
||||
}
|
||||
|
||||
interface HeaderData {
|
||||
globalPermission: string;
|
||||
unreadNotifications: number;
|
||||
unansweredInvites: number;
|
||||
unresolvedFlags: number;
|
||||
projectApprovals: number;
|
||||
reviewQueueCount: number;
|
||||
organizationCount: number;
|
||||
}
|
||||
|
||||
interface HangarUser extends User, Table {
|
||||
headerData: HeaderData;
|
||||
readPrompts: number[];
|
||||
language: string;
|
||||
}
|
||||
|
||||
interface UserTable extends Table, Named {
|
||||
tagline: string;
|
||||
joinDate: string;
|
||||
readPrompts: number[];
|
||||
locked: boolean;
|
||||
language: string;
|
||||
}
|
||||
|
||||
interface RoleTable extends Table {
|
||||
accepted: boolean;
|
||||
principalId: number;
|
||||
userId: number;
|
||||
role: Role;
|
||||
}
|
||||
|
||||
interface JoinableMember {
|
||||
user: UserTable;
|
||||
role: RoleTable;
|
||||
hidden: boolean;
|
||||
}
|
||||
|
||||
interface Joinable {
|
||||
owner: ProjectOwner;
|
||||
roleCategory: RoleCategory;
|
||||
members: JoinableMember[];
|
||||
}
|
||||
|
||||
interface Organization extends Joinable {
|
||||
id: number;
|
||||
}
|
||||
}
|
40
frontend-new/src/types/internal/versions.d.ts
vendored
Normal file
40
frontend-new/src/types/internal/versions.d.ts
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
declare module 'hangar-internal' {
|
||||
import { DependencyVersion, FileInfo, Named, ProjectNamespace, Version } from 'hangar-api';
|
||||
import { Table } from 'hangar-internal';
|
||||
import { Platform } from '~/types/enums';
|
||||
|
||||
interface PlatformDependency {
|
||||
name: string;
|
||||
required: boolean;
|
||||
namespace: ProjectNamespace | null;
|
||||
externalUrl: string | null;
|
||||
}
|
||||
|
||||
interface PendingVersion extends DependencyVersion {
|
||||
platformDependencies: Record<Platform, string[]>;
|
||||
versionString: string | null;
|
||||
description: string | null;
|
||||
fileInfo: FileInfo;
|
||||
externalUrl: string | null;
|
||||
channelName: string;
|
||||
channelColor: string;
|
||||
channelNonReviewed: boolean;
|
||||
forumSync: boolean;
|
||||
unstable: boolean;
|
||||
recommended: boolean;
|
||||
isFile: boolean;
|
||||
}
|
||||
|
||||
interface ProjectChannel extends Named, Partial<Table> {
|
||||
color: string;
|
||||
nonReviewed: boolean;
|
||||
editable: boolean;
|
||||
temp?: boolean;
|
||||
versionCount: number;
|
||||
}
|
||||
|
||||
interface HangarVersion extends Version {
|
||||
id: number;
|
||||
approvedBy?: string;
|
||||
}
|
||||
}
|
5
frontend-new/src/types/swagger-ui.d.ts
vendored
Normal file
5
frontend-new/src/types/swagger-ui.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module 'swagger-ui' {
|
||||
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||
|
||||
export default SwaggerUIBundle;
|
||||
}
|
26
frontend-new/tsconfig.json
Normal file
26
frontend-new/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"target": "es2017",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"types": [
|
||||
"vite/client",
|
||||
"vite-plugin-pages/client",
|
||||
"vite-plugin-vue-layouts/client"
|
||||
],
|
||||
"paths": {
|
||||
"~/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
173
frontend-new/vite.config.ts
Normal file
173
frontend-new/vite.config.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import Vue from "@vitejs/plugin-vue";
|
||||
import Pages from "vite-plugin-pages";
|
||||
import Layouts from "vite-plugin-vue-layouts";
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import Icons from "unplugin-icons/vite";
|
||||
import IconsResolver from "unplugin-icons/resolver";
|
||||
import Markdown from "vite-plugin-md";
|
||||
import WindiCSS from "vite-plugin-windicss";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import VueI18n from "@intlify/vite-plugin-vue-i18n";
|
||||
import Prism from "markdown-it-prism";
|
||||
import LinkAttributes from 'markdown-it-link-attributes'
|
||||
import viteSSR from "vite-ssr/plugin";
|
||||
import EslintPlugin from 'vite-plugin-eslint';
|
||||
|
||||
const proxyHost = process.env.proxyHost || 'http://localhost:8080';
|
||||
const authHost = process.env.authHost || 'http://localhost:3001';
|
||||
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"~/": `${path.resolve(__dirname, "src")}/`,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
viteSSR(),
|
||||
|
||||
Vue({
|
||||
include: [/\.vue$/, /\.md$/],
|
||||
}),
|
||||
|
||||
// https://github.com/hannoeru/vite-plugin-pages
|
||||
Pages({
|
||||
extensions: ["vue", "md"],
|
||||
}),
|
||||
|
||||
// https://github.com/JohnCampionJr/vite-plugin-vue-layouts
|
||||
Layouts(),
|
||||
|
||||
// https://github.com/antfu/vite-plugin-md
|
||||
Markdown({
|
||||
wrapperClasses: "prose prose-sm m-auto text-left",
|
||||
headEnabled: true, // This relies on useHead
|
||||
markdownItSetup(md) {
|
||||
// https://prismjs.com/
|
||||
md.use(Prism);
|
||||
md.use(LinkAttributes, {
|
||||
pattern: /^https?:\/\//,
|
||||
attrs: {
|
||||
target: '_blank',
|
||||
rel: 'noopener',
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/unplugin-auto-import
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
'vue-i18n',
|
||||
'@vueuse/head',
|
||||
'@vueuse/core',
|
||||
'pinia',
|
||||
{
|
||||
'axios': [['default', 'axios'] ],
|
||||
},
|
||||
],
|
||||
dts: 'src/types/auto-imports.d.ts',
|
||||
eslintrc: {
|
||||
enabled: true,
|
||||
},
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/vite-plugin-components
|
||||
Components({
|
||||
// allow auto load markdown components under `./src/components/`
|
||||
extensions: ["vue", "md"],
|
||||
|
||||
// allow auto import and register components used in markdown
|
||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||
|
||||
// auto import icons
|
||||
resolvers: [
|
||||
// https://github.com/antfu/vite-plugin-icons
|
||||
IconsResolver({
|
||||
componentPrefix: "",
|
||||
// enabledCollections: ['carbon']
|
||||
}),
|
||||
],
|
||||
dts: 'src/types/components.d.ts',
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/vite-plugin-icons
|
||||
Icons({
|
||||
autoInstall: true
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/vite-plugin-windicss
|
||||
WindiCSS({
|
||||
safelist: "prose prose-sm m-auto",
|
||||
}),
|
||||
|
||||
// https://github.com/antfu/vite-plugin-pwa
|
||||
VitePWA({
|
||||
manifest: {
|
||||
name: "Hangar | PaperMC",
|
||||
short_name: "Hangar",
|
||||
description: "Plugin repository for Paper plugins and more!",
|
||||
theme_color: "#ffffff",
|
||||
icons: [
|
||||
{
|
||||
src: "/pwa-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/pwa-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "/pwa-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "any maskable",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
// https://github.com/intlify/vite-plugin-vue-i18n
|
||||
VueI18n({
|
||||
include: [path.resolve(__dirname, "src/i18n/translations/**")],
|
||||
}),
|
||||
|
||||
EslintPlugin({
|
||||
fix: true
|
||||
}),
|
||||
],
|
||||
|
||||
optimizeDeps: {
|
||||
include: ['vue', 'vue-router', '@vueuse/core', '@vueuse/head'],
|
||||
exclude: ['vue-demi'],
|
||||
},
|
||||
|
||||
server: {
|
||||
proxy: {
|
||||
// backend
|
||||
"/api/": proxyHost,
|
||||
"/signup": proxyHost,
|
||||
"/login": proxyHost,
|
||||
"/logout": proxyHost,
|
||||
"/handle-logout": proxyHost,
|
||||
"/refresh": proxyHost,
|
||||
"/invalidate": proxyHost,
|
||||
"/v2/api-docs/": proxyHost,
|
||||
"/robots.txt": proxyHost,
|
||||
"/sitemap.xml": proxyHost,
|
||||
"/global-sitemap.xml": proxyHost,
|
||||
"/*/sitemap.xml": proxyHost,
|
||||
"/statusz": proxyHost,
|
||||
// auth
|
||||
"/avatar": authHost,
|
||||
"/oauth/logout": authHost,
|
||||
"/oauth2": authHost,
|
||||
},
|
||||
}
|
||||
});
|
41
frontend-new/windi.config.ts
Normal file
41
frontend-new/windi.config.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { defineConfig } from 'vite-plugin-windicss'
|
||||
import colors from 'windicss/colors'
|
||||
import typography from 'windicss/plugin/typography'
|
||||
|
||||
export default defineConfig({
|
||||
darkMode: 'class',
|
||||
attributify: true,
|
||||
plugins: [
|
||||
typography(),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
maxWidth: '65ch',
|
||||
color: 'inherit',
|
||||
a: {
|
||||
'color': 'inherit',
|
||||
'opacity': 0.75,
|
||||
'fontWeight': '500',
|
||||
'textDecoration': 'underline',
|
||||
'&:hover': {
|
||||
opacity: 1,
|
||||
color: colors.teal[600],
|
||||
},
|
||||
},
|
||||
b: { color: 'inherit' },
|
||||
strong: { color: 'inherit' },
|
||||
em: { color: 'inherit' },
|
||||
h1: { color: 'inherit' },
|
||||
h2: { color: 'inherit' },
|
||||
h3: { color: 'inherit' },
|
||||
h4: { color: 'inherit' },
|
||||
code: { color: 'inherit' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
Loading…
Reference in New Issue
Block a user