summaryrefslogtreecommitdiff
path: root/packages/frontend/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-01-08 17:41:09 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2023-01-08 17:41:09 +0900
commit27c2ca50488680595c114dfae6f8de2ec3c48b32 (patch)
tree913bad82798e39c40e14d1bbc7fa20683bdf3622 /packages/frontend/src
parent:art: (diff)
downloadsharkey-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.vue70
-rw-r--r--packages/frontend/src/components/MkPlusOneEffect.vue69
-rw-r--r--packages/frontend/src/directives/click-anime.ts3
-rw-r--r--packages/frontend/src/pages/clicker.vue24
-rw-r--r--packages/frontend/src/router.ts4
-rw-r--r--packages/frontend/src/scripts/clicker-game.ts46
-rw-r--r--packages/frontend/src/ui/_common_/common.ts5
-rw-r--r--packages/frontend/src/widgets/clicker.vue44
-rw-r--r--packages/frontend/src/widgets/index.ts2
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',
];