2023-09-24 18:39:52 +02:00
import '../theatre_modules/core-and-studio.js'
import {
downloadFile ,
uploadFile ,
verifyVariableTimeProject ,
hashFromString ,
clone ,
getParents ,
arraysEqual ,
2023-10-02 10:05:20 +02:00
sequencialPromises ,
2023-09-24 18:39:52 +02:00
} from './utils.js' ;
//import {
//config
//} from './config.js';
const TheatrePlay = function ( autoInit = false ) {
//private
const {
core ,
studio
} = Theatre ;
let project = false ;
const theatreObjects = { } ;
let theatrePanel = null ;
let sequencePanelLeft = null ;
const getSequencePanelLeft = ( ) => {
// if (sequencePanelLeft === null) {
sequencePanelLeft = tp . shadowRoot . querySelector ( '[data-testid="SequencePanel-Left"]' ) ;
// }
return sequencePanelLeft ;
} ;
const getPanel = ( ) => {
if ( theatrePanel === null ) {
theatrePanel = tp . shadowRoot . querySelector ( '[data-testid="DetailPanel-Object"]' ) ;
}
return theatrePanel ;
} ;
const setPanelClasses = ( ) => {
const parents = getParents ( getPanel ( ) . querySelector ( '[title^="obj.props."]' ) , getPanel ( ) )
const panelControlsWrapper = parents [ parents . length - 3 ] ;
const panelWrapper = parents [ parents . length - 2 ] ;
const panelMomWrapper = parents [ parents . length - 1 ] ;
panelControlsWrapper . classList . add ( "panelControlsWrapper" ) ;
panelWrapper . classList . add ( "panelWrapper" ) ;
panelMomWrapper . classList . add ( "panelMomWrapper" ) ;
getPanel ( ) . classList . add ( 'panel' ) ;
getPanel ( ) . setAttribute ( 'id' , 'panel' ) ;
} ;
const getPanelPropTitle = ( propTitle ) => {
const panel = getPanel ( ) ;
let panelPropTitle = panel . querySelector ( ` [title="obj.props. ${ propTitle } "] ` ) ;
if ( panelPropTitle !== null ) {
return panelPropTitle ;
}
panelPropTitle = panel . querySelector ( ` [title^="obj.props. ${ propTitle } "] ` ) ;
if ( panelPropTitle !== null ) {
return panelPropTitle . parentElement . parentElement ;
}
return null ;
}
const getPanelPropContainer = ( panelPropTitle ) => {
if ( typeof panelPropTitle === 'string' ) {
panelPropTitle = getPanelPropTitle ( panelPropTitle ) ;
}
if ( panelPropTitle === null ) {
return null ;
}
const panel = getPanel ( ) ;
let panelControls = panel . querySelector ( '.panelControlsWrapper' ) ;
if ( panelControls === null ) {
setPanelClasses ( ) ;
}
panelControls = panel . querySelector ( '.panelControlsWrapper' ) ;
if ( panelControls === null ) {
return null ;
}
const parents = getParents ( panelPropTitle , panelControls ) ;
if ( parents === null || parents . length < 1 ) {
return null ;
}
return parents [ parents . length - 1 ] ;
} ;
const getPropKeyframes = ( layer , propPath ) => {
if ( ! Array . isArray ( propPath ) || propPath . length <= 0 ) {
return false ;
}
let prop = layer . theatreObject . props ;
let value = layer . theatreObject . value ;
for ( let i = 0 ; i < propPath . length ; i ++ ) {
const p = propPath [ i ] ;
prop = prop [ p ] ;
value = value [ p ] ;
if ( typeof value === 'undefined' ) {
return false ;
}
} ;
return this . sheet . sequence . _ _experimental _getKeyframes ( prop ) ;
} ;
// wtf, this function was being written in one go
// without any errors. huh?
const getKeyframes = ( layer , valuePath = [ ] ) => {
const keyframes = [ ] ;
const theatreValues = layer . theatreObject . value ;
let value = theatreValues ;
for ( let i = 0 ; i < valuePath . length ; i ++ ) {
value = value [ valuePath [ i ] ] ;
if ( typeof value === 'undefined' ) {
return false ;
}
}
const isColor = valuePath . length > 0 &&
typeof value === 'object' &&
valuePath [ valuePath . length - 1 ] === 'color' ;
if ( typeof value === 'object' && ! isColor ) {
Object . keys ( value ) . forEach ( ( key ) => {
const newValuePath = [ ... valuePath , ... [ key ] ] ;
const subkeyframes = getKeyframes ( layer , newValuePath ) ;
if ( subkeyframes !== false && subkeyframes . length > 0 ) {
for ( let i = 0 ; i < subkeyframes . length ; i ++ ) {
keyframes . push ( subkeyframes [ i ] ) ;
}
}
} ) ;
} else {
const propKeyframes = getPropKeyframes ( layer , valuePath ) ;
if ( propKeyframes . length > 0 ) {
keyframes . push ( { path : valuePath , keyframes : propKeyframes } ) ;
}
}
return keyframes ;
} ;
const getSequenceButton = ( path ) => {
2023-10-07 12:21:36 +02:00
let t = getPanelPropTitle ( Array . isArray ( path ) ? path . join ( '.' ) : path ) ;
2023-09-24 18:39:52 +02:00
if ( t === null ) {
return null ;
}
return t . parentElement . querySelector ( '[title="Sequence this prop"]' ) ;
} ;
2023-10-07 12:21:36 +02:00
const isSequenced = ( path ) => {
return getSequenceButton ( path ) === null ;
} ;
2023-10-02 10:05:20 +02:00
2023-10-07 12:21:36 +02:00
const setSequenced = ( propTitle , sequenced , metaResolve = false ) => {
const f = ( resolve ) => {
const propIsSequenced = isSequenced ( propTitle ) ;
const somethingToDo = sequenced !== propIsSequenced ;
if ( somethingToDo ) {
const contextItem = sequenced ? 'sequence' : 'make static' ;
const antiContextItem = sequenced ? 'make static' : 'sequence' ;
2023-10-02 10:05:20 +02:00
2023-10-07 12:21:36 +02:00
const finishedSequencedEvent = ( e ) => {
// only care about events from our prop
if ( propTitle === e . detail . prop . join ( '.' ) ) {
// if we un-sequence, we listen to stateEditors' event
if ( ! sequenced && e . detail . origin === 'stateEditors.ts' && e . detail . sequenced === sequenced ) {
window . removeEventListener ( 'sequenceEvent' , finishedSequencedEvent ) ;
resolve ( true ) ;
2023-10-02 10:05:20 +02:00
2023-10-07 12:21:36 +02:00
// if we sequence, then we wait until the track is there
} else if ( sequenced && e . detail . origin === 'BasicKeyframedTrack.tsx' && e . detail . sequenced === sequenced ) {
window . removeEventListener ( 'sequenceEvent' , finishedSequencedEvent ) ;
resolve ( true ) ;
} else {
2023-10-07 18:01:00 +02:00
// pretty verbose
//console.log('TheatrePlayu::setSequenced', 'ignored event', e, e.detail);
2023-10-07 12:21:36 +02:00
}
2023-10-02 10:05:20 +02:00
}
2023-10-07 12:21:36 +02:00
} ;
2023-10-02 10:05:20 +02:00
2023-10-07 12:21:36 +02:00
let counter = 0 ;
const clickContextMenu = ( e ) => {
let done = false ;
if ( e . target !== null ) {
e . target . removeEventListener ( 'contextmenu' , clickContextMenu ) ;
}
tp . shadowRoot . querySelectorAll ( 'ul li span' ) . forEach ( ( s ) => {
if ( s . innerHTML . toLowerCase ( ) === contextItem . toLowerCase ( ) ) {
window . addEventListener ( 'sequenceEvent' , finishedSequencedEvent ) ;
s . click ( ) ;
done = true ;
} else if ( s . innerHTML . toLowerCase ( ) === antiContextItem . toLowerCase ( ) ) {
done = true ;
resolve ( false ) ;
}
} ) ;
if ( ! done ) {
setTimeout ( ( ) => {
if ( counter < 4 ) {
clickContextMenu ( e ) ;
counter ++ ;
} else {
setSequenced ( propTitle , sequenced , resolve ) ;
}
} , 100 ) ;
}
} ;
getPanelPropTitle ( propTitle ) . addEventListener ( 'contextmenu' , clickContextMenu ) ;
getPanelPropTitle ( propTitle ) . dispatchEvent ( new Event ( 'contextmenu' ) ) ;
} else {
resolve ( ) ;
}
} ;
if ( ! metaResolve ) {
return new Promise ( ( resolve ) => {
f ( resolve ) ;
} ) ;
} else {
f ( metaResolve ) ;
}
2023-10-02 10:05:20 +02:00
} ;
2023-09-24 18:39:52 +02:00
const addKeyframes = ( layer , keyframes ) => {
return new Promise ( ( resolve ) => {
if ( ! Array . isArray ( keyframes ) ) {
2023-10-02 10:05:20 +02:00
resolve ( false ) ;
2023-09-24 18:39:52 +02:00
return false ;
}
const existingKeyframes = getKeyframes ( layer ) ;
const promises = [ ] ;
2023-10-07 12:21:36 +02:00
const ms = 0 ; //config.tp.addKeyframesTimeout_s * 1000;
2023-09-24 18:39:52 +02:00
keyframes . forEach ( ( k ) => {
let prop = layer . theatreObject . props ;
for ( let i = 0 ; i < k . path . length ; i ++ ) {
prop = prop [ k . path [ i ] ] ;
}
const position = tp . sheet . sequence . position ;
2023-10-07 12:21:36 +02:00
promises . push ( ( ) => {
return new Promise ( ( subResolve ) => {
2023-09-24 18:39:52 +02:00
setTimeout ( ( ) => {
2023-10-07 12:21:36 +02:00
if ( layer . isSelected ( ) ) {
setSequenced ( k . path . join ( '.' ) , true )
. then ( ( ) => {
2023-10-02 10:05:20 +02:00
subResolve ( ) ;
2023-10-07 12:21:36 +02:00
} ) ;
} else {
// we cannot select layers without pseudoclicking
// so let's wait for a happy 'injected' event that
// closes off the selection
//
// first, the listener callback
const f = ( ) => {
tp . getPanel ( ) . removeEventListener ( 'injected' , f ) ;
setSequenced ( k . path . join ( '.' ) , true )
. then ( ( ) => {
subResolve ( ) ;
} ) ;
} ;
// then add it
tp . getPanel ( ) . addEventListener ( 'injected' , f ) ;
// and fire the click
layer . select ( ) ;
}
} , ms ) ; // * promises.length);
} )
} ) ;
2023-09-24 18:39:52 +02:00
let propHasKeyframesAt = - 1 ;
2023-10-07 12:21:36 +02:00
if ( existingKeyframes !== null &&
existingKeyframes !== false &&
typeof existingKeyframes !== 'undefined' &&
Array . isArray ( existingKeyframes ) ) {
existingKeyframes . forEach ( ( existingK , existingKI ) => {
if ( arraysEqual ( k . path , existingK . path ) ) {
propHasKeyframesAt = existingKI ;
}
} ) ;
}
2023-09-24 18:39:52 +02:00
k . keyframes . forEach ( ( keyframe ) => {
let alreadyThere = false ;
if ( propHasKeyframesAt >= 0 ) {
existingKeyframes [ propHasKeyframesAt ] . keyframes . forEach ( ( kf ) => {
if ( keyframe . position === kf . position &&
keyframe . value === kf . value ) {
alreadyThere = true ;
}
} ) ;
}
if ( ! alreadyThere ) {
2023-10-07 12:21:36 +02:00
promises . push ( ( ) => {
return new Promise ( ( subResolve ) => {
setTimeout ( ( ) => {
tp . sheet . sequence . position = keyframe . position ;
this . studio . transaction ( ( {
set
} ) => {
set ( prop , keyframe . value ) ;
subResolve ( ) ;
} ) ;
} , ms ) ; // * promises.length);
} )
} ) ;
2023-09-24 18:39:52 +02:00
}
} ) ;
2023-10-07 12:21:36 +02:00
promises . push ( ( ) => {
return new Promise ( ( subResolve ) => {
setTimeout ( ( ) => {
tp . sheet . sequence . position = position ;
subResolve ( ) ;
} , ms ) ; // * promises.length);
} )
} ) ;
2023-09-24 18:39:52 +02:00
} ) ;
2023-10-02 10:05:20 +02:00
sequencialPromises ( promises , resolve ) ;
//Promise.all(promises).then(() => {
2023-10-07 12:21:36 +02:00
//resolve();
2023-10-02 10:05:20 +02:00
//});
} ) ;
} ;
const setKeyframes = ( layer , keyframes ) => {
return new Promise ( ( resolve ) => {
if ( ! Array . isArray ( keyframes ) ) {
resolve ( false ) ;
return false ;
}
const promises = [ ] ;
2023-10-07 12:21:36 +02:00
let waitify = false ;
2023-10-02 10:05:20 +02:00
keyframes . forEach ( ( k ) => {
2023-10-07 12:21:36 +02:00
const propTitle = k . path . join ( '.' ) ;
if ( isSequenced ( propTitle ) ) {
waitify = true ;
promises . push ( ( ) => {
return new Promise ( ( subResolve ) => {
setSequenced ( propTitle , false )
. then ( ( ) => {
subResolve ( ) ;
} ) ;
} ) ;
} ) ;
}
2023-09-24 18:39:52 +02:00
} ) ;
2023-10-07 12:21:36 +02:00
sequencialPromises ( promises , ( ) => {
const timeout _ms = waitify ? 1000 : 0 ;
setTimeout ( ( ) => {
2023-10-02 10:05:20 +02:00
addKeyframes ( layer , keyframes )
. then ( resolve ) ;
2023-10-07 12:21:36 +02:00
} , timeout _ms ) ;
} ) ;
2023-09-24 18:39:52 +02:00
} ) ;
} ;
2023-10-02 10:05:20 +02:00
2023-09-24 18:39:52 +02:00
const friendlySequenceNames = ( ) => {
const sequencePanelLeft = tp . getSequencePanelLeft ( ) ;
let doItAgain = true ;
if ( sequencePanelLeft !== null ) {
const titles = sequencePanelLeft . querySelectorAll ( '[data-testid="SequencePanel-Title"]' ) ;
const propTitles = sequencePanelLeft . querySelectorAll ( '[data-testid="SequencePanel-PropTitle"]' ) ;
const allTitles = [ ... [ ... titles ] , ... [ ... propTitles ] ] ;
if ( allTitles . length > 0 ) {
allTitles . forEach ( ( title ) => {
// NOTE: should we have the same artboard and layer friendlyName,
// the layer friendlyName will be picked
Object . keys ( config . layer . friendlyNames ) . forEach ( ( key ) => {
if ( title . innerHTML === key ) {
title . innerHTML = config . layer . friendlyNames [ key ] ;
}
} ) ;
Object . keys ( config . artboard . friendlyNames ) . forEach ( ( key ) => {
if ( title . innerHTML === key ) {
title . innerHTML = config . artboard . friendlyNames [ key ] ;
}
} ) ;
} ) ;
}
doItAgain = false ;
}
if ( doItAgain ) {
setTimeout ( ( ) => {
friendlySequenceNames ( ) ;
} , 1000 ) ;
}
} ;
//public
2023-10-02 10:05:20 +02:00
this . setSequenced = setSequenced ;
2023-09-24 18:39:52 +02:00
this . friendlySequenceNames = friendlySequenceNames ;
this . getKeyframes = getKeyframes ;
this . addKeyframes = addKeyframes ;
2023-10-02 10:05:20 +02:00
this . setKeyframes = setKeyframes ;
2023-09-24 18:39:52 +02:00
this . theatreObjects = theatreObjects ;
this . addObject = ( name , props , onValuesChange ) => {
const obj = this . sheet . object ( name , props ) ;
const listener = obj . onValuesChange ( onValuesChange ) ;
theatreObjects [ name ] = {
obj ,
listener
} ;
return obj ;
} ;
this . changeObject = ( name , props ) => {
this . sheet . object ( name , props , {
reconfigure : true
} ) ;
} ;
this . removeObject = ( name ) => {
// detach listener
// yeah, it looks weird, but this is how it is described
// https://www.theatrejs.com/docs/latest/manual/objects#detaching-objects
theatreObjects [ name ] . listener ( ) ;
// make sure object is DELETED (or is it?)
studio . transaction ( ( { unset } ) => unset ( theatreObjects [ name ] . obj . props ) )
// detach object
this . sheet . detachObject ( name ) ;
// make sure object is DELETED (possibly is)
tp . studio . transaction ( ( api ) => {
api . _ _experimental _forgetObject ( theatreObjects [ name ] . obj ) ;
} ) ;
// remove object from objects list
delete theatreObjects [ name ] ;
} ;
2023-10-07 12:21:36 +02:00
this . isSequenced = isSequenced ;
this . getSequenceButton = getSequenceButton ;
2023-09-24 18:39:52 +02:00
this . getSequencePanelLeft = getSequencePanelLeft ;
this . getPanel = getPanel ;
this . getPanelPropTitle = getPanelPropTitle ;
this . getPanelPropContainer = getPanelPropContainer ;
this . setPanelClasses = setPanelClasses ;
this . findInjectPanels = ( ) => {
const id = project . address . projectId ;
{ // hierarchy panel
const panel = tp . shadowRoot . querySelector ( ` .layerMover ${ id } ` ) ;
if ( panel !== null ) {
if ( panel . querySelector ( 'addLayerButton' ) !== null ) {
panel . querySelector ( 'addLayerButton' ) . remove ( ) ;
}
const addLayerButton = document . createElement ( 'div' ) ;
addLayerButton . classList . add ( 'addLayerButton' ) ;
addLayerButton . innerHTML = ` <img src="/web/assets/add_layer.svg" /> ` ;
addLayerButton . addEventListener ( 'click' , ( clickEvent ) => {
window . addLayer ( ) ;
} ) ;
panel . append ( addLayerButton ) ;
}
}
} ;
this . addShadowCss = ( ) => {
if ( this . shadowRoot . querySelector ( ` #tpShadowCss ` ) === null ) {
let style = document . createElement ( "style" ) ;
style . setAttribute ( 'id' , 'tpShadowCss' ) ;
// ASYA: here you can adjust css
style . textContent = `
. alignButtons , . textAlignButtons {
flex - direction : row ;
padding : 10 px 0 px ! important ;
justify - content : center ;
}
. word {
margin - right : 10 px ;
}
. letter {
transition : 0.5 s font - variation - settings ;
}
@ font - face {
font - family : 'vtVF' ;
src : url ( "/web/fonts/vtVF.ttf" ) format ( "TrueType" ) ;
}
. vtTitle {
font - family : 'vtVF' ;
font - size : 3 em ;
font - variation - settings : "wght" 0 , "wdth" 0 , "opsz" 0 ;
height : 50 px ;
width : 100 % ;
position : inherit ;
display : flex ;
justify - content : center ;
align - items : center ;
}
# panel ,
. panel {
display : flex ;
flex - direction : column ;
}
. panel > div {
order : 1 ;
}
. panel > . panelWrapperMom {
order : 2 ;
}
. panel > . bottomButtonsContainer {
order : 3 ;
}
. panelWrapper {
}
. panelControlsWrapper {
grid - template - columns : 1 fr 1 fr 1 fr 1 fr 1 fr 1 fr ;
grid - row - start : 2 ;
grid - column - start : 1 ;
grid - column - end : 7 ;
display : flex ;
flex - wrap : wrap ;
}
. xWrapper , . yWrapper , . fontFamilyWrapper , . fontFamilyWrapper , . rotationWrapper , . letterDelayWrapper , . transformOriginWrapper , . widthWrapper , . fontSizeWrapper , . letterSpacingWrapper , . lineHeighWrapper , . textWrapper , . colorWrapper , . mirror _xWrapper , . mirror _yWrapper , . mirror _xyWrapper , . mirror _x _distanceWrapper , . mirror _y _distanceWrapper , . alignButtons , . textAlignButtons {
border : none ;
padding : 0 px ;
display : flex ;
width : 100 % ;
}
. textWrappingButton {
margin - left : 30 px ;
}
. textWrappingButton . active {
background : white ;
}
. alignButtonsVertical {
padding - bottom : 10 px ;
}
. alignButtons {
display : flex ;
width : 50 % ;
padding - top : 10 px ;
padding - bottom : 10 px ;
}
. xWrapper {
}
. yWrapper {
}
. mirror _xWrapper {
}
. mirror _xWrapper input , . mirror _yWrapper input , . mirror _xyWrapper input {
margin : 0 px 10 px ;
}
label {
color : # ea2333 ;
margin - left : 10 px ;
}
input [ type = checkbox ] {
position : absolute ;
width : 27 px ;
opacity : 0 ;
}
input [ type = checkbox ] : checked + label {
color : # 1 cba94 ;
}
input [ type = checkbox ] + label : : after {
content : ' OFF' ;
}
input [ type = checkbox ] : checked + label : : after {
content : ' ON' ;
}
. mirror _yWrapper {
}
. mirror _xyWrapper {
}
. mirror _x _distanceWrapper {
}
. mirror _y _distanceWrapper {
}
. fontFamilyWrapper {
}
. rotationWrapper {
border - top : 1 px dashed # 91919177 ;
padding - top : 10 px ;
}
. transformOriginWrapper {
}
. widthWrapper {
}
. heightWrapper {
display : none ;
}
. fontSizeWrapper {
}
. letterSpacingWrapper {
}
. letterDelayWrapper {
}
. lineHeighWrapper {
}
. textWrapper {
}
. colorWrapper {
border - bottom : 1 px dashed # 91919177 ;
border - top : 1 px dashed # 91919177 ;
margin - bottom : 10 px ;
padding - bottom : 10 px ;
padding - top : 10 px ;
}
span . icon {
font - family : 'VariableIcons' ;
font - size : 3 em ;
margin : 2 px 5 px ;
line - height : 0.2 em ;
box - sizing : border - box ;
}
. main _panel _button , . vte _button {
background - color : white ;
font - size : 1.15 em ;
padding : 5 px 5 px 4 px 5 px ;
margin : 10 px ;
display : flex ;
flex - grow : 1 ;
justify - content : center ;
border - radius : 10 px ;
border : none ;
padding : 10 px ;
text - transform : uppercase ;
width : calc ( 100 % - 20 px ) ;
box - sizing : border - box ;
cursor : pointer ;
font - variation - settings : 'wght' 750 , 'wdth' 100 ;
}
. main _panel _button : hover , . vte _button : hover {
color : white ;
background - color : black ;
}
. removeButtonContainer {
display : flex ;
padding - right : 10 px ;
padding - bottom : 10 px ;
}
. alignButtons img ,
. textAlignButtons img {
height : 19 px ;
cursor : pointer ;
margin : 5 px 5 px 2 px 5 px ;
}
li . layerMover div {
}
li . layerMover div . selected {
background : rgba ( 255 , 255 , 255 , 1 ) ;
}
li . layerMover div . selected svg circle {
fill : # ea2333 ;
}
. letterDelaysContWrapper {
display : flex ;
flex - direction : column ;
width : 100 % ;
padding - bottom : 10 px ;
}
. letterDelaysContWrapper > * , . fontVariationAxesContWrapper > * {
margin - left : calc ( var ( -- left - pad ) * ( var ( -- depth ) - 1 ) ) ;
}
. fontVariationAxesContWrapper {
display : flex ;
flex - direction : column ;
width : 100 % ;
padding - bottom : 10 px ;
margin - bottom : 10 px ;
border - bottom : 1 px dashed # 91919177 ;
border - top : 1 px dashed # 91919177 ;
margin - top : 10 px ;
}
. moveLayerButton {
}
. removeLayerButton img ,
. duplicateLayerButton img ,
. addLayerButton img ,
. moveLayerButton img {
height : 10 px ;
cursor : pointer ;
}
. propTitleStuff {
color : red ;
}
. addLayerButton {
width : calc ( 100 % - 40 px ) ;
display : flex ;
align - items : center ;
justify - content : center ;
/* padding-left: 15px; */
align - self : end ;
margin - top : 5 px ;
}
2023-09-27 13:10:28 +02:00
. audioButton {
width : 20 px ;
2023-09-28 16:41:11 +02:00
margin : 2 px ;
2023-09-27 13:10:28 +02:00
}
. audioButton . active {
background : green ;
}
2023-09-28 16:41:11 +02:00
. recordButton {
width : 20 px ;
margin : 2 px ;
}
. recordButton . active {
background : green ;
}
2023-09-24 18:39:52 +02:00
` ;
this . shadowRoot . appendChild ( style ) ;
}
} ;
const waitingForShadowRoot = ( callback ) => {
let studioRoot = document . getElementById ( 'theatrejs-studio-root' ) ;
if ( studioRoot !== null && studioRoot . shadowRoot !== null ) {
callback ( ) ;
} else {
setTimeout ( ( ) => {
waitingForShadowRoot ( callback ) ;
} , 10 ) ;
}
} ;
this . addUserFont = ( previousName = "" , filePath = "" ) => {
return new Promise ( ( resolve ) => {
const fileName = filePath . split ( "/" ) . reverse ( ) . shift ( ) ; // basename
let uploadFontButton = document . createElement ( 'div' ) ;
let uploadFontButtonContainer = document . createElement ( 'div' ) ;
uploadFontButton . classList . add ( 'upload_font_button' ) ;
uploadFontButtonContainer . classList . add ( 'upload_font_button_container' ) ;
uploadFontButton . innerHTML = "upload font " +
( previousName === "" ? "" : ` ( ${ previousName } ) ` ) ;
uploadFontButton . style . cursor = 'pointer' ;
uploadFontButtonContainer . append ( uploadFontButton ) ;
document . getElementById ( 'body' ) . append ( uploadFontButtonContainer ) ;
uploadFontButton . addEventListener ( 'click' , ( ) => {
uploadFontButtonContainer . remove ( ) ;
uploadFile ( 'font' )
. then ( ( fontFile ) => {
if ( fileName !== "" ) {
fontFile . name = fileName ;
}
moduleFS
. save ( fontFile )
. then ( ( ) => {
resolve ( fontFile ) ;
} )
} ) ;
} ) ;
} ) ;
} ;
this . possiblyAskForFontsPromise = ( vt _project ) => {
return new Promise ( ( resolve ) => {
if ( vt _project . fontsHashMap !== false ) {
this . possiblyAskForFonts ( vt _project , resolve ) ;
} else {
resolve ( vt _project ) ;
}
} ) ;
} ;
this . projectUsesFont = ( vt _project , hash ) => {
const layerKeys = Object . keys ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject ) ;
for ( let l = 0 ; l < layerKeys . length ; l ++ ) {
if ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject [ layerKeys [ l ] ] . fontFamily === hash ) {
return true ;
} else {
//console.log(hash, "not used by ", vt_project);
}
}
return false ;
} ;
this . possiblyAskForFonts = ( vt _project , resolve , ignoreThese = [ ] ) => {
const availables = listAvailableFontsAndAxes ( ) ;
const hashes = Object . keys ( vt _project . fontsHashMap ) ;
let isRecursive = false ;
for ( let h = 0 ; h < hashes . length ; h ++ ) {
const hash = hashes [ h ] ;
if ( ignoreThese . indexOf ( hash ) >= 0 ) {
continue ;
}
const fontInfo = vt _project . fontsHashMap [ hash ] ;
let found = false ;
for ( let i = 0 ; i < availables . length ; i ++ ) {
if ( availables [ i ] . fontPath === fontInfo . fontPath ) {
found = true ;
}
}
if ( ! found ) {
const used = this . projectUsesFont ( vt _project , hash ) ;
let satisfied = false ;
for ( let a = 0 ; a < availables . length ; a ++ ) {
if ( fontInfo . fontName === availables [ a ] . fontName ) {
satisfied = true ;
const uploadedPath = availables [ a ] . fontPath ;
const uploadedHash = hashFromString ( uploadedPath ) ;
delete vt _project . fontsHashMap [ hash ] ; // will be generated? or maybe not
vt _project . fontsHashMap [ uploadedHash ] = {
fontName : availables [ a ] . fontName ,
fontPath : availables [ a ] . fontPath ,
axes : availables [ a ] . axes ,
} ;
const layerKeys = Object . keys ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject ) ;
for ( let l = 0 ; l < layerKeys . length ; l ++ ) {
if ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject [ layerKeys [ l ] ] . fontFamily === hash ) {
vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject [ layerKeys [ l ] ] . fontFamily = uploadedHash ;
}
}
break ;
}
}
if ( ! satisfied && used && confirm ( ` font ${ fontInfo . fontName } not found. \n ` +
` do you want to upload it (or a substitute) now? \n ` +
` Otherwise it will be later substituted. \n \n ` +
` If you click okay, click on the upcoming button ` ) ) {
isRecursive = true ;
this . addUserFont ( fontInfo . fontName ) . then ( ( uploadedFile ) => {
const uploadedPath = ` ${ config . fs . idbfsFontDir } / ${ uploadedFile . name } ` ;
const uploadedHash = hashFromString ( uploadedPath ) ;
delete vt _project . fontsHashMap [ hash ] ; // will be generated?
const layerKeys = Object . keys ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject ) ;
for ( let l = 0 ; l < layerKeys . length ; l ++ ) {
if ( vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject [ layerKeys [ l ] ] . fontFamily === hash ) {
vt _project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject [ layerKeys [ l ] ] . fontFamily = uploadedHash ;
}
}
this . possiblyAskForFonts ( vt _project , resolve , ignoreThese ) ;
} ) ;
break ;
} else if ( satisfied ) {
isRecursive = true ;
this . possiblyAskForFonts ( vt _project , resolve , ignoreThese ) ;
} else {
ignoreThese . push ( hash ) ;
}
}
}
if ( ! isRecursive ) {
resolve ( vt _project ) ;
}
} ;
this . variableTime2theatre = ( vt _project ) => {
return vt _project . theatre ;
} ;
this . theatre2variableTime = ( theatre _project , vt _params ) => {
vt _params . theatre = theatre _project ;
vt _params . theatre . revisionHistory = [ ] ;
return vt _params ;
} ;
this . listProjects = ( ) => {
const projectIds = [ ] ;
for ( let i = 0 ; i < localStorage . length ; i ++ ) {
let key = localStorage . key ( i ) ;
if ( key . indexOf ( config . projects . savePrefix ) === 0 ) {
const projectId = key . replace ( config . projects . savePrefix , '' ) ;
projectIds . push ( projectId ) ;
}
}
return projectIds ;
} ;
this . uploadProject = ( reeeload = false ) => {
uploadFile ( ) . then ( ( vt _project ) => {
if ( vt _project . type === 'application/zip' ) {
moduleFS . save ( vt _project ) . then ( ( fileLocation ) => {
const jsonString = Module . importProjectAsZip ( fileLocation , ` ${ config . fs . idbfsTmpDir } /outdir ` ) ;
const json = JSON . parse ( jsonString ) ;
if ( verifyVariableTimeProject ( json . project ) ) {
this . possiblyAskForFontsPromise ( json . project ) . then ( ( vt _project _u ) => {
vt _project = vt _project _u ;
window . debug _vt _project = vt _project ;
config . autoSave = false ;
this . saveProject ( vt _project . projectId , vt _project , true ) ;
if ( reeeload ) {
this . reloadToProject ( vt _project . projectId ) ;
}
} ) ;
} else {
console . log ( 'TheatrePlay::uploadProject' , 'could not verify project' ) ;
}
} ) ;
//Module.importProjectAsZip(vt_project.arrayBuffer, vt_project.fileSize, "debug_tmp");
} else if ( verifyVariableTimeProject ( vt _project ) ) {
this . possiblyAskForFontsPromise ( vt _project ) . then ( ( vt _project _u ) => {
vt _project = vt _project _u ;
//if (reeeload) {
//localStorage.clear();
//}
this . saveProject ( vt _project . projectId , vt _project , true ) ;
if ( reeeload ) {
this . reloadToProject ( vt _project . projectId ) ;
}
} ) ;
} else {
console . log ( 'TheatrePlay::uploadProject' , 'could not verify project' ) ;
}
} , ( error ) => {
console . error ( error ) ;
} ) ;
} ;
// we always save the currently opened project,
// but we may save it under a different id
this . createSaveFile = ( projectId = project . address . projectId ) => {
const currentProjectId = project . address . projectId ;
const fontsHashMap = window . getLayers ( ) . length > 0 ? window . getLayers ( ) [ 0 ] . fontsHashMap : false ;
const vt _params = {
variable _time _version : VARIABLE _TIME _VERSION ,
layerOrder : window . layerOrder . get ( ) ,
fontsHashMap ,
projectId ,
} ;
const theatre = tp . studio . createContentOfSaveFile ( currentProjectId ) ;
return this . theatre2variableTime ( theatre , vt _params ) ;
} ;
this . saveProject = ( projectId = project . address . projectId , vt _project = false , silent = true ) => {
if ( ! silent ) {
projectId = prompt ( "Please choose a name for your project" , projectId ) ;
}
if ( vt _project === false ) {
vt _project = this . createSaveFile ( ) ;
}
localStorage . setItem ( ` ${ config . projects . savePrefix } ${ projectId } ` , JSON . stringify ( vt _project ) ) ;
} ;
this . getProject = ( projectId , unwrapped = false ) => {
const json = localStorage . getItem ( ` ${ config . projects . savePrefix } ${ projectId } ` ) ;
if ( json === null ) {
return false
}
return unwrapped ? JSON . parse ( json ) : json ;
}
this . downloadProject = ( projectId = project . address . projectId , saveBeforeDownloading = true ) => {
if ( projectId === project . address . projectId ) {
let vt _project = this . createSaveFile ( ) ;
if ( saveBeforeDownloading ) {
this . saveProject ( projectId , vt _project , true ) ;
}
//downloadFile(JSON.stringify(vt_project), `${config.projects.savePrefix}${projectId}.json`, 'application/json');
Module . downloadProject ( projectId , JSON . stringify ( vt _project ) ) ;
} else {
const p = this . getProject ( projectId ) ;
if ( p !== false ) {
//downloadFile(p, `${config.projects.savePrefix}${projectId}.json`, 'application/json');
Module . downloadProject ( projectId , JSON . stringify ( p ) ) ;
} else {
console . error ( 'TheatrePlay::downloadProject' , ` cannot download project with id ${ projectId } , because it is neither current project or saved in localStorage ` ) ;
}
}
} ;
this . reloadToProject = ( projectId , hardReload = true ) => {
const p = this . getProject ( projectId , true ) ;
if ( p !== false ) {
localStorage . setItem ( 'currentProject' , projectId ) ;
if ( hardReload ) {
window . location . reload ( ) ;
} else {
const layers = getLayers ( ) ;
for ( let l = 0 ; l < layers . length ; l ++ ) {
const doSaveProject = false ;
console . log ( 'TheatrePlay::reloadToProject' , 'deleting layer' , layers [ l ] . id ( ) ) ;
deleteLayer ( layers [ l ] . id ( ) , doSaveProject ) ;
}
localStorage . removeItem ( 'theatre-0.4.persistent' ) ;
studio . initialize ( ) ;
this . initProject ( projectId , p . theatre ) . then ( ( ) => {
this . loadProject ( projectId , p ) ;
} ) ;
}
} else {
console . error ( 'TheatrePlay::reloadToProject' , ` no saved project with id ${ projectId } ` ) ;
}
} ;
this . initProject = ( projectId , projectJson = false ) => {
return new Promise ( ( resolve ) => {
project = projectJson === false ? core . getProject ( projectId ) : core . getProject ( projectId , {
state : projectJson
} ) ;
window . setLoadingTask ( 'setting up animation' , 10 ) ;
project . ready . then ( ( ) => {
this . sheet = project . sheet ( 'mainSheet' ) ;
window . setLoadingTask ( 'setting up animation' , 30 ) ;
waitingForShadowRoot ( ( ) => {
this . shadowRoot = document . getElementById ( 'theatrejs-studio-root' ) . shadowRoot ;
this . addShadowCss ( ) ;
resolve ( ) ;
window . setLoadingTask ( 'setting up animation' , 42 ) ;
} ) ;
localStorage . setItem ( 'currentProject' , project . address . projectId ) ;
} ) ;
} ) ;
} ;
this . duration = 0 ;
this . loadProject = ( projectId = false , project = false ) => {
console . log ( 'TheatrePlay::loadProject' ) ;
this . isProjectLoaded = false ;
return new Promise ( ( resolve ) => {
if ( projectId === false ) {
projectId = this . sheet . project . address . projectId ;
}
if ( project === false ) {
project = this . getProject ( projectId , true ) ;
}
if ( project !== false ) { // if project is not saved yet, create new
// get all objects
const objects = project
. theatre
. sheetsById
. mainSheet
. staticOverrides
. byObject ;
// load artboard
let artboardValues = objects [ 'artboard' ] ;
// for backward compatibility rename old values
if ( artboardValues . hasOwnProperty ( 'scale' ) ) {
artboardValues [ 'zoom' ] = artboardValues [ 'scale' ] ;
delete artboardValues [ 'scale' ] ;
}
// for backward compatibility merge with current values
const defaultArtboardValues = window . getArtboard ( ) . theatreObject . value ;
const artboardProps = { ... defaultArtboardValues , ... artboardValues } ;
studio . transaction ( ( {
set
} ) => {
set ( window . getArtboard ( ) . theatreObject . props , artboardProps ) ;
} ) ;
// load layers
const layerPromises = [ ] ;
Object . keys ( objects )
. filter ( ( e ) => e . indexOf ( 'layer-' ) === 0 )
. forEach ( ( layerId ) => {
window . project _fontsHashMap = project . fontsHashMap ;
layerPromises . push ( window . addExistingLayer ( layerId , objects [ layerId ] ) ) ;
} ) ;
Promise . all ( layerPromises ) . then ( ( ) => {
window . layerOrder . set ( project . layerOrder ) ;
this . duration = this . core . val ( this . sheet . sequence . pointer . length ) ;
if ( project . layerOrder . length > 0 ) {
getLayers ( ) . forEach ( ( layer ) => {
if ( layer . id ( ) === project . layerOrder [ project . layerOrder . length - 1 ] ) {
layer . select ( ) ;
}
} ) ;
}
resolve ( ) ;
this . isProjectLoaded = true ;
} ) ;
} else {
if ( getLayers ( ) . length === 0 && config . layer . autoCreateFirstLayer ) {
getFontsAndAxes ( ) . then ( ( newFontsAndAxes ) => {
const autoInitLayer = false ;
const layer = addLayer ( autoInitLayer ) ;
layer . init ( ) . then ( ( ) => {
layer . select ( ) ;
this . duration = this . core . val ( this . sheet . sequence . pointer . length ) ;
resolve ( ) ;
this . isProjectLoaded = true ;
} ) ;
} ) ;
} else {
this . duration = this . core . val ( this . sheet . sequence . pointer . length ) ;
resolve ( ) ;
this . isProjectLoaded = true ;
}
}
} ) ;
} ;
this . startNewProject = ( ) => {
if ( confirm ( 'Starting a new project will clear all project data and spin up a blank project from scratch. Uploaded assets (a.k.a. fonts) will stay. Is this fine?' ) ) {
setTimeout ( ( ) => {
window . localStorage . clear ( ) ;
window . location . reload ( ) ;
} , 100 ) ;
}
} ;
this . isInitialized = false ;
this . isProjectLoaded = false ;
this . init = ( ) => {
return new Promise ( ( resolve ) => {
console . log ( 'TheatrePlay' , 'init' ) ;
localStorage . removeItem ( 'theatre-0.4.persistent' ) ;
window . setLoadingTask ( 'setting up animation' , 0 ) ;
studio . initialize ( ) ;
const currentProjectId = localStorage . getItem ( 'currentProject' ) ;
if ( currentProjectId === null ||
this . listProjects ( ) . indexOf ( currentProjectId ) < 0 ) {
this . initProject ( 'variable-time' ) . then ( ( ) => {
this . findInjectPanels ( ) ;
window . setLoadingTask ( 'setting up animation' , 60 ) ;
this . isInitialized = true ;
resolve ( ) ;
} ) ;
} else {
const currentProject = this . getProject ( currentProjectId , true ) ;
this . initProject ( currentProjectId , currentProject . theatre ) . then ( ( ) => {
this . findInjectPanels ( ) ;
window . setLoadingTask ( 'setting up animation' , 60 ) ;
this . isInitialized = true ;
resolve ( ) ;
} ) ;
}
} ) ;
}
this . connectModuleCallbacks = ( ) => {
console . log ( 'TheatrePlay::connectModuleCallbacks' ) ;
//onChange(sequence.pointer.length, (len) => {
//console.log('Length of the sequence changed to:', len)
//})
if ( config . timeline . rolloverReset ) {
core . onChange ( this . sheet . sequence . pointer . position , ( position ) => {
if ( position < config . timeline . rolloverThreshold _s ) {
Module . resetLetterDelay ( ) ;
}
} ) ;
}
core . onChange ( this . sheet . sequence . pointer . playing , ( playing ) => {
Module . setPlaying ( playing ) ;
} ) ;
} ;
this . studio = studio ;
this . core = core ;
//action
if ( autoInit ) {
this . init ( ) ;
}
} ;
export {
TheatrePlay
} ;