From 0a320e1eb0748f8d7b3bf028d36698b355915b3e Mon Sep 17 00:00:00 2001 From: Haoyu Xu Date: Mon, 27 Feb 2023 16:34:36 -0500 Subject: [PATCH] feat(directory): add voice --- Version | 2 +- aklive2d.js | 11 +- config.yaml | 3 + directory/.env | 4 +- directory/Version | 1 + directory/src/App.css | 3 +- directory/src/component/dropdown.css | 15 +- directory/src/component/dropdown.jsx | 9 +- directory/src/component/switch.css | 62 +++++++ directory/src/component/switch.jsx | 23 +++ directory/src/context/useHeaderContext.jsx | 2 +- directory/src/i18n.json | 112 +++++++------ directory/src/routes/path/home.css | 19 ++- directory/src/routes/path/home.jsx | 179 ++++++++++++++++----- directory/src/routes/root.css | 8 +- directory/src/routes/root.jsx | 12 +- package.json | 1 + pnpm-lock.yaml | 19 +++ vite.config.js | 37 ++++- 19 files changed, 399 insertions(+), 123 deletions(-) create mode 100644 directory/Version create mode 100644 directory/src/component/switch.css create mode 100644 directory/src/component/switch.jsx diff --git a/Version b/Version index 86fb650..f9f89f5 100644 --- a/Version +++ b/Version @@ -1 +1 @@ -3.3.7 +3.3.10 \ No newline at end of file diff --git a/aklive2d.js b/aklive2d.js index 5332b5a..3539aa3 100644 --- a/aklive2d.js +++ b/aklive2d.js @@ -5,20 +5,18 @@ import { fork } from 'child_process'; import getConfig from './libs/config.js' import ProjectJson from './libs/project_json.js' import EnvGenerator from './libs/env_generator.js' -import { write, rmdir, copy, writeSync, copyDir, readSync } from './libs/file.js' +import { write, rmdir, copy, writeSync, copyDir } from './libs/file.js' import AssetsProcessor from './libs/assets_processor.js' import init from './libs/initializer.js' import directory from './libs/directory.js' import { appendReadme } from './libs/append.js' +import { increase } from './libs/version.js'; import Background from './libs/background.js' import CharwordTable from './libs/charword_table.js'; async function main() { global.__projetRoot = path.dirname(fileURLToPath(import.meta.url)) - global.__config = { - ...getConfig(), - version: readSync(path.join(__projetRoot, 'Version')) - } + global.__config = getConfig() const op = process.argv[2] let OPERATOR_NAMES = process.argv.slice(3); @@ -39,6 +37,7 @@ async function main() { for (const [key, _] of Object.entries(__config.operators)) { OPERATOR_NAMES.push(key) } + increase(__projetRoot) break case 'preview': assert(OPERATOR_NAMES.length !== 0, 'Please set the operator name.') @@ -176,7 +175,7 @@ async function main() { value: __config.operators[OPERATOR_NAME].link }, { key: "version", - value: __config.version + value: __config.version.showcase }, { key: "title", value: __config.operators[OPERATOR_NAME].title diff --git a/config.yaml b/config.yaml index fae7b77..7952649 100644 --- a/config.yaml +++ b/config.yaml @@ -21,6 +21,9 @@ share: title: zh-CN: "明日方舟:" en-US: "Arknights: " +directory: + title: AKLive2D + voice: jp/CN_037.ogg operators: chen: !include config/chen.yaml dusk: !include config/dusk.yaml diff --git a/directory/.env b/directory/.env index 6a0ab7e..8229cde 100644 --- a/directory/.env +++ b/directory/.env @@ -1 +1,3 @@ -VITE_APP_TITLE="AKLive2D" \ No newline at end of file +VITE_APP_TITLE=AKLive2D +VITE_VERSION=0.5.5 +VITE_APP_VOICE_URL=jp/CN_037.ogg \ No newline at end of file diff --git a/directory/Version b/directory/Version new file mode 100644 index 0000000..ad83b1b --- /dev/null +++ b/directory/Version @@ -0,0 +1 @@ +0.5.6 \ No newline at end of file diff --git a/directory/src/App.css b/directory/src/App.css index cf34d27..57c1de6 100644 --- a/directory/src/App.css +++ b/directory/src/App.css @@ -4,6 +4,7 @@ :root { --text-color: rgba(255, 255, 255, 0.87); + --text-color-full: #fff; --secondary-text-color: #686a72; --date-color: rgba(255, 255, 255, 0.2); --border-color: #707070; @@ -17,7 +18,7 @@ font-family: "Geometos", "Noto Sans SC", sans-serif; font-size: 16px; - line-height: 1.1em; + line-height: 1.2em; font-weight: 400; color: var(--text-color); diff --git a/directory/src/component/dropdown.css b/directory/src/component/dropdown.css index 226da17..4f25334 100644 --- a/directory/src/component/dropdown.css +++ b/directory/src/component/dropdown.css @@ -4,13 +4,13 @@ user-select: none; z-index: 2; padding: 0.5rem; + cursor: pointer; } .dropdown .text { display: flex; flex-direction: row; align-items: center; - cursor: pointer; } .dropdown .content { @@ -18,6 +18,9 @@ } .dropdown .icon { + position: absolute; + bottom: 0.5rem; + right: -0.1rem; width: 0.5em; height: 0.5em; display: inline-block; @@ -27,10 +30,10 @@ border-right: 0.15em solid var(--text-color); border-top: 0.15em solid var(--text-color); transform: translate(0, -0.15em) rotate(-45deg); - transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; } -.dropdown .icon.active { +.dropdown.active .icon, +.dropdown:hover .icon { animation: icon-flash 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite; } @@ -55,7 +58,7 @@ visibility: hidden; } -.dropdown .menu.active { +.dropdown.active .menu { visibility: visible; opacity: 1; z-index: 2; @@ -76,7 +79,9 @@ transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; } -.dropdown .menu .item:hover, .dropdown .menu .item:focus, .dropdown .menu .item.active { +.dropdown .menu .item:hover, +.dropdown .menu .item:focus, +.dropdown .menu .item.active { color: var(--link-highlight-color); } diff --git a/directory/src/component/dropdown.jsx b/directory/src/component/dropdown.jsx index 5a0600b..88774bb 100644 --- a/directory/src/component/dropdown.jsx +++ b/directory/src/component/dropdown.jsx @@ -1,6 +1,5 @@ import { - useState, - useEffect + useState } from 'react' import './dropdown.css' @@ -13,15 +12,15 @@ export default function Dropdown(props) { return ( <> -
+
toggleDropdown()} > {props.text} - +
-
    +
      { props.menu.map((item) => { return ( diff --git a/directory/src/component/switch.css b/directory/src/component/switch.css new file mode 100644 index 0000000..1667939 --- /dev/null +++ b/directory/src/component/switch.css @@ -0,0 +1,62 @@ +.switch { + position: relative; + user-select: none; + z-index: 2; + padding: 8px 36px 8px 8px; + cursor: pointer; + display: flex; + flex-direction: row; + align-items: center; + color: var(--secondary-text-color); + transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; +} + +.switch.active { + color: var(--text-color); +} + +.switch .content { + padding-right: 8px; +} + +.switch .icon-wrapper { + color: var(--secondary-text-color); + transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; +} + +.switch.active .icon-wrapper { + color: var(--text-color-full); +} + +.switch .icon { + position: absolute; + bottom: 8px; + right: 18px; + width: 8px; + height: 8px; + display: inline-block; + vertical-align: middle; + border-left: 2px solid currentColor; + border-bottom: 2px solid currentColor; + border-right: 2px solid currentColor; + border-top: 2px solid currentColor; + transform: translate(0, -2px) rotate(-45deg); + transition: right cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s, background-color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; +} + +.switch.active .icon { + background-color: currentColor; + right: 0; +} + +.switch .icon-line { + position: absolute; + bottom: 15px; + right: 6px; + width: 18px; + height: 2px; + display: inline-block; + vertical-align: middle; + background-color: currentColor; + z-index: -1; +} \ No newline at end of file diff --git a/directory/src/component/switch.jsx b/directory/src/component/switch.jsx new file mode 100644 index 0000000..46395a1 --- /dev/null +++ b/directory/src/component/switch.jsx @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react'; +import './switch.css'; + +export default function Switch(props) { + const [on, setOn] = useState(props.on) + + useEffect(() => { + setOn(props.on) + }, [props.on]) + + return ( +
      props.handleOnClick()} + > + {props.text} +
      + + +
      +
      + ) +} \ No newline at end of file diff --git a/directory/src/context/useHeaderContext.jsx b/directory/src/context/useHeaderContext.jsx index 3d21587..28a97a9 100644 --- a/directory/src/context/useHeaderContext.jsx +++ b/directory/src/context/useHeaderContext.jsx @@ -31,7 +31,7 @@ export function HeaderProvider(props) { {props.children} diff --git a/directory/src/i18n.json b/directory/src/i18n.json index a6ed7ce..be4f900 100644 --- a/directory/src/i18n.json +++ b/directory/src/i18n.json @@ -3,57 +3,65 @@ "zh-CN", "en-US" ], "key": { - "dynamic_compile": { - "zh-CN": "干员动态集录", - "en-US": "Dynamic Compile" - }, - "home": { - "zh-CN": "首页", - "en-US": "Home" - }, - "changelogs": { - "zh-CN": "更新日志", - "en-US": "Changelogs" - }, - "offical_page": { - "zh-CN": "官方页面", - "en-US": "Offical Page" - }, - "disclaimer": { - "zh-CN": "免责声明", - "en-US": "Disclaimer" - }, - "disclaimer_content": { - "zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务,Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。", - "en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity." - }, - "privacy_policy": { - "zh-CN": "隐私政策", - "en-US": "Privacy Policy" - }, - "contact_us": { - "zh-CN": "联系我们", - "en-US": "Contact Us" - }, - "all": { - "zh-CN": "综合", - "en-US": "All" - }, - "elite2": { - "zh-CN": "干员晋升", - "en-US": "Elite 2" - }, - "skin": { - "zh-CN": "干员时装", - "en-US": "Skin" - }, - "zh-CN": { - "zh-CN": "简体中文", - "en-US": "Chinese (Simplified)" - }, - "en-US": { - "zh-CN": "英语", - "en-US": "English" - } + "dynamic_compile": { + "zh-CN": "动态集录", + "en-US": "Dynamic Compile" + }, + "home": { + "zh-CN": "首页", + "en-US": "Home" + }, + "changelogs": { + "zh-CN": "更新日志", + "en-US": "Changelogs" + }, + "offical_page": { + "zh-CN": "官方页面", + "en-US": "Offical Page" + }, + "disclaimer": { + "zh-CN": "免责声明", + "en-US": "Disclaimer" + }, + "disclaimer_content": { + "zh-CN": "本网站由 Halyul 设立并为明日方舟社区服务,Halyul 声明本网站完全独立运营,与 上海鹰角网络科技有限公司, Esoteric Software LLC 或其任何关联实体并无任何联系。", + "en-US": "This website is set up and operated by Halyul for the benefit of the Arknights Community. Halyul hereby states that this website is dedicated, but not related to Hypergryph Co., Ltd, Esoteric Software LLC or any of its affiliated entity." + }, + "privacy_policy": { + "zh-CN": "隐私政策", + "en-US": "Privacy Policy" + }, + "contact_us": { + "zh-CN": "联系我们", + "en-US": "Contact Us" + }, + "all": { + "zh-CN": "综合", + "en-US": "All" + }, + "elite2": { + "zh-CN": "干员晋升", + "en-US": "Elite 2" + }, + "skin": { + "zh-CN": "干员时装", + "en-US": "Skin" + }, + "voice": { + "zh-CN": "语音", + "en-US": "Voice" + }, + "live2d": { + "zh-CN": "Live2D", + "en-US": "Live2D" + }, + "zh-CN": { + "zh-CN": "简体中文", + "en-US": "Chinese (Simplified)" + }, + "en-US": { + "zh-CN": "英语", + "en-US": "English" + } } } \ No newline at end of file diff --git a/directory/src/routes/path/home.css b/directory/src/routes/path/home.css index b00fa8d..73e4aa4 100644 --- a/directory/src/routes/path/home.css +++ b/directory/src/routes/path/home.css @@ -14,6 +14,7 @@ font-size: 1.5rem; letter-spacing: 0.1rem; flex: auto; + user-select: none; } .home .item-group .item { @@ -41,7 +42,7 @@ transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s; } -.home .item-group .item .item-outline{ +.home .item-group .item .item-outline { width: 100%; height: 100%; left: -6px; @@ -86,11 +87,17 @@ background-color: var(--home-item-hover-background-color); } +.home .item-group .item .item-img .live2d { + width: 100%; + height: 360px; +} + .home .item-group .item .item-info { white-space: nowrap; position: relative; padding: 0.8rem 0.4rem; - line-height: 1; + line-height: 1.2em; + height: 36px; } .home .item-group .item .item-info .item-title-container { @@ -109,11 +116,15 @@ } .home .item-group .item .item-info .item-title-container .item-title { - line-height: 1.1em; - height: 1.25rem; + line-height: 1.3em; + height: auto; } .home .item-group .item .item-info .item-title-container .item-type { + display: flex; + flex-direction: row; + align-items: baseline; + text-align: center; width: 1.5rem; fill: var(--text-color) } diff --git a/directory/src/routes/path/home.jsx b/directory/src/routes/path/home.jsx index a5e4e5c..e4626ea 100644 --- a/directory/src/routes/path/home.jsx +++ b/directory/src/routes/path/home.jsx @@ -2,10 +2,11 @@ import { useState, useEffect, useContext, - useMemo + useRef } from 'react' import { Link, + useOutletContext, } from "react-router-dom"; import './home.css' import { ConfigContext } from '@/context/useConfigContext'; @@ -13,14 +14,19 @@ import { LanguageContext } from '@/context/useLanguageContext'; import { HeaderContext } from '@/context/useHeaderContext'; import CharIcon from '@/component/char_icon'; import MainBorder from '@/component/main_border'; -import useUmami from '@parcellab/react-use-umami' +import useUmami from '@parcellab/react-use-umami'; +import Switch from '@/component/switch'; +import spine from '!/libs/spine-player'; +import '!/libs/spine-player.css'; export default function Home() { const _trackEvt = useUmami('/') + const { setAppbarExtraArea } = useOutletContext() const { setTitle, - tabs, setTabs, - currentTab, setCurrentTab } = useContext(HeaderContext) + setTabs, + currentTab, setCurrentTab + } = useContext(HeaderContext) const { config } = useContext(ConfigContext) const { language, @@ -29,6 +35,12 @@ export default function Home() { i18n } = useContext(LanguageContext) const [content, setContent] = useState([]) + const [voiceOn, setVoiceOn] = useState(false) + const [live2dOn, setLive2dOn] = useState(false) + const [audioUrl, setAudioUrl] = useState('') + const audioEl = new Audio(audioUrl) + const live2dRefObject = useRef({}) + const live2dSpineObject = useRef({}) useEffect(() => { setTitle('dynamic_compile') @@ -48,20 +60,100 @@ export default function Home() { }, []) useEffect(() => { - const value = config.reduce((acc, cur) => { - const date = cur.date - if (acc[date]) { - acc[date].push(cur) - } else { - acc[date] = [cur] - } - return acc - }, {}) - setContent(Object.values(value).sort((a, b) => new Date(b[0].date) - new Date(a[0].date))) + setContent(config?.operators || []) }, [config]) + const toggleVoice = () => { + setVoiceOn(!voiceOn) + setAudioUrl('') + } + + useEffect(() => { + setAppbarExtraArea([ + ( + toggleVoice()} + /> + // ), ( + // setLive2dOn(!live2dOn)} + // /> + ) + ]) + }, [voiceOn, live2dOn]) + const isShown = (type) => currentTab === 'all' || currentTab === type + const playVoice = (link) => { + if (!voiceOn) return + audioEl.src = `/${link}/assets/voice/${import.meta.env.VITE_APP_VOICE_URL}` + let startPlayPromise = audioEl.play() + if (startPlayPromise !== undefined) { + startPlayPromise + .then(() => { + return + }) + .catch(() => { + return + }) + } + } + + const getLive2d = (link) => { + const reactEl =
      { + live2dRefObject.current[link] = ref + }}>
      + return reactEl + } + + useEffect(() => { + if (live2dOn) { + Object.keys(live2dRefObject.current).forEach((link) => { + const ref = live2dRefObject.current[link] + if (ref) { + if (live2dSpineObject.current[link]) { + return + } + fetch(`/_assets/dyn_portrait_char_2014_nian_nian%234.json`) + .then((res) => res.json()) + .then((data) => { + live2dSpineObject.current[link] = new spine.SpinePlayer(ref, { + skelUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.skel`, + atlasUrl: `./assets/dyn_portrait_char_2014_nian_nian%234.atlas`, + rawDataURIs: data, + animation: "Idle", + premultipliedAlpha: true, + alpha: true, + backgroundColor: "#00000000", + viewport: { + debugRender: false, + padLeft: `0%`, + padRight: `0%`, + padTop: `0%`, + padBottom: `0%`, + x: 0, + y: 0, + }, + showControls: false, + touch: false, + fps: 60, + defaultMix: 0, + success: function (widget) { + + }, + }) + }) + } + }) + } + }, [live2dOn]) + return (
      { @@ -71,31 +163,44 @@ export default function Home() {