Skip to content

Commit

Permalink
[add] 文字播放效果
Browse files Browse the repository at this point in the history
  • Loading branch information
VecHK committed Dec 23, 2023
1 parent cfc4a00 commit 8916a6c
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 33 deletions.
25 changes: 22 additions & 3 deletions front/src/components/Submission/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@
}

.ScriptPlayer {
padding: 20px 0;
padding: 40px 0;
width: 320px;
box-sizing: border-box;
text-align: center;

color: rgba(58, 58, 69, 0.9);
font-family: "Noto Sans CJK SC", "Source Han Sans", "Source Han Sans CN", "Helvetica Neue", Helvetica, "Han Heiti", "微软雅黑", "黑体", sans-serif;

font-size: 20px;
}
.ScriptPlayerSelects {
padding-bottom: 0;
margin-top: 40px;
margin-bottom: 0;
padding: 0;
text-align: left;
}
.ScriptPlayerSelects > * {
.ScriptPlayerSelect {
cursor: pointer;
margin-bottom: 0.5em;
font-size: 18px;
}
.ScriptPlayerSelect:last-child {
margin-bottom: 0;
}
.ScriptPlayerText {
background-color: grey;
}
.TextContentEffect {
text-align: inherit;
}
.TextContentEffectChar {
display: inline-block;
}
178 changes: 163 additions & 15 deletions front/src/components/Submission/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode, useContext, useEffect, useMemo, useState } from 'react'
import React, { ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import s from './index.module.scss'
import { nth, partialRight, pipe, prop, thunkify } from 'ramda'

Expand Down Expand Up @@ -47,6 +47,7 @@ export type Select = {

export type Script = {
Content: Content
show_content_waittime?: number
show_select_timeout: number
selects: Select[]
}
Expand Down Expand Up @@ -83,41 +84,171 @@ export const useSubmissionStore = create<State>()(
setGalleryId: (gallery_id) => set(() => ({ gallery_id })),
setPhoto: (photo) => set(() => ({ photo }))
}),
// { },
{ name: 'submission-store' },
),
)
Object.assign(window, { useSubmissionStore })

function RenderContent({ Content, changeScript }: { Content: Content; changeScript(s: Script): void }) {
function TextContentEffectChar({ show, ch }: { show: boolean; ch: string }) {
if (show) {
return <span style={{ opacity: 0 }} className={s.TextContentEffectChar}>{ch}</span>
} else {
return <span className={s.TextContentEffectChar}>{ch}</span>
}
}

const INTERVAL_TIME = 42
export function textContentEffectTotalTime(init_time: number, Content: Content): number {
if (typeof Content === 'string') {
return init_time + (Content.length * INTERVAL_TIME)
} else {
return 0
}
}
export function TextContentEffect({
textContent,
showContentWaittime,
onPlaying,
}: { textContent: string; showContentWaittime: number; onPlaying?(): void }) {
const [cursor, setShowingCursor] = useState(0)
const [ is_playing, setPlaying ] = useState(false)

useEffect(() => {
if (is_playing) {
onPlaying && onPlaying()
}
})

useEffect(() => {
let int_handler: undefined | NodeJS.Timeout = undefined

function playInterval() {
int_handler = setInterval(() => {
setPlaying(true)
setShowingCursor(cursor => {
if (cursor < textContent.length) {
return cursor + 1
} else {
setPlaying(false)
return textContent.length
}
})
}, INTERVAL_TIME)
}

if (cursor === 0) {
const h = setTimeout(() => {
playInterval()
}, showContentWaittime)
return () => {
clearTimeout(h)
if (int_handler !== undefined) {
clearInterval(int_handler)
}
}
} else {
playInterval()
return () => {
if (int_handler !== undefined) {
clearInterval(int_handler)
}
}
}
}, [cursor, showContentWaittime, textContent.length])

const chel_list = useMemo(() => {
return textContent.split('').map((ch, idx) => {
const show = cursor <= idx
return <TextContentEffectChar show={show} ch={ch} key={`${idx}-${ch}`} />
})
}, [cursor, textContent])

return <div className={s.TextContentEffect}>{chel_list}</div>
}

function RenderContent({
Content,
changeScript,
showContentWaittime,
}: { Content: Content; changeScript(s: Script): void; showContentWaittime: number }) {
const is_component = typeof Content === 'function'
return useMemo(() => {
if (typeof Content === 'function') {
if (is_component) {
return <Content changeScript={changeScript} />
} else {
return <TextContentEffect textContent={ Content } showContentWaittime={showContentWaittime} />
}
}, [Content, changeScript, is_component, showContentWaittime])
}

function Select({
Content,
show_wait,
changeScript,
onPress,
}: { Content: Content; changeScript(s: Script): void; show_wait: number; onPress(): void }) {
const [show_list_item_icon, setShow] = useState(
typeof Content === 'function'
)
const is_component = typeof Content === 'function'
const inner = useMemo(() => {
if (is_component) {
return <Content changeScript={changeScript} />
} else {
return <>{Content}</>
return <TextContentEffect
textContent={ Content }
showContentWaittime={show_wait}
onPlaying={() => setShow(true)}
/>
}
}, [Content, changeScript])
}, [Content, changeScript, is_component, show_wait])
return (
<li
className={s.ScriptPlayerSelect}
onClick={onPress}
style={{
listStyleType: show_list_item_icon ? 'disclosure-closed' : 'none'
}}
>
{inner}
</li>
)
}

