diff options
-rw-r--r-- | components/icons.tsx | 12 | ||||
-rw-r--r-- | components/videosourcesettings.tsx | 43 | ||||
-rw-r--r-- | pages/editor.tsx | 97 | ||||
-rw-r--r-- | project.ts | 39 |
4 files changed, 117 insertions, 74 deletions
diff --git a/components/icons.tsx b/components/icons.tsx index 5e06bf8..3240f9e 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -267,7 +267,7 @@ export function BracketsRoundedIcon() { </svg>; } -export function SlashIconRounded() { +export function SlashRoundedIcon() { return <svg width='24' height='24' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'> <path fill-rule='evenodd' @@ -276,3 +276,13 @@ export function SlashIconRounded() { /> </svg>; } + +export function UploadRoundedIcon() { + return <svg width='24' height='24' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'> + <path + d='M6.70711 8.29289L11.2929 3.70711C11.6834 3.31658 12.3166 3.31658 12.7071 3.70711L17.2929 8.29289C17.9229 8.92286 17.4767 10 16.5858 10H7.41421C6.52331 10 6.07714 8.92286 6.70711 8.29289Z' + /> + <path d='M9 10H15V15C15 15.5523 14.5523 16 14 16H10C9.44772 16 9 15.5523 9 15V10Z' /> + <rect x='5' y='18' width='14' height='2' rx='1' /> + </svg>; +} diff --git a/components/videosourcesettings.tsx b/components/videosourcesettings.tsx new file mode 100644 index 0000000..ae7f75b --- /dev/null +++ b/components/videosourcesettings.tsx @@ -0,0 +1,43 @@ +import { useRef } from 'react'; +import { LocalVideo } from '../project'; + +import Button from '@material-ui/core/Button'; +import Switch from '@material-ui/core/Switch'; + +import { UploadRoundedIcon } from './icons'; + +export function LocalVideoSettings(props: { settings: LocalVideo; }) { + var fileUploadRef = useRef(null); + + return <> + <input + ref={fileUploadRef} + type='file' + accept='video/*' + className='dispnone' + onChange={event => { + var file = event.target.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.addEventListener('load', async ev => { + props.settings.load(ev.target.result as ArrayBuffer); + }); + reader.readAsArrayBuffer(file); + }} + /> + <Button + variant='contained' + color='default' + children='Load video' + onClick={() => (fileUploadRef.current as HTMLInputElement).click()} + startIcon={<UploadRoundedIcon />} + /> + <div className='sidebyside'> + <span className='body'>Fully buffer video before presentation</span> + <Switch + value={props.settings.config.fullyBuffer} + onChange={() => props.settings.config.fullyBuffer = !props.settings.config.fullyBuffer} + /> + </div> + </>; +} diff --git a/pages/editor.tsx b/pages/editor.tsx index fac8899..5a63cb4 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -8,18 +8,17 @@ import { v4 as uuid } from 'uuid'; import FadeThroughTransition from '../components/fadethrough'; import { - BracketsRoundedIcon, FullScreenControlsRoundedIcon, MenuBarControlsRoundedIcon, PressureIcon, - SlashIconRounded, + SlashRoundedIcon, SlideKeyframe, } from '../components/icons'; import KeybindSelector from '../components/keybindselector'; import PlaySkipIconAni from '../components/play-skip'; import Selection from '../components/selection'; import TimecodeInput from '../components/timeinput'; -import Project, { LocalVideo } from '../project'; +import Project, { LocalVideo, VideoSources, VideoSourceType } from '../project'; import timeline, { anySlide, clickThroughBehaviours, @@ -34,11 +33,8 @@ import { TimedVideoPlayer } from './present'; import AppBar from '@material-ui/core/AppBar'; import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; import Fab from '@material-ui/core/Fab'; import FormControl from '@material-ui/core/FormControl'; -import IconButton from '@material-ui/core/IconButton'; import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import Select from '@material-ui/core/Select'; @@ -54,18 +50,13 @@ import Icon from '@mdi/react'; import AddRoundedIcon from '@material-ui/icons/AddRounded'; import ArrowDropDownRoundedIcon from '@material-ui/icons/ArrowDropDownRounded'; -import CloseIcon from '@material-ui/icons/Close'; import FullscreenRoundedIcon from '@material-ui/icons/FullscreenRounded'; -import KeyboardArrowDownRoundedIcon from '@material-ui/icons/KeyboardArrowDownRounded'; import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded'; import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded'; -import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded'; import SkipPreviousRoundedIcon from '@material-ui/icons/SkipPreviousRounded'; import { mdiCursorDefault } from '@mdi/js'; import DescriptionRoundedIcon from '@material-ui/icons/DescriptionRounded'; -import GetAppRoundedIcon from '@material-ui/icons/GetAppRounded'; -import VideoLabelRoundedIcon from '@material-ui/icons/VideoLabelRounded'; var keyframeInAnimations: { [key: string]: { x: number; y: number; }; } = {}; var slideAPIs: { [key: string]: any; }[] = []; @@ -92,9 +83,6 @@ interface globalState { right: slideTypes | null; }; }; - dialog: { - projectSettings: boolean; - }; settings: { node: ReactNode; name: string; @@ -130,9 +118,6 @@ var global = createState<globalState>({ right: null, }, }, - dialog: { - projectSettings: false, - }, ready: { timeline: false, video: { @@ -981,50 +966,17 @@ function TimelineEditor() { </div>; } -function DialogBox(props: { - open?: boolean; - close: () => any; - children: ReactNode; - title: string; -}) { - return <Dialog open={props.open} onClose={props.close}> - <MuiDialogTitle> - <span className='title'>{props.title}</span> - <IconButton onClick={props.close} children={<CloseIcon />} /> - </MuiDialogTitle> - <div className='inner'> - {props.children} - </div> - </Dialog>; -} - -function ProjectSettings() { - var proj = useHookstate(project).timeline; - var open = useHookstate(global).dialog.projectSettings; - - function close() { - global.update.refreshLiveTimeline.value(); - open.set(false); - } - - return <DialogBox open={open.get()} close={close} title='Project settings'> - <div className='body fullwidth-inputs form-spacing'> - <span>This is being worked on :tada:</span> - </div> - </DialogBox>; -} - function DefaultSettings() { var [nextSlideKeybinds, setNextSlideKeybinds] = useState(['Space', 'n', 'Enter']); var [previousSlideKeybinds, setPreviousSlideKeybinds] = useState(['Backspace', 'p']); var [showMenuKeybinds, setShowMenuKeybinds] = useState(['Escape', 'm']); + var [videoSourceType, setVideoSourceType] = useState(VideoSources[0].type); + var proj = useHookstate(project).timeline; var ready = useHookstate(global).ready; - return <> - <ProjectSettings /> <h2 className='title posabs h0 t0'>Presentation settings</h2> <div className='scroll posabs h0 b0'> <div className={'section ' + (ready.timeline.value ? '' : 'disabled')}> @@ -1094,6 +1046,34 @@ function DefaultSettings() { /> <KeybindSelector label='Show menu' value={showMenuKeybinds} onChange={setShowMenuKeybinds} /> </div> + <div className={'section ' + (ready.timeline.value ? '' : 'disabled')}> + <span className='title'>Source</span> + <FormControl variant='filled'> + <InputLabel>Video source</InputLabel> + <Select + value={projectFile.video?.type || ''} + onChange={e => + projectFile.video = new (VideoSources.find(s => + s.type == e.target.value as VideoSourceType + ).class)()} + IconComponent={ArrowDropDownRoundedIcon} + > + {VideoSources.map(s => <MenuItem value={s.type} children={s.name} />)} + </Select> + </FormControl> + {(() => { + if (!projectFile.video) return null; + var SourceSettings = VideoSources.find(s => s.type == projectFile.video.type).settings; + return <SourceSettings settings={projectFile.video} />; + })()} + </div> + <div className={'section ' + (ready.timeline.value ? '' : 'disabled')}> + <span className='title'>Remotes</span> + <div className='sidebyside'> + <span className='body'>Allow anonymous remotes</span> + <Switch /> + </div> + </div> <div className='section'> <span className='title'>Cool temporary buttons</span> <input @@ -1153,7 +1133,7 @@ function DefaultSettings() { projectFile.downloadProjectFile(); }} /> - <Button + {false && <Button variant='contained' color='default' children='New project' @@ -1170,10 +1150,9 @@ function DefaultSettings() { global.timeline.workingTimeline.set(player.timeline.slides); global.update.refreshLiveTimeline.value(); global.ready.timeline.set(true); - global.dialog.projectSettings.set(true); }} startIcon={<AddRoundedIcon />} - /> + />} </div> </div> </>; @@ -1451,7 +1430,7 @@ function TitleBar() { <h1>pressure</h1> <div className={'posabs abscenter projarea ' + (ready.timeline.get() ? '' : 'disabled')}> <span className='projfolder'>My presentations</span> - <SlashIconRounded /> + <SlashRoundedIcon /> <span className='projname' contentEditable @@ -1460,12 +1439,6 @@ function TitleBar() { onBlur={() => proj.name.set((nameRef.current as HTMLSpanElement).textContent.trim())} children={proj.name.get()} /> - <KeyboardArrowDownRoundedIcon - className='cpointer' - onClick={() => { - global.dialog.projectSettings.set(true); - }} - /> </div> </Toolbar> </AppBar>; @@ -2,12 +2,19 @@ import { MediaInfo as Mediainfo, ResultObject } from 'mediainfo.js/dist/types'; import JSZip from 'jszip'; import { TimedVideoPlayer } from './pages/present'; +import { LocalVideoSettings } from './components/videosourcesettings'; + // garbage garbage garbage declare var MediaInfo: () => Promise<Mediainfo>; const filext = '.prspr'; -export class LocalVideo { +interface VideoSource { + load: (data: ArrayBuffer) => void; + save: (dir: JSZip) => void; +} + +export class LocalVideo implements VideoSource { source: ArrayBuffer; type: string; mimetype: string; @@ -15,8 +22,15 @@ export class LocalVideo { framerate: number; framecount: number; + config: { + fullyBuffer: boolean; + }; + constructor(video?: ArrayBuffer) { this.type = 'local'; + this.config = { + fullyBuffer: false, + }; if (video) this.load(video); } @@ -45,21 +59,21 @@ export class LocalVideo { } } -export const VideoSourceTypeToClass = { - 'local': LocalVideo, -} as const; -// export type VideoSources = [LocalVideo]; -export type valueof<T> = T[keyof T]; -export type VideoSources = InstanceType<valueof<typeof VideoSourceTypeToClass>>; +export const VideoSources = [ + { type: 'local', class: LocalVideo, name: 'Local video', settings: LocalVideoSettings }, +] as const; + +export type VideoSourceClass = InstanceType<typeof VideoSources[number]['class']>; +export type VideoSourceType = typeof VideoSources[number]['type']; export default class { version: string; zip: JSZip; project: TimedVideoPlayer['timeline']; - video: VideoSources; + video: VideoSourceClass; constructor() { - this.version = '0.1.0'; + this.version = '0.1.1'; this.zip = new JSZip(); } @@ -89,6 +103,7 @@ export default class { var source = this.zip.folder('source'); this.video.save(source); source.file('mimetype', this.video.mimetype); + source.file('config', JSON.stringify(this.video.config, null, 4)); this.zip.file('slides', JSON.stringify(this.project.slides, null, 4)); } @@ -104,8 +119,10 @@ export default class { framecount: Number(await this.zip.file('source/framecount').async('string')), } as TimedVideoPlayer['timeline']; var type = await this.zip.file('source/type').async('string'); - if (!VideoSourceTypeToClass.hasOwnProperty(type)) return; - this.video = new VideoSourceTypeToClass[type](await this.zip.file('source/video').async('arraybuffer')); + var videoSourceType = VideoSources.find(s => s.type == type); + if (!videoSourceType) return; + this.video = new videoSourceType.class(await this.zip.file('source/video').async('arraybuffer')); this.video.mimetype = await this.zip.file('source/mimetype').async('string'); + this.video.config = JSON.parse(await this.zip.file('source/config').async('string')); } } |