refacor video upload (#138)

* refacor video upload

* remove unused imports
This commit is contained in:
Ilya Y 2023-07-19 14:00:58 +03:00 committed by GitHub
parent 8a2f8d2f98
commit 664adfc2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 84 additions and 117 deletions

View File

@ -180,7 +180,12 @@ export const FullArticle = (props: ArticleProps) => {
<For each={media() || []}> <For each={media() || []}>
{(m: MediaItem) => ( {(m: MediaItem) => (
<div class={styles.shoutMediaBody}> <div class={styles.shoutMediaBody}>
<VideoPlayer videoUrl={m.url} title={m.title} description={m.body} /> <VideoPlayer
articleView={true}
videoUrl={m.url}
title={m.title}
description={m.body}
/>
<Show when={m?.body}> <Show when={m?.body}>
<MD body={m.body} /> <MD body={m.body} />
</Show> </Show>

View File

@ -2,32 +2,24 @@ import { clsx } from 'clsx'
import styles from './VideoUploader.module.scss' import styles from './VideoUploader.module.scss'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { createDropzone } from '@solid-primitives/upload' import { createDropzone } from '@solid-primitives/upload'
import { createEffect, createSignal, Show } from 'solid-js' import { createSignal, For, Show } from 'solid-js'
import { useSnackbar } from '../../../context/snackbar' import { useSnackbar } from '../../../context/snackbar'
import { validateUrl } from '../../../utils/validateUrl' import { validateUrl } from '../../../utils/validateUrl'
import { VideoPlayer } from '../../_shared/VideoPlayer'
import type { MediaItem } from '../../../pages/types' import type { MediaItem } from '../../../pages/types'
// import { handleFileUpload } from '../../../utils/handleFileUpload' import { composeMediaItems } from '../../../utils/composeMediaItems'
import { VideoPlayer } from '../../_shared/VideoPlayer'
type Props = { type Props = {
class?: string video: MediaItem[]
data: (value: MediaItem[]) => void onVideoAdd: (value: MediaItem[]) => void
onVideoDelete: (mediaItemIndex: number) => void
} }
export const VideoUploader = (props: Props) => { export const VideoUploader = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const [dragActive, setDragActive] = createSignal(false) const [dragActive, setDragActive] = createSignal(false)
const [dragError, setDragError] = createSignal<string>() const [error, setError] = createSignal<string>()
const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false) const [incorrectUrl, setIncorrectUrl] = createSignal<boolean>(false)
const [data, setData] = createSignal<MediaItem>()
const updateData = (key, value) => {
setData((prev) => ({ ...prev, [key]: value }))
}
createEffect(() => {
props.data([data()])
})
const { const {
actions: { showSnackbar } actions: { showSnackbar }
@ -39,97 +31,84 @@ export const VideoUploader = (props: Props) => {
current: null current: null
} }
// const [videoUrl, setVideoUrl] = createSignal<string | undefined>()
// const runUpload = async (file) => {
// try {
// const fileUrl = await handleFileUpload(file)
// setVideoUrl(fileUrl)
// } catch (error) {
// console.error('[runUpload]', error)
// }
// }
const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({ const { setRef: dropzoneRef, files: droppedFiles } = createDropzone({
onDrop: async () => { onDrop: async () => {
setDragActive(false) setDragActive(false)
if (droppedFiles().length > 1) { if (droppedFiles().length > 1) {
setDragError(t('Many files, choose only one')) setError(t('Many files, choose only one'))
} else if (droppedFiles()[0].file.type.startsWith('video/')) { } else if (droppedFiles()[0].file.type.startsWith('video/')) {
await showSnackbar({ await showSnackbar({
body: t( body: t(
'This functionality is currently not available, we would like to work on this issue. Use the download link.' 'This functionality is currently not available, we would like to work on this issue. Use the download link.'
) )
}) })
// await runUpload(droppedFiles()[0])
} else { } else {
setDragError(t('Video format not supported')) setError(t('Video format not supported'))
} }
} }
}) })
const handleDrag = (event) => { const handleDrag = (event) => {
if (event.type === 'dragenter' || event.type === 'dragover') { if (event.type === 'dragenter' || event.type === 'dragover') {
setDragActive(true) setDragActive(true)
setDragError() setError()
} else if (event.type === 'dragleave') { } else if (event.type === 'dragleave') {
setDragActive(false) setDragActive(false)
} }
} }
const handleUrlInput = async (value: string) => { const handleUrlInput = async (value: string) => {
setError()
if (validateUrl(value)) { if (validateUrl(value)) {
updateData('url', value) props.onVideoAdd(composeMediaItems([{ url: value }]))
} else { } else {
setIncorrectUrl(true) setIncorrectUrl(true)
} }
} }
return ( return (
<div class={clsx(styles.VideoUploader, props.class)}> <Show
<Show when={props.video.length === 0}
when={data() && data().url} fallback={
fallback={ <For each={props.video}>
<> {(mi, index) => (
<div <VideoPlayer onVideoDelete={() => props.onVideoDelete(index())} videoUrl={mi?.url} />
onDragEnter={handleDrag} )}
onDragLeave={handleDrag} </For>
onDragOver={handleDrag} }
onClick={() => >
showSnackbar({ <div class={styles.VideoUploader}>
body: t( <div
'This functionality is currently not available, we would like to work on this issue. Use the download link.' onDragEnter={handleDrag}
) onDragLeave={handleDrag}
}) onDragOver={handleDrag}
} onClick={() =>
ref={dropzoneRef} showSnackbar({
class={clsx(styles.dropArea, { [styles.active]: dragActive() })} body: t(
> 'This functionality is currently not available, we would like to work on this issue. Use the download link.'
<div class={styles.text}>{t('Upload video')}</div> )
</div> })
<Show when={dragError()}> }
<div class={styles.error}>{dragError()}</div> ref={dropzoneRef}
</Show> class={clsx(styles.dropArea, { [styles.active]: dragActive() })}
<div class={styles.inputHolder}> >
<input <div class={styles.text}>{t('Upload video')}</div>
class={clsx(styles.urlInput, { [styles.hasError]: incorrectUrl() })} </div>
ref={(el) => (urlInput.current = el)} <Show when={error()}>
type="text" <div class={styles.error}>{error()}</div>
placeholder={t('Insert video link')} </Show>
onChange={(event) => handleUrlInput(event.currentTarget.value)} <div class={styles.inputHolder}>
/> <input
</div> class={clsx(styles.urlInput, { [styles.hasError]: incorrectUrl() })}
<Show when={incorrectUrl()}> ref={(el) => (urlInput.current = el)}
<div class={styles.error}>{t('It does not look like url')}</div> type="text"
</Show> placeholder={t('Insert video link')}
</> onChange={(event) => handleUrlInput(event.currentTarget.value)}
} />
> </div>
<VideoPlayer <Show when={incorrectUrl()}>
deleteAction={() => setData()} <div class={styles.error}>{t('It does not look like url')}</div>
videoUrl={data().url} </Show>
title={data().title} </div>
description={data().body} </Show>
/>
</Show>
</div>
) )
} }

View File

@ -1,9 +1,9 @@
import { For, Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js' import { Show, createSignal, createEffect, onMount, onCleanup } from 'solid-js'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { Modal } from './Modal' import { Modal } from './Modal'
import { AuthModal } from './AuthModal' import { AuthModal } from './AuthModal'
import { useModalStore } from '../../stores/ui' import { useModalStore } from '../../stores/ui'
import { router, ROUTES, useRouter } from '../../stores/router' import { router, useRouter } from '../../stores/router'
import styles from './Header.module.scss' import styles from './Header.module.scss'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'

View File

@ -1,4 +1,4 @@
import { Accessor, createMemo, createSignal, For, onCleanup, onMount, Show } from 'solid-js' import { Accessor, createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { Title } from '@solidjs/meta' import { Title } from '@solidjs/meta'
@ -17,7 +17,6 @@ import { imageProxy } from '../../utils/imageProxy'
import { GrowingTextarea } from '../_shared/GrowingTextarea' import { GrowingTextarea } from '../_shared/GrowingTextarea'
import { VideoUploader } from '../Editor/VideoUploader' import { VideoUploader } from '../Editor/VideoUploader'
import { AudioUploader } from '../Editor/AudioUploader' import { AudioUploader } from '../Editor/AudioUploader'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { slugify } from '../../utils/slugify' import { slugify } from '../../utils/slugify'
import { SolidSwiper } from '../_shared/SolidSwiper' import { SolidSwiper } from '../_shared/SolidSwiper'
import { DropArea } from '../_shared/DropArea' import { DropArea } from '../_shared/DropArea'
@ -136,7 +135,7 @@ export const EditView = (props: Props) => {
setForm('media', JSON.stringify(data)) setForm('media', JSON.stringify(data))
} }
const handleImageDelete = (index) => { const handleMediaDelete = (index) => {
const copy = [...mediaItems()] const copy = [...mediaItems()]
copy.splice(index, 1) copy.splice(index, 1)
setForm('media', JSON.stringify(copy)) setForm('media', JSON.stringify(copy))
@ -307,36 +306,18 @@ export const EditView = (props: Props) => {
editorMode={true} editorMode={true}
images={mediaItems()} images={mediaItems()}
onImageChange={handleMediaChange} onImageChange={handleMediaChange}
onImageDelete={(index) => handleImageDelete(index)} onImageDelete={(index) => handleMediaDelete(index)}
onImagesAdd={(value) => handleAddMedia(value)} onImagesAdd={(value) => handleAddMedia(value)}
onImagesSorted={(value) => handleSortedMedia(value)} onImagesSorted={(value) => handleSortedMedia(value)}
/> />
</Show> </Show>
<Show when={props.shout.layout === 'video'}> <Show when={props.shout.layout === 'video'}>
<Show <VideoUploader
when={form.media} video={mediaItems()}
fallback={ onVideoAdd={(data) => handleAddMedia(data)}
<VideoUploader onVideoDelete={(index) => handleMediaDelete(index)}
data={(data) => { />
handleAddMedia(data)
}}
/>
}
>
<For each={mediaItems()}>
{(mediaItem) => (
<>
<VideoPlayer
videoUrl={mediaItem?.url}
title={mediaItem?.title}
description={mediaItem?.body}
deleteAction={() => setForm('media', null)}
/>
</>
)}
</For>
</Show>
</Show> </Show>
<Show when={props.shout.layout === 'audio'}> <Show when={props.shout.layout === 'audio'}>

View File

@ -25,7 +25,6 @@ interface Props {
export const Slider = (props: Props) => { export const Slider = (props: Props) => {
let el: HTMLDivElement | undefined let el: HTMLDivElement | undefined
let thumbsEl: HTMLDivElement | undefined let thumbsEl: HTMLDivElement | undefined
let pagEl: HTMLDivElement | undefined
let nextEl: HTMLDivElement | undefined let nextEl: HTMLDivElement | undefined
let prevEl: HTMLDivElement | undefined let prevEl: HTMLDivElement | undefined

View File

@ -3,11 +3,13 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin: 1rem 0; margin: 1rem auto;
position: relative; position: relative;
@include media-breakpoint-up(md) { &.articleView {
margin: 1rem -16.6666% 2rem -25%; @include media-breakpoint-up(md) {
margin: 1rem -16.6666% 2rem -25%;
}
} }
.controls { .controls {

View File

@ -11,7 +11,8 @@ type Props = {
title?: string title?: string
description?: string description?: string
class?: string class?: string
deleteAction?: () => void onVideoDelete?: () => void
articleView?: boolean
} }
export const VideoPlayer = (props: Props) => { export const VideoPlayer = (props: Props) => {
@ -37,15 +38,15 @@ export const VideoPlayer = (props: Props) => {
}) })
return ( return (
<div class={clsx(styles.VideoPlayer, props.class)}> <div class={clsx(styles.VideoPlayer, props.class, { [styles.articleView]: props.articleView })}>
<Show when={props.deleteAction}> <Show when={props.onVideoDelete}>
<Popover content={t('Delete')}> <Popover content={t('Delete')}>
{(triggerRef: (el) => void) => ( {(triggerRef: (el) => void) => (
<Button <Button
ref={triggerRef} ref={triggerRef}
size="S" size="S"
class={styles.deleteAction} class={styles.deleteAction}
onClick={props.deleteAction} onClick={() => props.onVideoDelete()}
value={<Icon class={styles.deleteIcon} name="delete" />} value={<Icon class={styles.deleteIcon} name="delete" />}
/> />
)} )}

View File

@ -45,5 +45,5 @@ export type MediaItem = {
export type UploadedFile = { export type UploadedFile = {
url: string url: string
originalFilename: string originalFilename?: string
} }

View File

@ -9,7 +9,7 @@ export const composeMediaItems = (value: UploadedFile[], optionalParams = {}) =>
return { return {
url: fileData.url, url: fileData.url,
source: '', source: '',
title: removeFileExtension(fileData.originalFilename), title: fileData.originalFilename ? removeFileExtension(fileData.originalFilename) : '',
body: '', body: '',
...optionalParams ...optionalParams
} }