Files
BiliDown/client/src/work/view/videoInfoCard.ts
T

198 lines
8.1 KiB
TypeScript

import van, { State, Val } from 'vanjs-core'
import { VideoParseResult, VideoInfoCardMode } from '../type'
import { secondToTime } from '../mixin'
import { VanComponent } from '../../mixin'
import VideoItemList from './videoItemList'
import { WorkRoute } from '..'
const { a, div, img } = van.tags
class VideoInfoCardComp implements VanComponent {
element: HTMLElement
constructor(
public workRoute: WorkRoute,
) {
const {
videoInfoCardData: data,
videoInfoCardMode: mode,
ownerFaceHide
} = workRoute
this.element = div({ class: 'card border-2 shadow-sm' },
div({ class: 'card-header' },
a({
class: 'link-dark text-decoration-none fw-bold focus-ring', href: () => data.val.targetURL,
target: '_blank',
},
() => data.val.title,
)
),
div({ class: 'card-body vstack gap-4' },
div({ class: 'vstack gap-3' },
div({ class: 'row gx-3 gy-3' },
// 封面
div({
class: () => mode.val == 'video'
? 'col-md-5 col-xl-4'
: 'col-8 col-sm-6 mx-auto col-md-5 col-lg-3 col-xl-2'
},
div({ class: 'position-relative shadow-sm rounded overflow-hidden' },
a({
href: () => data.val.targetURL,
title: () => `打开视频播放页面`,
target: '_blank',
},
img({
src: () => data.val.cover,
class: 'w-100',
ondragstart: event => event.preventDefault(),
referrerPolicy: 'no-referrer',
})
),
a({
href: () => `https://space.bilibili.com/${data.val.owner.mid}`,
title: () => `查看用户主页:${data.val.owner.name}`,
target: '_blank',
tabIndex: () => mode.val == 'video' ? 0 : -1,
},
img({
src: () => data.val.owner.face,
hidden: ownerFaceHide,
referrerPolicy: 'no-referrer',
ondragstart: event => event.preventDefault(),
style: `right: 1rem; bottom: 1rem;`,
class: 'rounded-3 border shadow position-absolute w-25'
})
),
),
),
// 字段信息
div({
class: () => mode.val == 'video'
? 'col-md-7 col-xl-8 vstack gap-2'
: 'col-md-7 col-lg-9 col-xl-10 vstack gap-2'
},
div({ class: 'position-relative h-100' },
div({ class: 'position-absolute top-0 bottom-0 position-relative-sm-down' },
Right(this)
)
),
),
),
DescriptionGroup(this, true),
),
VideoItemList(workRoute)
)
)
}
}
const Right = (parent: VideoInfoCardComp) => {
const mode = parent.workRoute.videoInfoCardMode
const data = parent.workRoute.videoInfoCardData
return div({ class: 'vstack gap-2 h-100' },
div({ class: 'row gx-2 gy-2' },
div({ class: 'col-xl-7 col-xxl-8' },
InputGroup(
van.derive(() => mode.val == 'video'
? (data.val.staff.length > 0 ? '制作信息' : '发布者')
: '参演人员'),
van.derive(() => {
if (data.val.staff.length > 0)
return data.val.staff.map(i => i.trim()).join(', ')
return data.val.owner.name
}), { disabled: true }
),
),
div({ class: 'col-xl-5 col-xxl-4' },
InputGroup('发布时间',
van.derive(() => data.val.publishData), { disabled: true }
)
),
div({ class: 'col-sm col-md-12 col-lg-4', hidden: () => mode.val != 'video' },
InputGroup('分辨率',
van.derive(() => `${data.val.dimension.width}x${data.val.dimension.height}`),
{ disabled: true }
)
),
div({ class: 'col col-lg-4', hidden: () => mode.val != 'video' },
InputGroup('时长',
van.derive(() => `${secondToTime(data.val.duration)}`),
{ disabled: true }
)
),
div({ class: 'col col-lg-4', hidden: () => mode.val != 'video' },
InputGroup('集数',
van.derive(() => data.val.pages.length.toString()),
{ disabled: true }
)
),
div({ class: 'col-md-12 col-lg-4', hidden: () => mode.val != 'season' },
InputGroup('状态',
van.derive(() => data.val.status),
{ disabled: true }
)
),
div({ class: 'col-sm col-lg-4', hidden: () => mode.val != 'season' },
InputGroup('地区',
van.derive(() => data.val.areas.map(i => i.trim()).join(', ')),
{ disabled: true }
)
),
div({ class: 'col-sm col-lg-4', hidden: () => mode.val != 'season' },
InputGroup('标签',
van.derive(() => data.val.styles.join(', ')),
{ disabled: true }
)
),
),
DescriptionGroup(parent),
)
}
/** 用于显示 `description` 字段的 `.input-group`
*
* @param parent 父组件
* @param bottom 是否在底部
*/
const DescriptionGroup = (parent: VideoInfoCardComp, bottom = false) => {
const mode = parent.workRoute.videoInfoCardMode
const data = parent.workRoute.videoInfoCardData
const _class = van.derive(() => mode.val == 'video' ? 'd-md-flex' : '')
const size = van.derive(() => mode.val == 'video' ? 'lg' : 'lg')
return div({
class: () => `shadow-sm input-group input-group-sm ${bottom
? `d-none d-lg-none ${_class.val}`
: `overflow-hidden flex-fill ${mode.val == 'video' ? 'd-md-none d-lg-flex ' : ''}`
}`,
},
div({ class: 'input-group-text align-items-start' }, '描述'),
() => {
const lines = (data.val.description.match(/^(\s*|.)$/) ? '暂无描述' : data.val.description).split('\n')
return div({ class: `form-control overflow-auto ${bottom ? `max-height-description` : `h-100`}` },
lines.map(line => div(line))
)
}
)
}
const InputGroup = (title: Val<string>, value: State<string>, option?: {
disabled?: Val<boolean>
elementType?: 'input' | 'textarea'
}) => {
return div({ class: 'input-group input-group-sm shadow-sm rounded' },
div({ class: 'input-group-text' }, title),
van.tags[option?.elementType || 'input']({
class: 'form-control bg-white',
disabled: option?.disabled || false,
style: 'cursor: text;',
value
})
)
}
export default (
workRoute: WorkRoute
) => new VideoInfoCardComp(workRoute).element