diff options
author | lonkaars <loek@pipeframe.xyz> | 2021-07-21 22:22:01 +0200 |
---|---|---|
committer | lonkaars <loek@pipeframe.xyz> | 2021-07-21 22:22:01 +0200 |
commit | 77cd794b98ede903caa8e181fea39ba2d7eac5a9 (patch) | |
tree | 998e13238e1a9bb91b3ad2447cb28f8a12a5a833 | |
parent | bba9a3cb8648ca865ca0c32565e66b48b9ab6f5a (diff) |
project file saving and loading :tada:
-rw-r--r-- | components/icons.tsx | 15 | ||||
-rw-r--r-- | pages/editor.tsx | 82 | ||||
-rw-r--r-- | project.ts | 40 |
3 files changed, 112 insertions, 25 deletions
diff --git a/components/icons.tsx b/components/icons.tsx index 81d447d..15632f0 100644 --- a/components/icons.tsx +++ b/components/icons.tsx @@ -251,3 +251,18 @@ export function LoginRoundedIcon() { /> </svg>; } + +export function BracketsRoundedIcon() { + return <svg width='24' height='24' viewBox='0 0 24 24' fill='currentColor' xmlns='http://www.w3.org/2000/svg'> + <path + fill-rule='evenodd' + clip-rule='evenodd' + d='M8.5 6C8.10218 6 7.72064 6.15804 7.43934 6.43934C7.15804 6.72064 7 7.10217 7 7.5V9.5C7 9.95963 6.90947 10.4148 6.73358 10.8394C6.55769 11.264 6.29988 11.6499 5.97487 11.9749C5.96645 11.9833 5.95799 11.9917 5.94949 12C5.95799 12.0083 5.96645 12.0167 5.97487 12.0251C6.29988 12.3501 6.55769 12.736 6.73358 13.1606C6.90947 13.5852 7 14.0404 7 14.5V16.5C7 16.8978 7.15804 17.2794 7.43934 17.5607C7.72064 17.842 8.10218 18 8.5 18H9C9.55228 18 10 18.4477 10 19C10 19.5523 9.55228 20 9 20H8.5C7.57174 20 6.6815 19.6313 6.02513 18.9749C5.36875 18.3185 5 17.4283 5 16.5V14.5C5 14.303 4.9612 14.108 4.88582 13.926C4.81044 13.744 4.69995 13.5786 4.56066 13.4393C4.42137 13.3001 4.25601 13.1896 4.07403 13.1142C3.89204 13.0388 3.69698 13 3.5 13H3C2.44772 13 2 12.5523 2 12C2 11.4477 2.44772 11 3 11H3.5C3.69698 11 3.89204 10.9612 4.07403 10.8858C4.25601 10.8104 4.42137 10.6999 4.56066 10.5607C4.69995 10.4214 4.81044 10.256 4.88582 10.074C4.9612 9.89204 5 9.69698 5 9.5V7.5C5 6.57174 5.36875 5.6815 6.02513 5.02513C6.6815 4.36875 7.57174 4 8.5 4H9C9.55228 4 10 4.44772 10 5C10 5.55228 9.55228 6 9 6H8.5Z' + /> + <path + fill-rule='evenodd' + clip-rule='evenodd' + d='M15.5 6C15.8978 6 16.2794 6.15804 16.5607 6.43934C16.842 6.72064 17 7.10217 17 7.5V9.5C17 9.95963 17.0905 10.4148 17.2664 10.8394C17.4423 11.264 17.7001 11.6499 18.0251 11.9749C18.0335 11.9833 18.042 11.9917 18.0505 12C18.042 12.0083 18.0335 12.0167 18.0251 12.0251C17.7001 12.3501 17.4423 12.736 17.2664 13.1606C17.0905 13.5852 17 14.0404 17 14.5V16.5C17 16.8978 16.842 17.2794 16.5607 17.5607C16.2794 17.842 15.8978 18 15.5 18H15C14.4477 18 14 18.4477 14 19C14 19.5523 14.4477 20 15 20H15.5C16.4283 20 17.3185 19.6313 17.9749 18.9749C18.6313 18.3185 19 17.4283 19 16.5V14.5C19 14.303 19.0388 14.108 19.1142 13.926C19.1896 13.744 19.3001 13.5786 19.4393 13.4393C19.5786 13.3001 19.744 13.1896 19.926 13.1142C20.108 13.0388 20.303 13 20.5 13H21C21.5523 13 22 12.5523 22 12C22 11.4477 21.5523 11 21 11H20.5C20.303 11 20.108 10.9612 19.926 10.8858C19.744 10.8104 19.5786 10.6999 19.4393 10.5607C19.3001 10.4214 19.1896 10.256 19.1142 10.074C19.0388 9.89204 19 9.69698 19 9.5V7.5C19 6.57174 18.6313 5.6815 17.9749 5.02513C17.3185 4.36875 16.4283 4 15.5 4H15C14.4477 4 14 4.44772 14 5C14 5.55228 14.4477 6 15 6H15.5Z' + /> + </svg>; +} diff --git a/pages/editor.tsx b/pages/editor.tsx index 47402ec..115411f 100644 --- a/pages/editor.tsx +++ b/pages/editor.tsx @@ -8,6 +8,7 @@ import { v4 as uuid } from 'uuid'; import FadeThroughTransition from '../components/fadethrough'; import { + BracketsRoundedIcon, FullScreenControlsRoundedIcon, MenuBarControlsRoundedIcon, PressureIcon, @@ -60,7 +61,7 @@ import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded'; import SkipPreviousRoundedIcon from '@material-ui/icons/SkipPreviousRounded'; import { mdiCursorDefault } from '@mdi/js'; -import CodeRoundedIcon from '@material-ui/icons/CodeRounded'; +import DescriptionRoundedIcon from '@material-ui/icons/DescriptionRounded'; import GetAppRoundedIcon from '@material-ui/icons/GetAppRounded'; import VideoLabelRoundedIcon from '@material-ui/icons/VideoLabelRounded'; @@ -68,6 +69,7 @@ var keyframeInAnimations: { [key: string]: { x: number; y: number; }; } = {}; var slideAPIs: { [key: string]: any; }[] = []; var player = new TimedVideoPlayer(); +var projectFile = new Project(); interface globalState { timeline: { @@ -1142,23 +1144,20 @@ function DefaultSettings() { <input type='file' id='jsonUpload' - accept='.prspr' + accept='application/json' className='dispnone' onChange={event => { var file = event.target.files[0]; if (!file) return; var reader = new FileReader(); reader.addEventListener('load', ev => { - var proj = new Project(); - proj.video = new LocalVideo(); - proj.video.load(ev.target.result as ArrayBuffer); - // player.loadSlides(ev.target.result as string); - // project.timeline.set(player.timeline); - // global.timeline.workingTimeline.set(player.timeline.slides); - // global.update.refreshLiveTimeline.value(); - // global.ready.timeline.set(true); + player.loadSlides(ev.target.result as string); + project.timeline.set(player.timeline); + global.timeline.workingTimeline.set(player.timeline.slides); + global.update.refreshLiveTimeline.value(); + global.ready.timeline.set(true); }); - reader.readAsArrayBuffer(file); + reader.readAsText(file); }} /> <Button @@ -1166,7 +1165,64 @@ function DefaultSettings() { color='default' children='Load json' onClick={() => document.getElementById('jsonUpload').click()} - startIcon={<CodeRoundedIcon />} + startIcon={<BracketsRoundedIcon />} + /> + <input + type='file' + id='prsprUpload' + accept='.prspr' + className='dispnone' + onChange={event => { + var file = event.target.files[0]; + if (!file) return; + var reader = new FileReader(); + reader.addEventListener('load', async ev => { + await projectFile.openProject(ev.target.result as ArrayBuffer); + + player.loadSlides(JSON.stringify(projectFile.project)); + project.timeline.set(player.timeline); + global.timeline.workingTimeline.set(player.timeline.slides); + global.update.refreshLiveTimeline.value(); + global.ready.timeline.set(true); + + player.loadVideo( + 'data:' + projectFile.video.mimetype + ';base64,' + + btoa(String.fromCharCode.apply(null, new Uint8Array(projectFile.video.source))), + ); + global.ready.video.available.set(true); + + player.player.addEventListener( + 'canplaythrough', + () => global.ready.video.playable.set(true), + ); + + player.player.addEventListener('play', () => global.timeline.playing.set(true)); + player.player.addEventListener('pause', () => global.timeline.playing.set(false)); + }); + reader.readAsArrayBuffer(file); + }} + /> + <Button + variant='contained' + color='default' + children='Load .prspr' + onClick={() => document.getElementById('prsprUpload').click()} + startIcon={<DescriptionRoundedIcon />} + /> + <Button + variant='contained' + color='default' + children='Download .prspr' + startIcon={<DescriptionRoundedIcon />} + onClick={async () => { + var vidSrc = player.player.src.split(';'); + projectFile.loadProject(player.timeline); + projectFile.video = new LocalVideo(); + await projectFile.video.load(Uint8Array.from(atob(vidSrc[1].substr(7)), c => c.charCodeAt(0))); + projectFile.video.mimetype = vidSrc[0].substr(5); + projectFile.saveProject(); + projectFile.downloadProjectFile(); + }} /> <Button variant='contained' @@ -1220,8 +1276,6 @@ function SlideProperties(props: { var slide = useHookstate(global).selection.slides[0]; - var [test, setTest] = useState(0); - return <div className='section'> <span className='title'>Properties</span> {{ @@ -9,15 +9,15 @@ const filext = '.prspr'; export class LocalVideo { source: ArrayBuffer; - fileext: string; - mimetype: `video/${string}`; type: string; + mimetype: string; framerate: number; framecount: number; - constructor() { + constructor(video?: ArrayBuffer) { this.type = 'local'; + if (video) this.load(video); } async load(data: ArrayBuffer) { @@ -38,27 +38,31 @@ export class LocalVideo { } save(dir: JSZip) { - dir.file('video.' + this.fileext, this.source); + dir.file('video', this.source); + dir.file('framecount', this.framecount.toString()); + dir.file('framerate', this.framerate.toString()); + dir.file('type', this.type); } } -export type VideoSources = [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 default class { version: string; zip: JSZip; project: TimedVideoPlayer['timeline']; - video: VideoSources[number]; + video: VideoSources; constructor() { this.version = '0.1.0'; this.zip = new JSZip(); } - loadProjectFile(data: Blob) { - this.zip = new JSZip(data); - } - async downloadProjectFile() { var zip = await this.zip.generateAsync({ type: 'blob' }); var blob = new Blob([zip], { type: 'application/octet-stream' }); @@ -84,10 +88,24 @@ export default class { var source = this.zip.folder('source'); this.video.save(source); + source.file('mimetype', this.video.mimetype); this.zip.file('slides', JSON.stringify(this.project.slides, null, 4)); } - openProject() { + async openProject(data: ArrayBuffer) { + this.zip = new JSZip(); + await this.zip.loadAsync(data); + this.project = { + name: await this.zip.file('meta/name').async('string'), + settings: { controlType: await this.zip.file('settings/controlType').async('string') }, + slides: JSON.parse(await this.zip.file('slides').async('string')), + framerate: Number(await this.zip.file('source/framerate').async('string')), + 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')); + this.video.mimetype = await this.zip.file('source/mimetype').async('string'); } } |