function ScriptPlayerSelects({
selects,
onClickSelect,
changeScript,
}: { selects: Select[]; onClickSelect: (i: number) => void; changeScript: (s: Script) => void }) {
waittime,
}: {
selects: Select[];
onClickSelect: (i: number) => void;
changeScript: (s: Script) => void
waittime: number
}) {
if (!selects.length) {
return <></>
} else {
const select_play_time_list = selects.map(
s => textContentEffectTotalTime(0, s.Content)
)
const add = (a: number, b: number) => a + b
const sum = (nums: number[]) => nums.reduce(add, 0)

return (
<ul className={s.ScriptPlayerSelects}>
{
selects.map((s, idx) => (
<li
className="script-player-select"
<Select
key={idx}
onClick={thunkify(onClickSelect)(idx)}
>
<RenderContent Content={ s.Content } changeScript={changeScript} />
</li>
onPress={thunkify(onClickSelect)(idx)}
Content={s.Content}
changeScript={changeScript}
show_wait={
waittime +
sum(select_play_time_list.slice(0, idx)) +
(INTERVAL_TIME * 10) * idx
}
/>
))
}
</ul>
Expand All @@ -126,14 +257,30 @@ function ScriptPlayerSelects({
}

function ScriptPlayer({ script, changeScript }: { script: Script; changeScript: (s: Script) => void }) {
const { show_content_waittime = 0 } = script
const { show_select_timeout } = script

const show_selects_waittime = useMemo(() => {
const t = textContentEffectTotalTime(
Number(show_content_waittime),
script.Content
)
if (t === 0) {
return 0 + show_select_timeout
} else {
return t + show_select_timeout
}
}, [script.Content, show_content_waittime, show_select_timeout])

return (
<div className={s.ScriptPlayer}>
<div className="script-player-text">
<RenderContent Content={ script.Content } changeScript={changeScript} />
<RenderContent showContentWaittime={show_content_waittime} Content={ script.Content } changeScript={changeScript} />
</div>
<ScriptPlayerSelects
selects={script.selects}
changeScript={changeScript}
waittime={show_selects_waittime}
onClickSelect={
pipe(
partialRight(nth, [script.selects]),
Expand All @@ -159,6 +306,7 @@ export default function Submission({ gallery }: { gallery: Gallery }) {
return (
<div className={s.Submission}>
<ScriptPlayer
key={Date.now()}
script={current_script}
changeScript={s => {
setTimeout(() => {
Expand Down
14 changes: 9 additions & 5 deletions front/src/components/Submission/scripts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { AppCriticalError } from 'App'
import { confirmQQNum, getSubmissionByQQNum } from 'api/member'

import { componentScript, script, select, Script, Select, Content, useSubmissionStore, jumpScript } from './'
// import { Script, ComponentScript, Select, } from './SP'

import WaitingInputFrame from 'components/ConfirmQQ/WaitingInputFrame'
import { timeout } from 'new-vait'
import Loading from 'components/Loading'
Expand Down Expand Up @@ -87,7 +87,10 @@ export function init() {
return (
<>
{exists_text}
<img src={photo.thumb_url} style={{ height: '100px' }} />
<img src={photo.thumb_url} style={{
width: '320px',
height: `${320 / (photo.width / photo.height)}px`
}} />
</>
)
})
Expand Down Expand Up @@ -168,7 +171,7 @@ export function init() {

const script_同装同装 = componentScript([], () => {
return <>
<img src={image_同装同装} style={{ height: '80px' }} />
<img src={image_同装同装} style={{ height: '120px' }} />
</>
})

Expand Down Expand Up @@ -197,10 +200,11 @@ export function init() {

return {
Content: '听说你要参加摄影大赛',
show_content_waittime: 750,
show_select_timeout: 1000,
selects: [
{
Content: '',
Content: '是的吧我要参加',
next_script: {
Content: ({ changeScript }) => <RequestInputQQNumber loginSuccess={async () => {
const my_submission = await updateMySubmission()
Expand All @@ -215,7 +219,7 @@ export function init() {
}
},
{
Content: '否',
Content: '否,我不想参加',
next_script: script_听说你在下周会来参加投票
},
{
Expand Down
16 changes: 8 additions & 8 deletions front/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import * as serviceWorker from './serviceWorker'
// const VConsole = require('vconsole')
// window.vConsole = new VConsole()

// ReactDOM.render(<App />, document.getElementById('root'))
ReactDOM.render(<App />, document.getElementById('root'))

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister()

import('./App').then(AppLoaded => {
import('react-dom').then((ReactDOMLoaded) => {
const ReactDOM = ReactDOMLoaded.default as any
const App = AppLoaded.default as any
ReactDOM.render(<App />, document.getElementById('root'))
})
})
// import('./App').then(AppLoaded => {
// import('react-dom').then((ReactDOMLoaded) => {
// const ReactDOM = ReactDOMLoaded.default as any
// const App = AppLoaded.default as any
// ReactDOM.render(<App />, document.getElementById('root'))
// })
// })

document.getElementById('root')
3 changes: 1 addition & 2 deletions front/src/layouts/GalleryHome/components/ActivityLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ export default function ActivityLayout({
const idx = newSelectedIdList.indexOf(id)

if (idx === -1) {
if (gallery.vote_limit && (newSelectedIdList.length >= gallery.vote_limit)) {
// alert('enough')
if ((gallery.vote_limit > 0) && (newSelectedIdList.length >= gallery.vote_limit)) {
return
} else {
setArrowTickTock(Date.now())
Expand Down

0 comments on commit 8916a6c

Please sign in to comment.