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'));  	}  }  |