diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-08 17:41:09 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2023-01-08 17:41:09 +0900 |
| commit | 27c2ca50488680595c114dfae6f8de2ec3c48b32 (patch) | |
| tree | 913bad82798e39c40e14d1bbc7fa20683bdf3622 /packages/frontend/src | |
| parent | :art: (diff) | |
| download | sharkey-27c2ca50488680595c114dfae6f8de2ec3c48b32.tar.gz sharkey-27c2ca50488680595c114dfae6f8de2ec3c48b32.tar.bz2 sharkey-27c2ca50488680595c114dfae6f8de2ec3c48b32.zip | |
feat(client): ๐ช๐
Diffstat (limited to 'packages/frontend/src')
| -rw-r--r-- | packages/frontend/src/components/MkClickerGame.vue | 70 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkPlusOneEffect.vue | 69 | ||||
| -rw-r--r-- | packages/frontend/src/directives/click-anime.ts | 3 | ||||
| -rw-r--r-- | packages/frontend/src/pages/clicker.vue | 24 | ||||
| -rw-r--r-- | packages/frontend/src/router.ts | 4 | ||||
| -rw-r--r-- | packages/frontend/src/scripts/clicker-game.ts | 46 | ||||
| -rw-r--r-- | packages/frontend/src/ui/_common_/common.ts | 5 | ||||
| -rw-r--r-- | packages/frontend/src/widgets/clicker.vue | 44 | ||||
| -rw-r--r-- | packages/frontend/src/widgets/index.ts | 2 |
9 files changed, 267 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue new file mode 100644 index 0000000000..6ae202cb63 --- /dev/null +++ b/packages/frontend/src/components/MkClickerGame.vue @@ -0,0 +1,70 @@ +<template> +<div> + <div v-if="game.ready" :class="$style.game"> + <div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div> + <button v-click-anime class="_button" :class="$style.button" @click="onClick"> + <img src="/client-assets/cookie.png" :class="$style.img"> + </button> + </div> + <div v-else> + <MkLoading/> + </div> +</div> +</template> + +<script lang="ts" setup> +import { computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue'; +import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue'; +import * as os from '@/os'; +import { useInterval } from '@/scripts/use-interval'; +import * as game from '@/scripts/clicker-game'; +import number from '@/filters/number'; + +defineProps<{ +}>(); + +const saveData = game.saveData; +const cookies = computed(() => saveData.value?.cookies); + +function onClick(ev: MouseEvent) { + saveData.value!.cookies++; + saveData.value!.clicked++; + + const x = ev.clientX; + const y = ev.clientY; + os.popup(MkPlusOneEffect, { x, y }, {}, 'end'); +} + +useInterval(game.save, 1000 * 5, { + immediate: false, + afterMounted: true, +}); + +onMounted(async () => { + await game.load(); +}); + +onUnmounted(() => { + game.save(); +}); +</script> + +<style lang="scss" module> +.game { + padding: 16px; + text-align: center; +} + +.count { + font-size: 1.3em; + margin-bottom: 6px; +} + +.button { + +} + +.img { + max-width: 90px; +} +</style> diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue new file mode 100644 index 0000000000..6a09669a68 --- /dev/null +++ b/packages/frontend/src/components/MkPlusOneEffect.vue @@ -0,0 +1,69 @@ +<template> +<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> + <span class="text" :class="{ up }">+1</span> +</div> +</template> + +<script lang="ts" setup> +import { onMounted } from 'vue'; +import * as os from '@/os'; + +const props = withDefaults(defineProps<{ + x: number; + y: number; +}>(), { +}); + +const emit = defineEmits<{ + (ev: 'end'): void; +}>(); + +let up = $ref(false); +const zIndex = os.claimZIndex('middle'); +const angle = (45 - (Math.random() * 90)) + 'deg'; + +onMounted(() => { + window.setTimeout(() => { + up = true; + }, 10); + + window.setTimeout(() => { + emit('end'); + }, 1100); +}); +</script> + +<style lang="scss" module> +.root { + pointer-events: none; + position: fixed; + width: 128px; + height: 128px; + + &:global { + > .text { + display: block; + height: 1em; + text-align: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + color: #fff; + text-shadow: 0 0 6px #000; + font-size: 18px; + font-weight: bold; + transform: translateY(0px); + transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5); + will-change: opacity, transform; + + &.up { + opacity: 0; + transform: translateY(-50px) rotateZ(v-bind(angle)); + } + } + } +} +</style> diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts index 83ec08543f..3d070177bd 100644 --- a/packages/frontend/src/directives/click-anime.ts +++ b/packages/frontend/src/directives/click-anime.ts @@ -12,6 +12,9 @@ export default { target.classList.add('_anime_bounce_standBy'); el.addEventListener('mousedown', () => { + target.classList.remove('_anime_bounce_ready'); + target.classList.remove('_anime_bounce'); + target.classList.add('_anime_bounce_standBy'); target.classList.add('_anime_bounce_ready'); diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue new file mode 100644 index 0000000000..082a303e6f --- /dev/null +++ b/packages/frontend/src/pages/clicker.vue @@ -0,0 +1,24 @@ +<template> +<MkStickyContainer> + <template #header><MkPageHeader/></template> + <MkSpacer :content-max="800"> + <MkClickerGame/> + </MkSpacer> +</MkStickyContainer> +</template> + +<script lang="ts" setup> +import { ref } from 'vue'; +import MkClickerGame from '@/components/MkClickerGame.vue'; +import { i18n } from '@/i18n'; +import { definePageMetadata } from '@/scripts/page-metadata'; + +definePageMetadata({ + title: '๐ช๐', + icon: 'ti ti-cookie', +}); +</script> + +<style lang="scss" module> + +</style> diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 63c753de22..bfa4a3ceab 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -461,6 +461,10 @@ export const routes = [{ component: page(() => import('./pages/antenna-timeline.vue')), loginRequired: true, }, { + path: '/clicker', + component: page(() => import('./pages/clicker.vue')), + loginRequired: true, +}, { name: 'index', path: '/', component: $i ? page(() => import('./pages/timeline.vue')) : page(() => import('./pages/welcome.vue')), diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts new file mode 100644 index 0000000000..77206cc8e2 --- /dev/null +++ b/packages/frontend/src/scripts/clicker-game.ts @@ -0,0 +1,46 @@ +import { ref, computed } from 'vue'; +import * as os from '@/os'; + +type SaveData = { + gameVersion: number; + cookies: number; + clicked: number; +}; + +export const saveData = ref<SaveData>(); +export const ready = computed(() => saveData.value != null); + +let prev = ''; + +export async function load() { + try { + saveData.value = await os.api('i/registry/get', { + scope: ['clickerGame'], + key: 'saveData', + }); + } catch (err) { + if (err.code === 'NO_SUCH_KEY') { + saveData.value = { + gameVersion: 1, + cookies: 0, + clicked: 0, + }; + save(); + return; + } + throw err; + } +} + +export async function save() { + const current = JSON.stringify(saveData.value); + if (current === prev) return; + + await os.api('i/registry/set', { + scope: ['clickerGame'], + key: 'saveData', + value: saveData.value, + }); + + prev = current; +} diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts index dfdf324bcf..c3b22cd9e1 100644 --- a/packages/frontend/src/ui/_common_/common.ts +++ b/packages/frontend/src/ui/_common_/common.ts @@ -41,6 +41,11 @@ export function openInstanceMenu(ev: MouseEvent) { to: '/api-console', text: 'API Console', icon: 'ti ti-terminal-2', + }, { + type: 'link', + to: '/clicker', + text: '๐ช๐', + icon: 'ti ti-cookie', }], }, null, { type: 'parent', diff --git a/packages/frontend/src/widgets/clicker.vue b/packages/frontend/src/widgets/clicker.vue new file mode 100644 index 0000000000..77d1777e97 --- /dev/null +++ b/packages/frontend/src/widgets/clicker.vue @@ -0,0 +1,44 @@ +<template> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-clicker"> + <template #header><i class="ti ti-cookie"></i>Clicker</template> + <MkClickerGame/> +</MkContainer> +</template> + +<script lang="ts" setup> +import { onMounted, onUnmounted, Ref, ref, watch } from 'vue'; +import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget'; +import { GetFormResultType } from '@/scripts/form'; +import { $i } from '@/account'; +import MkContainer from '@/components/MkContainer.vue'; +import MkClickerGame from '@/components/MkClickerGame.vue'; + +const name = 'clicker'; + +const widgetPropsDef = { + showHeader: { + type: 'boolean' as const, + default: true, + }, +}; + +type WidgetProps = GetFormResultType<typeof widgetPropsDef>; + +// ็พๆ็นใงใฏvueใฎๅถ้ใซใใimportใใtypeใใธใงใใชใใฏใซๆธกใใชใ +//const props = defineProps<WidgetComponentProps<WidgetProps>>(); +//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); +const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); + +const { widgetProps, configure } = useWidgetPropsManager(name, + widgetPropsDef, + props, + emit, +); + +defineExpose<WidgetComponentExpose>({ + name, + configure, + id: props.widget ? props.widget.id : null, +}); +</script> diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts index 3966649da4..eba4abd2f7 100644 --- a/packages/frontend/src/widgets/index.ts +++ b/packages/frontend/src/widgets/index.ts @@ -25,6 +25,7 @@ export default function(app: App) { app.component('MkwAiscriptApp', defineAsyncComponent(() => import('./aiscript-app.vue'))); app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue'))); app.component('MkwUserList', defineAsyncComponent(() => import('./user-list.vue'))); + app.component('MkwClicker', defineAsyncComponent(() => import('./clicker.vue'))); } export const widgets = [ @@ -52,4 +53,5 @@ export const widgets = [ 'aiscriptApp', 'aichan', 'userList', + 'clicker', ]; |