@hyperframes/studio 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/index-ByOno-cZ.js +290 -0
- package/dist/assets/index-DHr9yo58.css +1 -0
- package/dist/index.html +2 -2
- package/package.json +15 -14
- package/src/index.ts +1 -0
- package/src/player/components/Player.tsx +59 -98
- package/src/player/hooks/useTimelinePlayer.ts +48 -11
- package/src/player/index.ts +1 -1
- package/src/player/store/playerStore.test.ts +3 -3
- package/LICENSE +0 -190
- package/dist/assets/index-B0VCLOXQ.js +0 -93
- package/dist/assets/index-BT9D8I7B.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media(min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media(min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media(min-width:1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media(min-width:1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media(min-width:1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.\!visible{visibility:visible!important}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.bottom-1{bottom:.25rem}.bottom-2{bottom:.5rem}.bottom-6{bottom:1.5rem}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.right-0{right:0}.right-3{right:.75rem}.top-0{top:0}.top-1{top:.25rem}.top-1\/2{top:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-\[1\]{z-index:1}.z-\[200\]{z-index:200}.z-\[2\]{z-index:2}.z-\[90\]{z-index:90}.z-\[91\]{z-index:91}.mx-1{margin-left:.25rem;margin-right:.25rem}.my-0\.5{margin-top:.125rem;margin-bottom:.125rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-0\.5{margin-bottom:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-1\.5{margin-left:.375rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[1080px\]{height:1080px}.h-\[3px\]{height:3px}.h-\[45px\]{height:45px}.h-\[5px\]{height:5px}.h-full{height:100%}.h-screen{height:100vh}.max-h-24{max-height:6rem}.max-h-\[70\%\]{max-height:70%}.max-h-\[80vh\]{max-height:80vh}.max-h-full{max-height:100%}.min-h-0{min-height:0px}.min-h-7{min-height:1.75rem}.min-h-8{min-height:2rem}.min-h-9{min-height:2.25rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-80{width:20rem}.w-\[160px\]{width:160px}.w-\[1920px\]{width:1920px}.w-full{width:100%}.w-px{width:1px}.w-screen{width:100vw}.min-w-0{min-width:0px}.min-w-7{min-width:1.75rem}.min-w-8{min-width:2rem}.min-w-9{min-width:2.25rem}.min-w-\[140px\]{min-width:140px}.min-w-\[160px\]{min-width:160px}.min-w-\[56px\]{min-width:56px}.min-w-\[72px\]{min-width:72px}.max-w-\[280px\]{max-width:280px}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-col-resize{cursor:col-resize}.cursor-crosshair{cursor:crosshair}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.cursor-row-resize{cursor:row-resize}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.resize-y{resize:vertical}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-none{border-style:none}.border-green-500\/30{border-color:#22c55e4d}.border-neutral-600{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.border-neutral-700{--tw-border-opacity: 1;border-color:rgb(64 64 64 / var(--tw-border-opacity, 1))}.border-neutral-700\/20{border-color:#40404033}.border-neutral-700\/40{border-color:#40404066}.border-neutral-700\/50{border-color:#40404080}.border-neutral-700\/60{border-color:#40404099}.border-neutral-800{--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.border-neutral-800\/30{border-color:#2626264d}.border-neutral-800\/40{border-color:#26262666}.border-neutral-800\/50{border-color:#26262680}.border-neutral-800\/60{border-color:#26262699}.border-red-500{--tw-border-opacity: 1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-red-700\/50{border-color:#b91c1c80}.border-studio-accent{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.border-studio-accent\/25{border-color:#3ce6ac40}.border-studio-accent\/30{border-color:#3ce6ac4d}.border-studio-accent\/50{border-color:#3ce6ac80}.border-studio-accent\/60{border-color:#3ce6ac99}.border-transparent{border-color:transparent}.bg-\[\#0a0a0b\]{--tw-bg-opacity: 1;background-color:rgb(10 10 11 / var(--tw-bg-opacity, 1))}.bg-\[\#3CE6AC\]\/10{background-color:#3ce6ac1a}.bg-\[\#3CE6AC\]\/5{background-color:#3ce6ac0d}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-green-500\/20{background-color:#22c55e33}.bg-green-600{--tw-bg-opacity: 1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-neutral-600{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity, 1))}.bg-neutral-600\/60{background-color:#52525299}.bg-neutral-700{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity, 1))}.bg-neutral-700\/40{background-color:#40404066}.bg-neutral-800{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.bg-neutral-800\/50{background-color:#26262680}.bg-neutral-800\/60{background-color:#26262699}.bg-neutral-900{--tw-bg-opacity: 1;background-color:rgb(23 23 23 / var(--tw-bg-opacity, 1))}.bg-neutral-900\/50{background-color:#17171780}.bg-neutral-950{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.bg-red-900\/60{background-color:#7f1d1d99}.bg-red-900\/90{background-color:#7f1d1de6}.bg-red-950\/30{background-color:#450a0a4d}.bg-studio-accent{--tw-bg-opacity: 1;background-color:rgb(60 230 172 / var(--tw-bg-opacity, 1))}.bg-studio-accent\/10{background-color:#3ce6ac1a}.bg-studio-accent\/15{background-color:#3ce6ac26}.bg-studio-accent\/20{background-color:#3ce6ac33}.bg-studio-accent\/\[0\.03\]{background-color:#3ce6ac08}.bg-studio-accent\/\[0\.05\]{background-color:#3ce6ac0d}.bg-studio-accent\/\[0\.06\]{background-color:#3ce6ac0f}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-0\.5{padding-bottom:.125rem}.pb-3{padding-bottom:.75rem}.pr-1\.5{padding-right:.375rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-wider{letter-spacing:.05em}.text-\[\#09090B\]{--tw-text-opacity: 1;color:rgb(9 9 11 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-neutral-100{--tw-text-opacity: 1;color:rgb(245 245 245 / var(--tw-text-opacity, 1))}.text-neutral-200{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.text-neutral-300{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity, 1))}.text-neutral-400{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.text-neutral-500{--tw-text-opacity: 1;color:rgb(115 115 115 / var(--tw-text-opacity, 1))}.text-neutral-600{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.text-neutral-700{--tw-text-opacity: 1;color:rgb(64 64 64 / var(--tw-text-opacity, 1))}.text-neutral-950{--tw-text-opacity: 1;color:rgb(10 10 10 / var(--tw-text-opacity, 1))}.text-purple-400{--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-studio-accent{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.text-studio-accent\/50{color:#3ce6ac80}.text-studio-accent\/60{color:#3ce6ac99}.text-studio-accent\/80{color:#3ce6accc}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.line-through{text-decoration-line:line-through}.accent-studio-accent{accent-color:#3CE6AC}.opacity-25{opacity:.25}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-black\/40{--tw-shadow-color: rgb(0 0 0 / .4);--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.outline-1{outline-width:1px}.-outline-offset-1{outline-offset:-1px}.outline-\[\#3CE6AC\]\/30{outline-color:#3ce6ac4d}.outline-\[\#3CE6AC\]\/40{outline-color:#3ce6ac66}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-2{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-studio-accent{--tw-ring-opacity: 1;--tw-ring-color: rgb(60 230 172 / var(--tw-ring-opacity, 1))}.ring-white\/50{--tw-ring-color: rgb(255 255 255 / .5)}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / .1)) drop-shadow(0 1px 1px rgb(0 0 0 / .06));filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert: invert(100%);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}body{margin:0;padding:0;background:#0a0a0a;color:#e5e5e5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;overflow:hidden}#root{width:100vw;height:100vh}.cm-editor{height:100%;font-size:13px}.cm-editor .cm-scroller{font-family:JetBrains Mono,Fira Code,SF Mono,monospace}.cm-editor.cm-focused{outline:none}.placeholder\:text-neutral-600::-moz-placeholder{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.placeholder\:text-neutral-600::placeholder{--tw-text-opacity: 1;color:rgb(82 82 82 / var(--tw-text-opacity, 1))}.last\:border-0:last-child{border-width:0px}.hover\:border-neutral-600:hover{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.hover\:border-studio-accent\/50:hover{border-color:#3ce6ac80}.hover\:bg-neutral-200:hover{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-600:hover{--tw-bg-opacity: 1;background-color:rgb(82 82 82 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-800:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-neutral-800\/30:hover{background-color:#2626264d}.hover\:bg-neutral-800\/50:hover{background-color:#26262680}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.hover\:bg-red-600:hover{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-red-800\/60:hover{background-color:#991b1b99}.hover\:bg-red-900\/30:hover{background-color:#7f1d1d4d}.hover\:bg-studio-accent:hover{--tw-bg-opacity: 1;background-color:rgb(60 230 172 / var(--tw-bg-opacity, 1))}.hover\:bg-studio-accent\/25:hover{background-color:#3ce6ac40}.hover\:bg-studio-accent\/80:hover{background-color:#3ce6accc}.hover\:text-amber-300:hover{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.hover\:text-green-400:hover{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.hover\:text-neutral-200:hover{--tw-text-opacity: 1;color:rgb(229 229 229 / var(--tw-text-opacity, 1))}.hover\:text-neutral-300:hover{--tw-text-opacity: 1;color:rgb(212 212 212 / var(--tw-text-opacity, 1))}.hover\:text-neutral-400:hover{--tw-text-opacity: 1;color:rgb(163 163 163 / var(--tw-text-opacity, 1))}.hover\:text-red-400:hover{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.hover\:text-studio-accent:hover{--tw-text-opacity: 1;color:rgb(60 230 172 / var(--tw-text-opacity, 1))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.hover\:ring-1:hover{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.hover\:ring-white\/30:hover{--tw-ring-color: rgb(255 255 255 / .3)}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-\[\#3CE6AC\]:focus{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.focus\:border-neutral-600:focus{--tw-border-opacity: 1;border-color:rgb(82 82 82 / var(--tw-border-opacity, 1))}.focus\:border-studio-accent:focus{--tw-border-opacity: 1;border-color:rgb(60 230 172 / var(--tw-border-opacity, 1))}.focus\:border-studio-accent\/40:focus{border-color:#3ce6ac66}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.active\:scale-\[0\.97\]:active{--tw-scale-x: .97;--tw-scale-y: .97;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.98\]:active{--tw-scale-x: .98;--tw-scale-y: .98;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:bg-studio-accent\/80:active{background-color:#3ce6accc}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-125{--tw-scale-x: 1.25;--tw-scale-y: 1.25;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}
|
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>HyperFrames Studio</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-ByOno-cZ.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DHr9yo58.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/studio",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -18,6 +18,13 @@
|
|
|
18
18
|
".": "./src/index.ts",
|
|
19
19
|
"./tailwind-preset": "./src/styles/tailwind-preset.ts"
|
|
20
20
|
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "vite",
|
|
23
|
+
"build": "vite build",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest"
|
|
27
|
+
},
|
|
21
28
|
"dependencies": {
|
|
22
29
|
"@codemirror/autocomplete": "^6.20.1",
|
|
23
30
|
"@codemirror/commands": "^6.10.3",
|
|
@@ -29,12 +36,14 @@
|
|
|
29
36
|
"@codemirror/state": "^6.6.0",
|
|
30
37
|
"@codemirror/theme-one-dark": "^6.1.2",
|
|
31
38
|
"@codemirror/view": "^6.40.0",
|
|
39
|
+
"@hyperframes/core": "workspace:*",
|
|
40
|
+
"@hyperframes/player": "workspace:*",
|
|
32
41
|
"@phosphor-icons/react": "^2.1.10",
|
|
33
42
|
"codemirror": "^6.0.1",
|
|
34
|
-
"motion": "^12.38.0"
|
|
35
|
-
"@hyperframes/core": "0.2.4"
|
|
43
|
+
"motion": "^12.38.0"
|
|
36
44
|
},
|
|
37
45
|
"devDependencies": {
|
|
46
|
+
"@hyperframes/producer": "workspace:*",
|
|
38
47
|
"@types/react": "^19.0.0",
|
|
39
48
|
"@types/react-dom": "^19.0.0",
|
|
40
49
|
"@vitejs/plugin-react": "^4.0.0",
|
|
@@ -43,21 +52,13 @@
|
|
|
43
52
|
"puppeteer-core": "^24.40.0",
|
|
44
53
|
"tailwindcss": "^3.4.0",
|
|
45
54
|
"typescript": "^5.0.0",
|
|
46
|
-
"vite": "^
|
|
55
|
+
"vite": "^6.4.2",
|
|
47
56
|
"vitest": "^3.2.4",
|
|
48
|
-
"zustand": "^5.0.0"
|
|
49
|
-
"@hyperframes/producer": "0.2.4"
|
|
57
|
+
"zustand": "^5.0.0"
|
|
50
58
|
},
|
|
51
59
|
"peerDependencies": {
|
|
52
60
|
"react": "^18.0.0 || ^19.0.0",
|
|
53
61
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
54
62
|
"zustand": "^4.0.0 || ^5.0.0"
|
|
55
|
-
},
|
|
56
|
-
"scripts": {
|
|
57
|
-
"dev": "vite",
|
|
58
|
-
"build": "vite build",
|
|
59
|
-
"typecheck": "tsc --noEmit",
|
|
60
|
-
"test": "vitest run",
|
|
61
|
-
"test:watch": "vitest"
|
|
62
63
|
}
|
|
63
|
-
}
|
|
64
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { forwardRef, useRef
|
|
1
|
+
import { forwardRef, useRef } from "react";
|
|
2
2
|
import { useMountEffect } from "../../hooks/useMountEffect";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const NATIVE_H = 1080;
|
|
3
|
+
import type { HyperframesPlayer } from "@hyperframes/player";
|
|
4
|
+
import "@hyperframes/player"; // registers <hyperframes-player> custom element
|
|
6
5
|
|
|
7
6
|
interface PlayerProps {
|
|
8
7
|
projectId?: string;
|
|
@@ -11,118 +10,80 @@ interface PlayerProps {
|
|
|
11
10
|
portrait?: boolean;
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Renders a composition preview using the <hyperframes-player> web component.
|
|
15
|
+
*
|
|
16
|
+
* The web component handles iframe scaling, dimension detection, and
|
|
17
|
+
* ResizeObserver internally. This wrapper bridges its inner iframe to the
|
|
18
|
+
* forwarded ref so useTimelinePlayer can access it for clip manifest parsing,
|
|
19
|
+
* timeline probing, and DOM inspection.
|
|
20
|
+
*/
|
|
14
21
|
export const Player = forwardRef<HTMLIFrameElement, PlayerProps>(
|
|
15
22
|
({ projectId, directUrl, onLoad, portrait }, ref) => {
|
|
16
23
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
17
|
-
const [scale, setScale] = useState(1);
|
|
18
|
-
const dimsRef = useRef({
|
|
19
|
-
w: portrait ? NATIVE_H : NATIVE_W,
|
|
20
|
-
h: portrait ? NATIVE_W : NATIVE_H,
|
|
21
|
-
});
|
|
22
|
-
const [dims, setDims] = useState(dimsRef.current);
|
|
23
24
|
const loadCountRef = useRef(0);
|
|
24
25
|
|
|
25
|
-
const updateScale = useCallback(() => {
|
|
26
|
-
const el = containerRef.current;
|
|
27
|
-
if (!el) return;
|
|
28
|
-
const rect = el.getBoundingClientRect();
|
|
29
|
-
const d = dimsRef.current;
|
|
30
|
-
setScale(Math.min(rect.width / d.w, rect.height / d.h));
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
26
|
useMountEffect(() => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (containerRef.current) ro.observe(containerRef.current);
|
|
27
|
+
const container = containerRef.current;
|
|
28
|
+
if (!container) return;
|
|
37
29
|
|
|
38
|
-
//
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
dimsRef.current = { w: data.width, h: data.height };
|
|
49
|
-
setDims(dimsRef.current);
|
|
50
|
-
updateScale();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
window.addEventListener("message", handleMessage);
|
|
30
|
+
// Create the web component imperatively to avoid JSX custom-element typing.
|
|
31
|
+
const player = document.createElement("hyperframes-player") as HyperframesPlayer;
|
|
32
|
+
const src = directUrl || `/api/projects/${projectId}/preview`;
|
|
33
|
+
player.setAttribute("src", src);
|
|
34
|
+
player.setAttribute("width", String(portrait ? 1080 : 1920));
|
|
35
|
+
player.setAttribute("height", String(portrait ? 1920 : 1080));
|
|
36
|
+
player.style.width = "100%";
|
|
37
|
+
player.style.height = "100%";
|
|
38
|
+
player.style.display = "block";
|
|
39
|
+
container.appendChild(player);
|
|
55
40
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
41
|
+
// Bridge the inner iframe to the forwarded ref for useTimelinePlayer.
|
|
42
|
+
const iframe = player.iframeElement;
|
|
43
|
+
if (typeof ref === "function") {
|
|
44
|
+
ref(iframe);
|
|
45
|
+
} else if (ref) {
|
|
46
|
+
(ref as React.MutableRefObject<HTMLIFrameElement | null>).current = iframe;
|
|
47
|
+
}
|
|
61
48
|
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
// Prevent the web component's built-in click-to-toggle behavior.
|
|
50
|
+
// The studio manages playback exclusively via useTimelinePlayer.
|
|
51
|
+
const preventToggle = (e: Event) => e.stopImmediatePropagation();
|
|
52
|
+
player.addEventListener("click", preventToggle, { capture: true });
|
|
64
53
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
dimsRef.current = { w: dw, h: dh };
|
|
76
|
-
setDims(dimsRef.current);
|
|
77
|
-
// Recalc scale with new dims
|
|
78
|
-
const el = containerRef.current;
|
|
79
|
-
if (el) {
|
|
80
|
-
const rect = el.getBoundingClientRect();
|
|
81
|
-
setScale(Math.min(rect.width / dw, rect.height / dh));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
54
|
+
// Forward the iframe's native load event to the studio's onIframeLoad.
|
|
55
|
+
const handleLoad = () => {
|
|
56
|
+
loadCountRef.current++;
|
|
57
|
+
// Reveal animation on reload (hot-reload, composition switch)
|
|
58
|
+
if (loadCountRef.current > 1) {
|
|
59
|
+
container.classList.remove("preview-revealing");
|
|
60
|
+
void container.offsetWidth;
|
|
61
|
+
container.classList.add("preview-revealing");
|
|
62
|
+
const onEnd = () => container.classList.remove("preview-revealing");
|
|
63
|
+
container.addEventListener("animationend", onEnd, { once: true });
|
|
85
64
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
65
|
+
onLoad();
|
|
66
|
+
};
|
|
67
|
+
iframe.addEventListener("load", handleLoad);
|
|
89
68
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
69
|
+
return () => {
|
|
70
|
+
iframe.removeEventListener("load", handleLoad);
|
|
71
|
+
player.removeEventListener("click", preventToggle, { capture: true });
|
|
72
|
+
container.removeChild(player);
|
|
73
|
+
// Clear the forwarded ref
|
|
74
|
+
if (typeof ref === "function") {
|
|
75
|
+
ref(null);
|
|
76
|
+
} else if (ref) {
|
|
77
|
+
(ref as React.MutableRefObject<HTMLIFrameElement | null>).current = null;
|
|
98
78
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
}, [onLoad, ref]);
|
|
79
|
+
};
|
|
80
|
+
});
|
|
102
81
|
|
|
103
82
|
return (
|
|
104
83
|
<div
|
|
105
84
|
ref={containerRef}
|
|
106
85
|
className="w-full h-full max-w-full max-h-full overflow-hidden bg-black flex items-center justify-center"
|
|
107
|
-
|
|
108
|
-
<iframe
|
|
109
|
-
ref={ref}
|
|
110
|
-
src={directUrl || `/api/projects/${projectId}/preview`}
|
|
111
|
-
onLoad={handleLoad}
|
|
112
|
-
sandbox="allow-scripts allow-same-origin"
|
|
113
|
-
allow="autoplay; fullscreen"
|
|
114
|
-
referrerPolicy="no-referrer"
|
|
115
|
-
title="Project Preview"
|
|
116
|
-
style={{
|
|
117
|
-
width: dims.w,
|
|
118
|
-
height: dims.h,
|
|
119
|
-
border: "none",
|
|
120
|
-
transform: `scale(${scale})`,
|
|
121
|
-
transformOrigin: "center center",
|
|
122
|
-
flexShrink: 0,
|
|
123
|
-
}}
|
|
124
|
-
/>
|
|
125
|
-
</div>
|
|
86
|
+
/>
|
|
126
87
|
);
|
|
127
88
|
},
|
|
128
89
|
);
|
|
@@ -186,8 +186,34 @@ function unmutePreviewMedia(iframe: HTMLIFrameElement | null): void {
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Resolve the underlying iframe from any host element. Supports:
|
|
191
|
+
* - Direct `<iframe>` element (most common — studio's own `Player.tsx`)
|
|
192
|
+
* - Custom elements (e.g. `<hyperframes-player>`) whose shadow DOM contains an iframe
|
|
193
|
+
* - Wrapper elements whose light DOM contains a descendant iframe
|
|
194
|
+
*
|
|
195
|
+
* Exported so web-component consumers can pre-resolve the iframe before
|
|
196
|
+
* assigning it to `iframeRef` returned by `useTimelinePlayer`. Returns `null`
|
|
197
|
+
* when the element has no associated iframe yet.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```tsx
|
|
201
|
+
* const { iframeRef } = useTimelinePlayer();
|
|
202
|
+
* const playerElRef = useRef<HyperframesPlayer>(null);
|
|
203
|
+
*
|
|
204
|
+
* useEffect(() => {
|
|
205
|
+
* iframeRef.current = resolveIframe(playerElRef.current);
|
|
206
|
+
* }, [iframeRef]);
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
export function resolveIframe(el: Element | null): HTMLIFrameElement | null {
|
|
210
|
+
if (!el) return null;
|
|
211
|
+
if (el instanceof HTMLIFrameElement) return el;
|
|
212
|
+
return el.shadowRoot?.querySelector("iframe") ?? el.querySelector("iframe") ?? null;
|
|
213
|
+
}
|
|
214
|
+
|
|
189
215
|
export function useTimelinePlayer() {
|
|
190
|
-
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
216
|
+
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
|
191
217
|
const rafRef = useRef<number>(0);
|
|
192
218
|
const probeIntervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);
|
|
193
219
|
const pendingSeekRef = useRef<number | null>(null);
|
|
@@ -200,7 +226,8 @@ export function useTimelinePlayer() {
|
|
|
200
226
|
|
|
201
227
|
const getAdapter = useCallback((): PlaybackAdapter | null => {
|
|
202
228
|
try {
|
|
203
|
-
const
|
|
229
|
+
const iframe = iframeRef.current;
|
|
230
|
+
const win = iframe?.contentWindow as IframeWindow | null;
|
|
204
231
|
if (!win) return null;
|
|
205
232
|
|
|
206
233
|
if (win.__player && typeof win.__player.play === "function") {
|
|
@@ -211,7 +238,17 @@ export function useTimelinePlayer() {
|
|
|
211
238
|
|
|
212
239
|
if (win.__timelines) {
|
|
213
240
|
const keys = Object.keys(win.__timelines);
|
|
214
|
-
if (keys.length > 0)
|
|
241
|
+
if (keys.length > 0) {
|
|
242
|
+
// Resolve the root composition id from the DOM — the outermost
|
|
243
|
+
// `[data-composition-id]` element is the master. Without this,
|
|
244
|
+
// Object.keys() order would let a sub-composition's timeline
|
|
245
|
+
// hijack play/pause/seek and the duration readout.
|
|
246
|
+
const rootId = iframe?.contentDocument
|
|
247
|
+
?.querySelector("[data-composition-id]")
|
|
248
|
+
?.getAttribute("data-composition-id");
|
|
249
|
+
const key = rootId && rootId in win.__timelines ? rootId : keys[keys.length - 1];
|
|
250
|
+
return wrapTimeline(win.__timelines[key]);
|
|
251
|
+
}
|
|
215
252
|
}
|
|
216
253
|
|
|
217
254
|
return null;
|
|
@@ -554,8 +591,9 @@ export function useTimelinePlayer() {
|
|
|
554
591
|
setIsPlaying(false);
|
|
555
592
|
|
|
556
593
|
try {
|
|
557
|
-
const
|
|
558
|
-
const
|
|
594
|
+
const iframe = iframeRef.current;
|
|
595
|
+
const doc = iframe?.contentDocument;
|
|
596
|
+
const iframeWin = iframe?.contentWindow as IframeWindow | null;
|
|
559
597
|
if (doc && iframeWin) {
|
|
560
598
|
normalizePreviewViewport(doc, iframeWin);
|
|
561
599
|
autoHealMissingCompositionIds(doc);
|
|
@@ -591,7 +629,7 @@ export function useTimelinePlayer() {
|
|
|
591
629
|
const rootId = rootComp.getAttribute("data-composition-id") || "composition";
|
|
592
630
|
// Derive compositionSrc from the iframe URL for thumbnail rendering.
|
|
593
631
|
// URL pattern: /api/projects/{id}/preview/comp/{path}
|
|
594
|
-
const iframeSrc =
|
|
632
|
+
const iframeSrc = iframe?.src || "";
|
|
595
633
|
const compPathMatch = iframeSrc.match(/\/preview\/comp\/(.+?)(?:\?|$)/);
|
|
596
634
|
const compositionSrc = compPathMatch
|
|
597
635
|
? decodeURIComponent(compPathMatch[1])
|
|
@@ -682,7 +720,8 @@ export function useTimelinePlayer() {
|
|
|
682
720
|
const handleMessage = (e: MessageEvent) => {
|
|
683
721
|
const data = e.data;
|
|
684
722
|
// Only process messages from the main preview iframe — ignore MediaPanel/ClipThumbnail iframes
|
|
685
|
-
|
|
723
|
+
const ourIframe = iframeRef.current;
|
|
724
|
+
if (e.source && ourIframe && e.source !== ourIframe.contentWindow) {
|
|
686
725
|
return;
|
|
687
726
|
}
|
|
688
727
|
// Also handle the runtime's state message which includes timeline data
|
|
@@ -690,8 +729,7 @@ export function useTimelinePlayer() {
|
|
|
690
729
|
// State message means the runtime is alive — check for elements
|
|
691
730
|
try {
|
|
692
731
|
if (usePlayerStore.getState().elements.length === 0) {
|
|
693
|
-
const
|
|
694
|
-
const iframeWin = iframe?.contentWindow as IframeWindow | null;
|
|
732
|
+
const iframeWin = ourIframe?.contentWindow as IframeWindow | null;
|
|
695
733
|
const manifest = iframeWin?.__clipManifest;
|
|
696
734
|
if (manifest && manifest.clips.length > 0) {
|
|
697
735
|
processTimelineMessageRef.current(manifest);
|
|
@@ -717,8 +755,7 @@ export function useTimelinePlayer() {
|
|
|
717
755
|
// If manifest produced 0 elements after filtering, try DOM fallback
|
|
718
756
|
if (usePlayerStore.getState().elements.length === 0) {
|
|
719
757
|
try {
|
|
720
|
-
const
|
|
721
|
-
const doc = iframe?.contentDocument;
|
|
758
|
+
const doc = ourIframe?.contentDocument;
|
|
722
759
|
const adapter = getAdapter();
|
|
723
760
|
if (doc && adapter) {
|
|
724
761
|
const els = parseTimelineFromDOM(doc, adapter.getDuration());
|
package/src/player/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { VideoThumbnail } from "./components/VideoThumbnail";
|
|
|
6
6
|
export { CompositionThumbnail } from "./components/CompositionThumbnail";
|
|
7
7
|
|
|
8
8
|
// Hooks
|
|
9
|
-
export { useTimelinePlayer } from "./hooks/useTimelinePlayer";
|
|
9
|
+
export { useTimelinePlayer, resolveIframe } from "./hooks/useTimelinePlayer";
|
|
10
10
|
|
|
11
11
|
// Store
|
|
12
12
|
export { usePlayerStore, liveTime } from "./store/playerStore";
|
|
@@ -112,13 +112,13 @@ describe("usePlayerStore", () => {
|
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
describe("
|
|
115
|
+
describe("updateElement", () => {
|
|
116
116
|
it("updates the start time of a specific element", () => {
|
|
117
117
|
usePlayerStore.getState().setElements([
|
|
118
118
|
{ id: "el-1", tag: "div", start: 0, duration: 5, track: 0 },
|
|
119
119
|
{ id: "el-2", tag: "div", start: 5, duration: 5, track: 1 },
|
|
120
120
|
]);
|
|
121
|
-
usePlayerStore.getState().
|
|
121
|
+
usePlayerStore.getState().updateElement("el-1", { start: 3 });
|
|
122
122
|
const elements = usePlayerStore.getState().elements;
|
|
123
123
|
expect(elements[0].start).toBe(3);
|
|
124
124
|
expect(elements[1].start).toBe(5); // unchanged
|
|
@@ -129,7 +129,7 @@ describe("usePlayerStore", () => {
|
|
|
129
129
|
{ id: "el-1", tag: "div", start: 0, duration: 5, track: 0 },
|
|
130
130
|
];
|
|
131
131
|
usePlayerStore.getState().setElements(original);
|
|
132
|
-
usePlayerStore.getState().
|
|
132
|
+
usePlayerStore.getState().updateElement("nonexistent", { start: 10 });
|
|
133
133
|
expect(usePlayerStore.getState().elements[0].start).toBe(0);
|
|
134
134
|
});
|
|
135
135
|
});
|