2021-10-16 10:29:32 +00:00
|
|
|
<template>
|
|
|
|
<main class="main">
|
|
|
|
<Container>
|
|
|
|
<div class="content-warpper">
|
|
|
|
<div class="content-view">
|
|
|
|
<Header />
|
|
|
|
|
|
|
|
<div class="playground">
|
|
|
|
<div class="avatar-wrapper">
|
|
|
|
<VueColorAvatar
|
|
|
|
ref="colorAvatarRef"
|
|
|
|
:option="avatarOption"
|
|
|
|
:size="280"
|
|
|
|
:style="{
|
|
|
|
transform: `rotateY(${flipped ? -180 : 0}deg)`,
|
|
|
|
}"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2021-10-31 02:12:27 +00:00
|
|
|
<ActionBar @action="handleAction" />
|
2021-10-16 10:29:32 +00:00
|
|
|
|
|
|
|
<div class="action-group">
|
2022-06-12 06:38:40 +00:00
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="action-btn action-randomize"
|
|
|
|
@click="handleGenerate"
|
|
|
|
>
|
2021-10-16 10:29:32 +00:00
|
|
|
{{ t('action.randomize') }}
|
|
|
|
</button>
|
2022-06-12 06:38:40 +00:00
|
|
|
|
2021-10-16 10:29:32 +00:00
|
|
|
<button
|
2022-06-12 06:38:40 +00:00
|
|
|
type="button"
|
|
|
|
class="action-btn action-download"
|
2021-10-16 10:29:32 +00:00
|
|
|
:disabled="downloading"
|
|
|
|
@click="handleDownload"
|
|
|
|
>
|
|
|
|
{{
|
|
|
|
downloading
|
|
|
|
? `${t('action.downloading')}...`
|
|
|
|
: t('action.download')
|
|
|
|
}}
|
|
|
|
</button>
|
2022-06-12 06:38:40 +00:00
|
|
|
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
class="action-btn action-multiple"
|
|
|
|
@click="() => generateMultiple()"
|
|
|
|
>
|
|
|
|
{{ t('action.downloadMultiple') }}
|
|
|
|
</button>
|
2021-10-16 10:29:32 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<Footer />
|
|
|
|
|
|
|
|
<CodeModal :visible="codeVisible" @close="codeVisible = false" />
|
|
|
|
|
|
|
|
<DownloadModal
|
|
|
|
:visible="downloadModalVisible"
|
|
|
|
:image-url="imageDataURL"
|
|
|
|
@close=";(downloadModalVisible = false), (imageDataURL = '')"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
|
2021-10-31 02:12:27 +00:00
|
|
|
<ConfettiCanvas />
|
2021-10-16 10:29:32 +00:00
|
|
|
|
|
|
|
<div class="gradient-bg">
|
|
|
|
<div class="gradient-top"></div>
|
|
|
|
<div class="gradient-bottom"></div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Container>
|
|
|
|
|
2022-06-12 06:38:40 +00:00
|
|
|
<BatchDownloadModal
|
|
|
|
:visible="avatarListVisible"
|
|
|
|
:avatar-list="avatarList"
|
2022-06-28 07:06:41 +00:00
|
|
|
@regenerate="generateMultiple"
|
2022-06-12 06:38:40 +00:00
|
|
|
@close=";(avatarListVisible = false), (avatarList = [])"
|
|
|
|
/>
|
|
|
|
|
2021-10-16 10:29:32 +00:00
|
|
|
<Sider>
|
|
|
|
<Configurator />
|
|
|
|
</Sider>
|
|
|
|
</main>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
2022-06-12 06:38:40 +00:00
|
|
|
import { ref, watchEffect } from 'vue'
|
2021-10-16 10:29:32 +00:00
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
|
|
|
import ActionBar from '@/components/ActionBar.vue'
|
|
|
|
import Configurator from '@/components/Configurator.vue'
|
2022-06-12 06:38:40 +00:00
|
|
|
import BatchDownloadModal from '@/components/Modal/BatchDownloadModal.vue'
|
|
|
|
import CodeModal from '@/components/Modal/CodeModal.vue'
|
|
|
|
import DownloadModal from '@/components/Modal/DownloadModal.vue'
|
2022-02-26 03:30:06 +00:00
|
|
|
import VueColorAvatar, {
|
|
|
|
type VueColorAvatarRef,
|
|
|
|
} from '@/components/VueColorAvatar.vue'
|
2021-10-16 10:29:32 +00:00
|
|
|
import { ActionType } from '@/enums'
|
|
|
|
import { useAvatarOption } from '@/hooks'
|
|
|
|
import Container from '@/layouts/Container.vue'
|
|
|
|
import Footer from '@/layouts/Footer.vue'
|
|
|
|
import Header from '@/layouts/Header.vue'
|
|
|
|
import Sider from '@/layouts/Sider.vue'
|
|
|
|
import { useStore } from '@/store'
|
|
|
|
import { REDO, UNDO } from '@/store/mutation-type'
|
|
|
|
import {
|
|
|
|
getRandomAvatarOption,
|
|
|
|
getSpecialAvatarOption,
|
|
|
|
showConfetti,
|
|
|
|
} from '@/utils'
|
|
|
|
import {
|
|
|
|
DOWNLOAD_DELAY,
|
|
|
|
NOT_COMPATIBLE_AGENTS,
|
|
|
|
TRIGGER_PROBABILITY,
|
|
|
|
} from '@/utils/constant'
|
2021-10-19 13:12:37 +00:00
|
|
|
import { recordEvent } from '@/utils/ga'
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2022-06-12 06:38:40 +00:00
|
|
|
import { name as appName } from '../package.json'
|
2021-10-31 02:12:27 +00:00
|
|
|
import ConfettiCanvas from './components/ConfettiCanvas.vue'
|
2022-06-12 06:38:40 +00:00
|
|
|
import type { AvatarOption } from './types'
|
2021-10-16 10:29:32 +00:00
|
|
|
|
|
|
|
const store = useStore()
|
|
|
|
|
|
|
|
const [avatarOption, setAvatarOption] = useAvatarOption()
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
const colorAvatarRef = ref<VueColorAvatarRef>()
|
|
|
|
|
|
|
|
function handleGenerate() {
|
|
|
|
if (Math.random() <= TRIGGER_PROBABILITY) {
|
|
|
|
let colorfulOption = getSpecialAvatarOption()
|
|
|
|
while (
|
|
|
|
JSON.stringify(colorfulOption) === JSON.stringify(avatarOption.value)
|
|
|
|
) {
|
|
|
|
colorfulOption = getSpecialAvatarOption()
|
|
|
|
}
|
2021-10-19 15:45:30 +00:00
|
|
|
colorfulOption.wrapperShape = avatarOption.value.wrapperShape
|
2021-10-16 10:29:32 +00:00
|
|
|
setAvatarOption(colorfulOption)
|
|
|
|
showConfetti()
|
|
|
|
} else {
|
|
|
|
const randomOption = getRandomAvatarOption(avatarOption.value)
|
|
|
|
setAvatarOption(randomOption)
|
|
|
|
}
|
2021-10-19 13:12:37 +00:00
|
|
|
|
|
|
|
recordEvent('click_randomize', {
|
|
|
|
event_category: 'click',
|
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const downloadModalVisible = ref(false)
|
|
|
|
const downloading = ref(false)
|
|
|
|
const imageDataURL = ref('')
|
|
|
|
|
|
|
|
async function handleDownload() {
|
|
|
|
try {
|
|
|
|
downloading.value = true
|
|
|
|
const avatarEle = colorAvatarRef.value?.avatarRef
|
|
|
|
|
|
|
|
const userAgent = window.navigator.userAgent.toLowerCase()
|
|
|
|
const notCompatible = NOT_COMPATIBLE_AGENTS.some(
|
|
|
|
(agent) => userAgent.indexOf(agent) !== -1
|
|
|
|
)
|
|
|
|
|
|
|
|
if (avatarEle) {
|
|
|
|
const html2canvas = (await import('html2canvas')).default
|
|
|
|
const canvas = await html2canvas(avatarEle, {
|
|
|
|
backgroundColor: null,
|
|
|
|
})
|
|
|
|
const dataURL = canvas.toDataURL()
|
|
|
|
|
|
|
|
if (notCompatible) {
|
|
|
|
imageDataURL.value = dataURL
|
|
|
|
downloadModalVisible.value = true
|
|
|
|
} else {
|
|
|
|
const trigger = document.createElement('a')
|
|
|
|
trigger.href = dataURL
|
2022-06-12 06:38:40 +00:00
|
|
|
trigger.download = `${appName}.png`
|
2021-10-16 10:29:32 +00:00
|
|
|
trigger.click()
|
|
|
|
}
|
|
|
|
}
|
2021-10-19 13:12:37 +00:00
|
|
|
|
|
|
|
recordEvent('click_download', {
|
|
|
|
event_category: 'click',
|
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
} finally {
|
|
|
|
setTimeout(() => {
|
|
|
|
downloading.value = false
|
|
|
|
}, DOWNLOAD_DELAY)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const flipped = ref(false)
|
|
|
|
const codeVisible = ref(false)
|
|
|
|
|
|
|
|
function handleAction(actionType: ActionType) {
|
|
|
|
switch (actionType) {
|
|
|
|
case ActionType.Undo:
|
2023-02-15 11:23:33 +00:00
|
|
|
store[UNDO]()
|
2021-10-19 13:12:37 +00:00
|
|
|
recordEvent('action_undo', {
|
|
|
|
event_category: 'action',
|
|
|
|
event_label: 'Undo',
|
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
case ActionType.Redo:
|
2023-02-15 11:23:33 +00:00
|
|
|
store[REDO]()
|
2021-10-19 13:12:37 +00:00
|
|
|
recordEvent('action_redo', {
|
|
|
|
event_category: 'action',
|
|
|
|
event_label: 'Redo',
|
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
case ActionType.Flip:
|
|
|
|
flipped.value = !flipped.value
|
2021-10-19 13:12:37 +00:00
|
|
|
recordEvent('action_flip_avatar', {
|
|
|
|
event_category: 'action',
|
|
|
|
event_label: 'Flip Avatar',
|
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
break
|
|
|
|
|
|
|
|
case ActionType.Code:
|
|
|
|
codeVisible.value = !codeVisible.value
|
2021-10-19 13:12:37 +00:00
|
|
|
recordEvent('action_view_code', {
|
|
|
|
event_category: 'action',
|
|
|
|
event_label: 'View Avatar Option Code',
|
2021-10-17 09:32:01 +00:00
|
|
|
})
|
2021-10-16 10:29:32 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-06-12 06:38:40 +00:00
|
|
|
|
|
|
|
const avatarListVisible = ref(false)
|
|
|
|
const avatarList = ref<AvatarOption[]>([])
|
|
|
|
|
|
|
|
watchEffect(() => {
|
|
|
|
avatarListVisible.value =
|
|
|
|
Array.isArray(avatarList.value) && avatarList.value.length > 0
|
|
|
|
})
|
|
|
|
|
|
|
|
async function generateMultiple(count = 5 * 6) {
|
|
|
|
const { default: hash } = await import('object-hash')
|
|
|
|
|
|
|
|
const avatarMap = [...Array(count)].reduce<Map<string, AvatarOption>>(
|
|
|
|
(res) => {
|
|
|
|
let randomAvatarOption: AvatarOption
|
|
|
|
let hashKey: string
|
|
|
|
|
|
|
|
do {
|
|
|
|
randomAvatarOption = getRandomAvatarOption(avatarOption.value)
|
|
|
|
hashKey = hash.sha1(randomAvatarOption)
|
|
|
|
} while (
|
|
|
|
randomAvatarOption.background.color === 'transparent' ||
|
|
|
|
res.has(hashKey)
|
|
|
|
)
|
|
|
|
|
|
|
|
res.set(hashKey, randomAvatarOption)
|
|
|
|
|
|
|
|
return res
|
|
|
|
},
|
|
|
|
new Map()
|
|
|
|
)
|
|
|
|
|
|
|
|
avatarList.value = Array.from(avatarMap.values())
|
|
|
|
|
|
|
|
recordEvent('click_generate_multiple', {
|
|
|
|
event_category: 'click',
|
|
|
|
})
|
|
|
|
}
|
2021-10-16 10:29:32 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
2021-10-31 02:41:44 +00:00
|
|
|
@use 'src/styles/var';
|
|
|
|
|
2021-10-16 10:29:32 +00:00
|
|
|
.main {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
overflow: hidden;
|
2021-10-31 02:41:44 +00:00
|
|
|
color: var.$color-text;
|
|
|
|
background-color: var.$color-page-bg;
|
2021-10-16 10:29:32 +00:00
|
|
|
|
|
|
|
.content-warpper {
|
|
|
|
height: 100%;
|
|
|
|
transform: scale(1);
|
|
|
|
|
|
|
|
.content-view {
|
|
|
|
position: relative;
|
|
|
|
z-index: 110;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
height: 100%;
|
|
|
|
overflow-y: auto;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.playground {
|
|
|
|
display: flex;
|
|
|
|
flex: 1;
|
|
|
|
flex-direction: column;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
padding: 2rem 0;
|
|
|
|
|
|
|
|
.avatar-wrapper {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
|
2021-10-31 02:41:44 +00:00
|
|
|
@media screen and (max-width: var.$screen-sm) {
|
2021-10-16 10:29:32 +00:00
|
|
|
transform: scale(0.85);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-group {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
margin-top: 4rem;
|
2022-06-12 06:38:40 +00:00
|
|
|
column-gap: 1rem;
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2022-06-12 06:38:40 +00:00
|
|
|
@supports not (column-gap: 1rem) {
|
|
|
|
.action-btn {
|
|
|
|
margin: 0 0.5rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.action-btn {
|
2021-10-16 10:29:32 +00:00
|
|
|
min-width: 6rem;
|
|
|
|
height: 2.5rem;
|
|
|
|
padding: 0 1rem;
|
2021-10-31 02:41:44 +00:00
|
|
|
color: var.$color-text;
|
2021-10-16 10:29:32 +00:00
|
|
|
font-weight: bold;
|
2021-10-31 02:41:44 +00:00
|
|
|
background: var.$color-gray;
|
2021-10-16 10:29:32 +00:00
|
|
|
border-radius: 0.6rem;
|
|
|
|
cursor: pointer;
|
|
|
|
transition: color 0.2s;
|
|
|
|
user-select: none;
|
|
|
|
|
|
|
|
&:hover {
|
2021-10-31 02:41:44 +00:00
|
|
|
color: lighten(var.$color-text, 10);
|
2021-10-16 10:29:32 +00:00
|
|
|
}
|
|
|
|
|
2021-10-17 03:13:46 +00:00
|
|
|
&:disabled,
|
|
|
|
&[disabled] {
|
2021-10-31 02:41:44 +00:00
|
|
|
color: rgba(var.$color-text, 0.5);
|
2021-10-16 10:29:32 +00:00
|
|
|
cursor: default;
|
|
|
|
}
|
|
|
|
}
|
2022-06-12 06:38:40 +00:00
|
|
|
|
|
|
|
@media screen and (max-width: var.$screen-sm) {
|
|
|
|
.action-multiple {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
}
|
2021-10-16 10:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-19 13:59:01 +00:00
|
|
|
@supports (filter: blur(4rem)) or (-webkit-filter: blur(4rem)) or
|
|
|
|
(-moz-filter: blur(4rem)) {
|
|
|
|
.gradient-bg {
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
@mixin gradient-style($color) {
|
|
|
|
position: absolute;
|
|
|
|
width: 100vh;
|
|
|
|
height: 100vh;
|
|
|
|
background-image: radial-gradient(
|
|
|
|
rgba($color, 0.8) 20%,
|
|
|
|
rgba($color, 0.6) 40%,
|
|
|
|
rgba($color, 0.4) 60%,
|
|
|
|
rgba($color, 0.2) 80%,
|
|
|
|
transparent 100%
|
|
|
|
);
|
|
|
|
border-radius: 50%;
|
|
|
|
opacity: 0.2;
|
|
|
|
filter: blur(4rem);
|
|
|
|
}
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2021-10-19 13:59:01 +00:00
|
|
|
.gradient-top {
|
2021-10-31 02:41:44 +00:00
|
|
|
@include gradient-style(var.$color-secondary);
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2021-10-19 13:59:01 +00:00
|
|
|
top: -50%;
|
|
|
|
right: -20%;
|
|
|
|
}
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2021-10-19 13:59:01 +00:00
|
|
|
.gradient-bottom {
|
2021-10-31 02:41:44 +00:00
|
|
|
@include gradient-style(var.$color-accent);
|
2021-10-16 10:29:32 +00:00
|
|
|
|
2021-10-19 13:59:01 +00:00
|
|
|
bottom: -50%;
|
|
|
|
left: -20%;
|
|
|
|
}
|
2021-10-16 10:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|