Merge branch 'oneko' into 'develop'
feat: oneko See merge request TransFem-org/Sharkey!387
This commit is contained in:
		
						commit
						ea44895b6b
					
				
					 9 changed files with 257 additions and 0 deletions
				
			
		| 
						 | 
					@ -1095,6 +1095,7 @@ accountMoved: "This user has moved to a new account:"
 | 
				
			||||||
accountMovedShort: "This account has been migrated."
 | 
					accountMovedShort: "This account has been migrated."
 | 
				
			||||||
operationForbidden: "Operation forbidden"
 | 
					operationForbidden: "Operation forbidden"
 | 
				
			||||||
forceShowAds: "Always show ads"
 | 
					forceShowAds: "Always show ads"
 | 
				
			||||||
 | 
					oneko: "Cat friend :3"
 | 
				
			||||||
addMemo: "Add memo"
 | 
					addMemo: "Add memo"
 | 
				
			||||||
editMemo: "Edit memo"
 | 
					editMemo: "Edit memo"
 | 
				
			||||||
reactionsList: "Reactions"
 | 
					reactionsList: "Reactions"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -4425,6 +4425,10 @@ export interface Locale extends ILocale {
 | 
				
			||||||
     * 常に広告を表示する
 | 
					     * 常に広告を表示する
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    "forceShowAds": string;
 | 
					    "forceShowAds": string;
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 猫友達 :3
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    "oneko": string;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * メモを追加
 | 
					     * メモを追加
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1102,6 +1102,7 @@ accountMoved: "このユーザーは新しいアカウントに移行しまし
 | 
				
			||||||
accountMovedShort: "このアカウントは移行されています"
 | 
					accountMovedShort: "このアカウントは移行されています"
 | 
				
			||||||
operationForbidden: "この操作はできません"
 | 
					operationForbidden: "この操作はできません"
 | 
				
			||||||
forceShowAds: "常に広告を表示する"
 | 
					forceShowAds: "常に広告を表示する"
 | 
				
			||||||
 | 
					oneko: "猫友達 :3"
 | 
				
			||||||
addMemo: "メモを追加"
 | 
					addMemo: "メモを追加"
 | 
				
			||||||
editMemo: "メモを編集"
 | 
					editMemo: "メモを編集"
 | 
				
			||||||
reactionsList: "リアクション一覧"
 | 
					reactionsList: "リアクション一覧"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								packages/frontend/assets/oneko.gif
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								packages/frontend/assets/oneko.gif
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.2 KiB  | 
							
								
								
									
										240
									
								
								packages/frontend/src/components/SkOneko.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								packages/frontend/src/components/SkOneko.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,240 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div ref="nekoEl" :class="$style.oneko" aria-hidden="true"></div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts" setup>
 | 
				
			||||||
 | 
					// oneko.js: https://github.com/adryd325/oneko.js
 | 
				
			||||||
 | 
					// modified to be a vue component by ShittyKopper :3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { shallowRef, onMounted } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const nekoEl = shallowRef<HTMLDivElement>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let nekoPosX = 32;
 | 
				
			||||||
 | 
					let nekoPosY = 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let mousePosX = 0;
 | 
				
			||||||
 | 
					let mousePosY = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let frameCount = 0;
 | 
				
			||||||
 | 
					let idleTime = 0;
 | 
				
			||||||
 | 
					let idleAnimation: string|null = null;
 | 
				
			||||||
 | 
					let idleAnimationFrame = 0;
 | 
				
			||||||
 | 
					let lastFrameTimestamp;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const nekoSpeed = 10;
 | 
				
			||||||
 | 
					const spriteSets = {
 | 
				
			||||||
 | 
						idle: [[-3, -3]],
 | 
				
			||||||
 | 
						alert: [[-7, -3]],
 | 
				
			||||||
 | 
						scratchSelf: [
 | 
				
			||||||
 | 
							[-5, 0],
 | 
				
			||||||
 | 
							[-6, 0],
 | 
				
			||||||
 | 
							[-7, 0],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						scratchWallN: [
 | 
				
			||||||
 | 
							[0, 0],
 | 
				
			||||||
 | 
							[0, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						scratchWallS: [
 | 
				
			||||||
 | 
							[-7, -1],
 | 
				
			||||||
 | 
							[-6, -2],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						scratchWallE: [
 | 
				
			||||||
 | 
							[-2, -2],
 | 
				
			||||||
 | 
							[-2, -3],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						scratchWallW: [
 | 
				
			||||||
 | 
							[-4, 0],
 | 
				
			||||||
 | 
							[-4, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						tired: [[-3, -2]],
 | 
				
			||||||
 | 
						sleeping: [
 | 
				
			||||||
 | 
							[-2, 0],
 | 
				
			||||||
 | 
							[-2, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						N: [
 | 
				
			||||||
 | 
							[-1, -2],
 | 
				
			||||||
 | 
							[-1, -3],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						NE: [
 | 
				
			||||||
 | 
							[0, -2],
 | 
				
			||||||
 | 
							[0, -3],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						E: [
 | 
				
			||||||
 | 
							[-3, 0],
 | 
				
			||||||
 | 
							[-3, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						SE: [
 | 
				
			||||||
 | 
							[-5, -1],
 | 
				
			||||||
 | 
							[-5, -2],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						S: [
 | 
				
			||||||
 | 
							[-6, -3],
 | 
				
			||||||
 | 
							[-7, -2],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						SW: [
 | 
				
			||||||
 | 
							[-5, -3],
 | 
				
			||||||
 | 
							[-6, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						W: [
 | 
				
			||||||
 | 
							[-4, -2],
 | 
				
			||||||
 | 
							[-4, -3],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
						NW: [
 | 
				
			||||||
 | 
							[-1, 0],
 | 
				
			||||||
 | 
							[-1, -1],
 | 
				
			||||||
 | 
						],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function init() {
 | 
				
			||||||
 | 
						if (!nekoEl.value) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nekoEl.value.style.left = `${nekoPosX - 16}px`;
 | 
				
			||||||
 | 
						nekoEl.value.style.top = `${nekoPosY - 16}px`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						document.addEventListener('mousemove', (event) => {
 | 
				
			||||||
 | 
							mousePosX = event.clientX;
 | 
				
			||||||
 | 
							mousePosY = event.clientY;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						window.requestAnimationFrame(onAnimationFrame);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function onAnimationFrame(timestamp) {
 | 
				
			||||||
 | 
						// Stops execution if the neko element is removed from DOM
 | 
				
			||||||
 | 
						if (!nekoEl.value?.isConnected) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (!lastFrameTimestamp) {
 | 
				
			||||||
 | 
							lastFrameTimestamp = timestamp;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (timestamp - lastFrameTimestamp > 100) {
 | 
				
			||||||
 | 
							lastFrameTimestamp = timestamp;
 | 
				
			||||||
 | 
							frame();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						window.requestAnimationFrame(onAnimationFrame);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// eslint-disable-next-line no-shadow
 | 
				
			||||||
 | 
					function setSprite(name, frame) {
 | 
				
			||||||
 | 
						if (!nekoEl.value) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const sprite = spriteSets[name][frame % spriteSets[name].length];
 | 
				
			||||||
 | 
						nekoEl.value.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function resetIdleAnimation() {
 | 
				
			||||||
 | 
						idleAnimation = null;
 | 
				
			||||||
 | 
						idleAnimationFrame = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function idle() {
 | 
				
			||||||
 | 
						idleTime += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// every ~ 20 seconds
 | 
				
			||||||
 | 
						if (
 | 
				
			||||||
 | 
							idleTime > 10 &&
 | 
				
			||||||
 | 
					      Math.floor(Math.random() * 200) === 0 &&
 | 
				
			||||||
 | 
					      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 | 
				
			||||||
 | 
					      idleAnimation == null
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							let avalibleIdleAnimations = ['sleeping', 'scratchSelf'];
 | 
				
			||||||
 | 
							if (nekoPosX < 32) {
 | 
				
			||||||
 | 
								avalibleIdleAnimations.push('scratchWallW');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (nekoPosY < 32) {
 | 
				
			||||||
 | 
								avalibleIdleAnimations.push('scratchWallN');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (nekoPosX > window.innerWidth - 32) {
 | 
				
			||||||
 | 
								avalibleIdleAnimations.push('scratchWallE');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (nekoPosY > window.innerHeight - 32) {
 | 
				
			||||||
 | 
								avalibleIdleAnimations.push('scratchWallS');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							idleAnimation =
 | 
				
			||||||
 | 
					        avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (idleAnimation) {
 | 
				
			||||||
 | 
							case 'sleeping':
 | 
				
			||||||
 | 
								if (idleAnimationFrame < 8) {
 | 
				
			||||||
 | 
									setSprite('tired', 0);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								setSprite('sleeping', Math.floor(idleAnimationFrame / 4));
 | 
				
			||||||
 | 
								if (idleAnimationFrame > 192) {
 | 
				
			||||||
 | 
									resetIdleAnimation();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case 'scratchWallN':
 | 
				
			||||||
 | 
							case 'scratchWallS':
 | 
				
			||||||
 | 
							case 'scratchWallE':
 | 
				
			||||||
 | 
							case 'scratchWallW':
 | 
				
			||||||
 | 
							case 'scratchSelf':
 | 
				
			||||||
 | 
								setSprite(idleAnimation, idleAnimationFrame);
 | 
				
			||||||
 | 
								if (idleAnimationFrame > 9) {
 | 
				
			||||||
 | 
									resetIdleAnimation();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								setSprite('idle', 0);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						idleAnimationFrame += 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function frame() {
 | 
				
			||||||
 | 
						if (!nekoEl.value) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						frameCount += 1;
 | 
				
			||||||
 | 
						const diffX = nekoPosX - mousePosX;
 | 
				
			||||||
 | 
						const diffY = nekoPosY - mousePosY;
 | 
				
			||||||
 | 
						const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (distance < nekoSpeed || distance < 48) {
 | 
				
			||||||
 | 
							idle();
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						idleAnimation = null;
 | 
				
			||||||
 | 
						idleAnimationFrame = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (idleTime > 1) {
 | 
				
			||||||
 | 
							setSprite('alert', 0);
 | 
				
			||||||
 | 
							// count down after being alerted before moving
 | 
				
			||||||
 | 
							idleTime = Math.min(idleTime, 7);
 | 
				
			||||||
 | 
							idleTime -= 1;
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let direction;
 | 
				
			||||||
 | 
						direction = diffY / distance > 0.5 ? 'N' : '';
 | 
				
			||||||
 | 
						direction += diffY / distance < -0.5 ? 'S' : '';
 | 
				
			||||||
 | 
						direction += diffX / distance > 0.5 ? 'W' : '';
 | 
				
			||||||
 | 
						direction += diffX / distance < -0.5 ? 'E' : '';
 | 
				
			||||||
 | 
						setSprite(direction, frameCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nekoPosX -= (diffX / distance) * nekoSpeed;
 | 
				
			||||||
 | 
						nekoPosY -= (diffY / distance) * nekoSpeed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
 | 
				
			||||||
 | 
						nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nekoEl.value.style.left = `${nekoPosX - 16}px`;
 | 
				
			||||||
 | 
						nekoEl.value.style.top = `${nekoPosY - 16}px`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					onMounted(init);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style module>
 | 
				
			||||||
 | 
					.oneko {
 | 
				
			||||||
 | 
						width: 32px;
 | 
				
			||||||
 | 
						height: 32px;
 | 
				
			||||||
 | 
						position: fixed;
 | 
				
			||||||
 | 
						pointer-events: none;
 | 
				
			||||||
 | 
						image-rendering: pixelated;
 | 
				
			||||||
 | 
						z-index: 2147483647;
 | 
				
			||||||
 | 
						background-image: url(/client-assets/oneko.gif);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -145,6 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
				<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
 | 
									<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
 | 
									<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
 | 
									<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
 | 
				
			||||||
 | 
									<MkSwitch v-model="oneko">{{ i18n.ts.oneko }}</MkSwitch>
 | 
				
			||||||
				<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 | 
									<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
| 
						 | 
					@ -332,6 +333,7 @@ const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
 | 
				
			||||||
const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
 | 
					const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
 | 
				
			||||||
const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
 | 
					const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
 | 
				
			||||||
const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
 | 
					const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
 | 
				
			||||||
 | 
					const oneko = computed(defaultStore.makeGetterSetter('oneko'));
 | 
				
			||||||
const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
 | 
					const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
 | 
				
			||||||
const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
 | 
					const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
 | 
				
			||||||
const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
 | 
					const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,6 +98,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 | 
				
			||||||
	'showClipButtonInNoteFooter',
 | 
						'showClipButtonInNoteFooter',
 | 
				
			||||||
	'reactionsDisplaySize',
 | 
						'reactionsDisplaySize',
 | 
				
			||||||
	'forceShowAds',
 | 
						'forceShowAds',
 | 
				
			||||||
 | 
						'oneko',
 | 
				
			||||||
	'numberOfReplies',
 | 
						'numberOfReplies',
 | 
				
			||||||
	'aiChanMode',
 | 
						'aiChanMode',
 | 
				
			||||||
	'devMode',
 | 
						'devMode',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -407,6 +407,10 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: false,
 | 
							default: false,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						oneko: {
 | 
				
			||||||
 | 
							where: 'device',
 | 
				
			||||||
 | 
							default: false,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	clickToOpen: {
 | 
						clickToOpen: {
 | 
				
			||||||
		where: 'device',
 | 
							where: 'device',
 | 
				
			||||||
		default: true,
 | 
							default: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,6 +42,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
<div v-if="dev" id="devTicker"><span>DEV BUILD</span></div>
 | 
					<div v-if="dev" id="devTicker"><span>DEV BUILD</span></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div>
 | 
					<div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<SkOneko v-if="defaultStore.state.oneko"/>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
| 
						 | 
					@ -59,6 +61,8 @@ import { i18n } from '@/i18n.js';
 | 
				
			||||||
import { defaultStore } from '@/store.js';
 | 
					import { defaultStore } from '@/store.js';
 | 
				
			||||||
import { globalEvents } from '@/events.js';
 | 
					import { globalEvents } from '@/events.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
 | 
					const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
 | 
				
			||||||
const XUpload = defineAsyncComponent(() => import('./upload.vue'));
 | 
					const XUpload = defineAsyncComponent(() => import('./upload.vue'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue