mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Add WIP video style preview dialog (#115)
Co-authored-by: Victor <victor@storyteller.ai>
This commit is contained in:
@@ -494,9 +494,7 @@ impl<'a> Iterator for Joints<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
JointIterType::Sequence(ref mut joints) => {
|
JointIterType::Sequence(ref mut joints) => {
|
||||||
let Some(next) = joints.pop_front() else {
|
let next = joints.pop_front()?;
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if next >= self.joints.len() {
|
if next >= self.joints.len() {
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// import GetApiHost from "./GetApiHost";
|
||||||
|
|
||||||
|
// const { formatUrl, host = "" } = GetApiHost();
|
||||||
|
|
||||||
|
const MakeMultipartRequest = (endpoint = "", body: any) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
Object.keys(body).forEach((key) => formData.append(key, body[key]));
|
||||||
|
|
||||||
|
// formData.append('source', "video");
|
||||||
|
|
||||||
|
return fetch("https://funnel.tailce84f.ts.net/preview/", {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
.then(res => res.blob());
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MakeMultipartRequest;
|
||||||
@@ -8,8 +8,8 @@ interface RouteSetup<UrlRouteArgs> {
|
|||||||
method: string;
|
method: string;
|
||||||
multipart?: boolean;
|
multipart?: boolean;
|
||||||
routingFunction: UrlRoutingFunction<UrlRouteArgs>;
|
routingFunction: UrlRoutingFunction<UrlRouteArgs>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const METHOD_OMITS_BODY: { [key: string]: boolean } = {
|
const METHOD_OMITS_BODY: { [key: string]: boolean } = {
|
||||||
DELETE: false,
|
DELETE: false,
|
||||||
GET: true,
|
GET: true,
|
||||||
@@ -19,25 +19,50 @@ const METHOD_OMITS_BODY: { [key: string]: boolean } = {
|
|||||||
PUT: false,
|
PUT: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MakeRequest = <UrlRouteArgs, Request, Response, UrlParams>(routeSetup: RouteSetup<UrlRouteArgs>) : (urlRouteArgs: UrlRouteArgs, request: Request, queries?: UrlParams) => Promise<Response> => {
|
export default <
|
||||||
return async function(urlRouteArgs: UrlRouteArgs, request: Request, queries?: any) : Promise<Response> {
|
UrlRouteArgs,
|
||||||
const newQueries = queries ? Object.keys(queries).map((key, i) => {
|
Request,
|
||||||
return `${ i ? "&" : "" }${ key }=${ Array.isArray(queries[key]) ? queries[key].join(`&${ key }=`) : queries[key] }`
|
Response,
|
||||||
}).join("") : null;
|
UrlParams extends Record<string, unknown>,
|
||||||
|
>(routeSetup: RouteSetup<UrlRouteArgs>) => {
|
||||||
|
return async function (
|
||||||
|
urlRouteArgs: UrlRouteArgs,
|
||||||
|
request: Request,
|
||||||
|
queries?: UrlParams,
|
||||||
|
overrideHost?: string
|
||||||
|
): Promise<Response> {
|
||||||
|
const newQueries = queries
|
||||||
|
? Object
|
||||||
|
.keys(queries)
|
||||||
|
.map((key, i) =>
|
||||||
|
`${ i ? "&" : "" }${ key }=${
|
||||||
|
Array.isArray(queries[key])
|
||||||
|
? (queries[key] as unknown[]).join(`&${ key }=`)
|
||||||
|
: queries[key]
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
: null;
|
||||||
|
|
||||||
const endpoint = `${ routeSetup.routingFunction(urlRouteArgs) }${ newQueries ? "?" + newQueries : "" }`;
|
const endpoint = `${
|
||||||
|
routeSetup.routingFunction(urlRouteArgs)
|
||||||
|
}${
|
||||||
|
newQueries ? "?" + newQueries : ""
|
||||||
|
}`;
|
||||||
const method = routeSetup.method;
|
const method = routeSetup.method;
|
||||||
const methodOmitsBody = METHOD_OMITS_BODY[method] || false;
|
const methodOmitsBody = METHOD_OMITS_BODY[method] || false;
|
||||||
|
|
||||||
console.log("🛼",request)
|
console.log("🛼",request)
|
||||||
|
|
||||||
return fetch(`https://api.fakeyou.com${ endpoint }`, {
|
return fetch(
|
||||||
method,
|
`${ overrideHost ? overrideHost : "https://api.fakeyou.com" }${ endpoint }`,
|
||||||
headers: {
|
{
|
||||||
"Accept": "application/json",
|
method,
|
||||||
...methodOmitsBody? {} : { "Content-Type": "application/json" }
|
headers: {
|
||||||
|
"Accept": "application/json",
|
||||||
|
...methodOmitsBody? {} : { "Content-Type": "application/json" }
|
||||||
},
|
},
|
||||||
credentials: 'include',
|
credentials: "include",
|
||||||
...methodOmitsBody ? {} : { body: JSON.stringify(request) },
|
...methodOmitsBody ? {} : { body: JSON.stringify(request) },
|
||||||
})
|
})
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
@@ -52,5 +77,3 @@ const MakeRequest = <UrlRouteArgs, Request, Response, UrlParams>(routeSetup: Rou
|
|||||||
.catch(e => ({ success : false }));
|
.catch(e => ({ success : false }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MakeRequest;
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import MakeMultipartRequest from "./MakeMultipartRequest";
|
||||||
|
|
||||||
|
export interface VideoStylePreviewRequest {
|
||||||
|
video: File,
|
||||||
|
request: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoStylePreview = (request: VideoStylePreviewRequest) => {
|
||||||
|
return MakeMultipartRequest("",request);
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import "@storyteller/studio-web/studio";
|
|||||||
import "@storyteller/studio-web/transform-toolbar";
|
import "@storyteller/studio-web/transform-toolbar";
|
||||||
import "./anim-editor.element";
|
import "./anim-editor.element";
|
||||||
import "./help.element";
|
import "./help.element";
|
||||||
|
import "./temp-video-preview.element";
|
||||||
|
|
||||||
import "./app.element.scss";
|
import "./app.element.scss";
|
||||||
import { mock_save_load } from "./util";
|
import { mock_save_load } from "./util";
|
||||||
@@ -60,6 +61,7 @@ export class AppElement extends LitElement {
|
|||||||
private api = new StorytellerApi();
|
private api = new StorytellerApi();
|
||||||
|
|
||||||
#addToSceneButtonRef = createRef<ButtonElement>();
|
#addToSceneButtonRef = createRef<ButtonElement>();
|
||||||
|
#videoStylePreviewBtnRef = createRef<ButtonElement>();
|
||||||
#mediaLibraryRef = createRef<DialogElement>();
|
#mediaLibraryRef = createRef<DialogElement>();
|
||||||
#inspectorRef = createRef<HTMLDivElement>();
|
#inspectorRef = createRef<HTMLDivElement>();
|
||||||
#hierarchyRef = createRef<SceneHierarchyElement>();
|
#hierarchyRef = createRef<SceneHierarchyElement>();
|
||||||
@@ -161,7 +163,7 @@ export class AppElement extends LitElement {
|
|||||||
<sts-scene-data-provider>
|
<sts-scene-data-provider>
|
||||||
<sts-overlay-provider>
|
<sts-overlay-provider>
|
||||||
<sts-iframe-messaging-bridge></sts-iframe-messaging-bridge>
|
<sts-iframe-messaging-bridge></sts-iframe-messaging-bridge>
|
||||||
|
|
||||||
${this.#params.mode === StudioMode.Editor ? html`
|
${this.#params.mode === StudioMode.Editor ? html`
|
||||||
<sts-add-to-scene-menu
|
<sts-add-to-scene-menu
|
||||||
.trigger=${this.#addToSceneButtonRef}
|
.trigger=${this.#addToSceneButtonRef}
|
||||||
@@ -220,6 +222,12 @@ export class AppElement extends LitElement {
|
|||||||
class="add-to-scene-dropdown"
|
class="add-to-scene-dropdown"
|
||||||
>
|
>
|
||||||
</sts-button>
|
</sts-button>
|
||||||
|
<sts-button
|
||||||
|
${ref(this.#videoStylePreviewBtnRef)}
|
||||||
|
icon="films"
|
||||||
|
class="video-style-preview-dropdown"
|
||||||
|
>
|
||||||
|
</sts-button>
|
||||||
<sts-transform-toolbar></sts-transform-toolbar>
|
<sts-transform-toolbar></sts-transform-toolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -234,7 +242,7 @@ export class AppElement extends LitElement {
|
|||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
${this.sidebars.map(({title, name}) => {
|
${this.sidebars.map(({title, name}) => {
|
||||||
return html`<sts-button class=${classMap({"current-tab": name === this.currentSidebar, "sidebar-tab": true})}>${title}</sts-button>`
|
return html`<sts-button class=${classMap({"current-tab": name === this.currentSidebar, "sidebar-tab": true})}>${title}</sts-button>`
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${this.renderSidebar()}
|
${this.renderSidebar()}
|
||||||
@@ -252,7 +260,7 @@ export class AppElement extends LitElement {
|
|||||||
<div class="entity-inspector">
|
<div class="entity-inspector">
|
||||||
<sts-entity-inspector></sts-entity-inspector>
|
<sts-entity-inspector></sts-entity-inspector>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<sts-anim-editor
|
<sts-anim-editor
|
||||||
class="timeline"
|
class="timeline"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
@@ -260,14 +268,19 @@ export class AppElement extends LitElement {
|
|||||||
})}
|
})}
|
||||||
></sts-anim-editor>
|
></sts-anim-editor>
|
||||||
` : nothing}
|
` : nothing}
|
||||||
|
|
||||||
|
|
||||||
|
<sts-temp-video-preview
|
||||||
|
.trigger=${ this.#videoStylePreviewBtnRef }>
|
||||||
|
</sts-temp-video-preview>
|
||||||
|
|
||||||
<!-- Dialogs and modals -->
|
<!-- Dialogs and modals -->
|
||||||
${this.showHelp ? html`
|
${this.showHelp ? html`
|
||||||
<div class="help">
|
<div class="help">
|
||||||
<sts-help></sts-help>
|
<sts-help></sts-help>
|
||||||
</div>
|
</div>
|
||||||
` : nothing}
|
` : nothing}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
${this.loading ? html`
|
${this.loading ? html`
|
||||||
|
|||||||
@@ -1,36 +1,57 @@
|
|||||||
.studio-bubble-select {
|
@use "sass:math";
|
||||||
|
|
||||||
|
:host {
|
||||||
// background: red;
|
// background: red;
|
||||||
display: flex;
|
display: flex;
|
||||||
list-style: none;
|
gap: 0.6rem;
|
||||||
margin-block-start: 0px;
|
box-sizing: border-box;
|
||||||
margin-block-end: 0px;
|
}
|
||||||
padding-inline-start: 0px;
|
|
||||||
|
|
||||||
li {
|
*, *::before, *::after {
|
||||||
flex: 0 0 auto;
|
box-sizing: border-box;
|
||||||
border: 2px solid darken(purple, 3%);
|
}
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-color: darken(purple, 3%);
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: rgba(#fff, 0.75);
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.selected-bubble {
|
.bubble {
|
||||||
color: #fff;
|
position: relative;
|
||||||
border-color: red;
|
flex: 0 0 auto;
|
||||||
background-color: lighten(purple, 6%);
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
&.fill {
|
width: 80px;
|
||||||
background-color: red;
|
height: 80px;
|
||||||
}
|
padding: 0.5rem;
|
||||||
}
|
border-radius: 0.5rem;
|
||||||
|
background: {
|
||||||
|
size: cover;
|
||||||
|
position: center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
outline: #FFF2 solid 2px;
|
||||||
|
color: rgba(#fff, 0.75);
|
||||||
|
transition: {
|
||||||
|
property: background-color, outline-color;
|
||||||
|
duration: 200ms;
|
||||||
|
timing-function: ease;
|
||||||
|
}
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
}
|
&.selected {
|
||||||
|
outline-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: linear-gradient(to top, #000B, #0000);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
position: relative;
|
||||||
|
font: {
|
||||||
|
size: math.div(12, 16) * 1rem;
|
||||||
|
weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,54 @@
|
|||||||
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
import { FormFieldBaseElement } from "@storyteller/studio-ui/forms/form-field-base";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { PropertyValues, html, unsafeCSS } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators.js";
|
||||||
|
import { classMap } from "lit/directives/class-map.js";
|
||||||
|
import { styleMap } from "lit/directives/style-map.js";
|
||||||
|
|
||||||
|
import { type StyleOption, type VstStyle } from "./inspector-tabs/style-tab";
|
||||||
|
|
||||||
import styles from "./bubble-select.element.scss?inline";
|
import styles from "./bubble-select.element.scss?inline";
|
||||||
|
|
||||||
// const abc = "word";
|
|
||||||
|
|
||||||
@customElement("sts-bubble-select")
|
@customElement("sts-bubble-select")
|
||||||
export class BubbleSelect extends LitElement {
|
export class BubbleSelect extends FormFieldBaseElement {
|
||||||
static override styles = unsafeCSS(styles);
|
static override styles = unsafeCSS(styles);
|
||||||
@property() name = "";
|
|
||||||
@property() onChange = (e: any) => {};
|
@property({ reflect: true })
|
||||||
@property() options = [];
|
override role = "listbox"
|
||||||
@property() value = "";
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
options: readonly StyleOption[] = [];
|
||||||
|
|
||||||
|
@property() value?: VstStyle;
|
||||||
|
|
||||||
|
protected override update(changes: PropertyValues<this>): void {
|
||||||
|
if (changes.has("value"))
|
||||||
|
this.internals.setFormValue(this.value ?? "");
|
||||||
|
|
||||||
|
super.update(changes);
|
||||||
|
}
|
||||||
|
|
||||||
protected override render = () => html`
|
protected override render = () => html`
|
||||||
<ul class="studio-bubble-select">
|
${ this.options.map(({ label, value, imageUrl }) => html`
|
||||||
${ this.options.map(({ label, value: itemValue },i) =>
|
<div
|
||||||
html`<li
|
class=${classMap({
|
||||||
class=${ itemValue === this.value ? "selected-bubble" : "" }
|
"bubble": true,
|
||||||
@click=${ ({ target }: any) => {
|
"selected": value === this.value,
|
||||||
this.onChange({ target: { name: this.name, value: itemValue } });
|
})}
|
||||||
} }
|
style=${styleMap({
|
||||||
>${ label }</li>`
|
"background-image": `url(${imageUrl})`,
|
||||||
)}
|
})}
|
||||||
</ul>
|
@click=${() => {
|
||||||
|
// this.onChange({ target: { name: this.name, value: itemValue } });
|
||||||
|
this.dispatchEvent(new CustomEvent("value-change", {
|
||||||
|
detail: value,
|
||||||
|
}))
|
||||||
|
} }
|
||||||
|
>
|
||||||
|
<div class="gradient"></div>
|
||||||
|
<span class="label">
|
||||||
|
${ label }
|
||||||
|
</span>
|
||||||
|
</div>`
|
||||||
|
)}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +1,56 @@
|
|||||||
import { LitElement, html, nothing, unsafeCSS } from "lit";
|
import { bind, on } from "@storyteller/framework";
|
||||||
|
import { LitElement, TemplateResult, html, unsafeCSS } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import styles from "./style-tab.scss?inline";
|
|
||||||
import { EnqueueEngineCompositing } from "../api/EnqueueEngineCompositing";
|
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import { drag, on, provide } from "@storyteller/framework";
|
|
||||||
|
import { EnqueueEngineCompositing } from "../api/EnqueueEngineCompositing";
|
||||||
|
|
||||||
|
import styles from "./style-tab.scss?inline";
|
||||||
|
|
||||||
|
export type VstStyle
|
||||||
|
= "anime_2d_flat"
|
||||||
|
| "cartoon_3d"
|
||||||
|
| "ink_bw_style"
|
||||||
|
| "paper_origami"
|
||||||
|
;
|
||||||
|
|
||||||
|
export interface StyleOption {
|
||||||
|
label: string;
|
||||||
|
imageUrl: string;
|
||||||
|
value: VstStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STYLE_OPTIONS: readonly StyleOption[] = [
|
||||||
|
{
|
||||||
|
label: "2D Anime",
|
||||||
|
imageUrl: "https://fakeyou.com/images/landing/onboarding/styles/style-2d-anime.webp",
|
||||||
|
value: "anime_2d_flat",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "3D Cartoon",
|
||||||
|
imageUrl: "https://fakeyou.com/images/landing/onboarding/styles/style-3d-cartoon.webp",
|
||||||
|
value: "cartoon_3d",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Ink B&W",
|
||||||
|
imageUrl: "https://fakeyou.com/images/landing/onboarding/styles/style-ink-bw.webp",
|
||||||
|
value: "ink_bw_style",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Origami",
|
||||||
|
imageUrl: "https://fakeyou.com/images/landing/onboarding/styles/style-origami.webp",
|
||||||
|
value: "paper_origami",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@customElement("sts-style-tab")
|
@customElement("sts-style-tab")
|
||||||
export class StyleTab extends LitElement {
|
export class StyleTab extends LitElement {
|
||||||
static override styles = unsafeCSS(styles);
|
static override styles = unsafeCSS(styles);
|
||||||
@state() vstStyle = "anime_2d_flat";
|
@state() vstStyle: VstStyle = "anime_2d_flat";
|
||||||
@state() posPrompt = "";
|
@state() posPrompt = "";
|
||||||
@state() negPrompt = "";
|
@state() negPrompt = "";
|
||||||
@property() sceneToken = "";
|
@property() sceneToken = "";
|
||||||
|
|
||||||
styleOptions = [
|
|
||||||
{
|
|
||||||
label: "2D Anime",
|
|
||||||
imageUrl: "/images/landing/onboarding/styles/style-2d-anime.webp",
|
|
||||||
value: "anime_2d_flat",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "3D Cartoon",
|
|
||||||
imageUrl: "/images/landing/onboarding/styles/style-3d-cartoon.webp",
|
|
||||||
value: "cartoon_3d",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Ink B&W",
|
|
||||||
imageUrl: "/images/landing/onboarding/styles/style-ink-bw.webp",
|
|
||||||
value: "ink_bw_style",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Origami",
|
|
||||||
imageUrl: "/images/landing/onboarding/styles/style-origami.webp",
|
|
||||||
value: "paper_origami",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
inputChange = ({ target }: { target: { name: string, value: any } }) => {
|
inputChange = ({ target }: { target: { name: string, value: any } }) => {
|
||||||
// console.log("💁🏼♀️", this[target.name]);
|
// console.log("💁🏼♀️", this[target.name]);
|
||||||
// this[target.name] = target.value; // fix later
|
// this[target.name] = target.value; // fix later
|
||||||
@@ -69,32 +84,36 @@ export class StyleTab extends LitElement {
|
|||||||
console.log("🏎️", this.sceneToken);
|
console.log("🏎️", this.sceneToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override render = () => html`<section class="inspector-input-set">
|
protected override render = (): TemplateResult => html`
|
||||||
${ this.vstStyle }
|
<section class="inspector-input-set">
|
||||||
<button @click=${ this.thingy }>State</button>
|
${ this.vstStyle }
|
||||||
<sts-bubble-select
|
<button @click=${ this.thingy }>State</button>
|
||||||
.onChange=${ ({ target }: any) => { this.vstStyle = target.value; } }
|
<sts-bubble-select
|
||||||
.options=${this.styleOptions}
|
.options=${STYLE_OPTIONS}
|
||||||
.value=${this.vstStyle}
|
.value=${bind(this, "vstStyle")}
|
||||||
></sts-bubble-select>
|
></sts-bubble-select>
|
||||||
<label htmlFor="inspector-posPrompt">Positive Prompt</label>
|
|
||||||
<textarea
|
<label htmlFor="inspector-posPrompt">Positive Prompt</label>
|
||||||
id="inspector-posPrompt"
|
<textarea
|
||||||
name="posPrompt"
|
id="inspector-posPrompt"
|
||||||
@input=${ this.inputChange }
|
name="posPrompt"
|
||||||
.value=${ this.posPrompt }
|
@input=${ this.inputChange }
|
||||||
></textarea>
|
.value=${ this.posPrompt }
|
||||||
<label htmlFor="inspector-posPrompt">Negative Prompt</label>
|
></textarea>
|
||||||
<textarea
|
|
||||||
id="inspector-negPrompt"
|
<label htmlFor="inspector-posPrompt">Negative Prompt</label>
|
||||||
name="negPrompt"
|
<textarea
|
||||||
@input=${ this.inputChange }
|
id="inspector-negPrompt"
|
||||||
.value=${ this.negPrompt }
|
name="negPrompt"
|
||||||
></textarea>
|
@input=${ this.inputChange }
|
||||||
<sts-button
|
.value=${ this.negPrompt }
|
||||||
@click=${this.compositorEnqueue(this.sceneToken)}
|
></textarea>
|
||||||
>
|
|
||||||
Generate your movie
|
<sts-button
|
||||||
</sts-button>
|
@click=${this.compositorEnqueue(this.sceneToken)}
|
||||||
</section>`;
|
>
|
||||||
}
|
Generate your movie
|
||||||
|
</sts-button>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
.studio-inspector {
|
|
||||||
color: #fff;
|
|
||||||
background: blue;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
@use "sass:math";
|
||||||
|
|
||||||
|
.video-style-preview {
|
||||||
|
width: calc(100vw - 3rem);
|
||||||
|
height: calc(100vh - 3rem);
|
||||||
|
|
||||||
|
&::part(content) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__side-by-side {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__panel {
|
||||||
|
flex: 0 0 50%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__heading {
|
||||||
|
margin: 0;
|
||||||
|
font: {
|
||||||
|
family: "Fira Sans";
|
||||||
|
size: 1rem;
|
||||||
|
weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-top: calc((536 / 960) * 100%);
|
||||||
|
background: {
|
||||||
|
size: cover;
|
||||||
|
position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__prompts {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
&--base-style {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font: {
|
||||||
|
family: "Fira Sans";
|
||||||
|
size: math.div(15, 16) * 1rem;
|
||||||
|
weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
|
||||||
|
background: #0004;
|
||||||
|
border: 2px solid #FFF2;
|
||||||
|
color: #FFFC;
|
||||||
|
transition: {
|
||||||
|
property: background-color, border-color, color;
|
||||||
|
duration: 33.333ms;
|
||||||
|
timing-function: linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
font: {
|
||||||
|
family: "Fira Sans", sans-serif;
|
||||||
|
size: math.div(15, 16) * 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import * as studio from "@storyteller/studio";
|
||||||
|
import { LitElement, type PropertyValues, html, nothing, unsafeCSS, TemplateResult } from "lit";
|
||||||
|
import { type Ref, createRef, ref } from "lit/directives/ref.js";
|
||||||
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
|
import { styleMap } from "lit/directives/style-map.js";
|
||||||
|
import { type ScreenshotReadyEvent } from "@storyteller/studio";
|
||||||
|
import { DialogElement } from "@storyteller/studio-ui/dialog";
|
||||||
|
import { bind, on } from "@storyteller/framework";
|
||||||
|
|
||||||
|
import { VideoStylePreview } from "./api/VideoStylePreview";
|
||||||
|
|
||||||
|
import "./bubble-select.element";
|
||||||
|
|
||||||
|
import styles from "./temp-video-preview.element.scss?inline";
|
||||||
|
import { STYLE_OPTIONS, type VstStyle } from "./inspector-tabs/style-tab";
|
||||||
|
|
||||||
|
enum InspectorMode {
|
||||||
|
Properties,
|
||||||
|
Style
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement("sts-temp-video-preview")
|
||||||
|
export class TempVideoPreview extends LitElement {
|
||||||
|
@property({ attribute: false })
|
||||||
|
trigger?: Ref<HTMLElement>;
|
||||||
|
|
||||||
|
@state() sourceBlob?: Blob;
|
||||||
|
@state() resultBlob?: Blob;
|
||||||
|
|
||||||
|
@state() sourceImageUrl?: string;
|
||||||
|
@state() resultImageUrl?: string;
|
||||||
|
|
||||||
|
@state() selectedStyle: VstStyle = "anime_2d_flat";
|
||||||
|
@state() prompt = "";
|
||||||
|
@state() negPrompt = "";
|
||||||
|
|
||||||
|
#temporaryVideoPreviewRef = createRef<DialogElement>();
|
||||||
|
|
||||||
|
protected override willUpdate(changes: PropertyValues<this>): void {
|
||||||
|
if (changes.has("trigger")) {
|
||||||
|
const prevTrigger = changes.get("trigger");
|
||||||
|
prevTrigger?.value?.removeEventListener("click", this.onDialogOpen);
|
||||||
|
|
||||||
|
this.trigger?.value?.addEventListener("click", this.onDialogOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.willUpdate(changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
override disconnectedCallback(): void {
|
||||||
|
this.trigger?.value?.removeEventListener("click", this.onDialogOpen);
|
||||||
|
|
||||||
|
if (this.sourceImageUrl) {
|
||||||
|
URL.revokeObjectURL(this.sourceImageUrl);
|
||||||
|
this.sourceImageUrl = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.resultImageUrl) {
|
||||||
|
URL.revokeObjectURL(this.resultImageUrl);
|
||||||
|
this.resultImageUrl = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
requestScreen = () => {
|
||||||
|
studio.requestScreenshot();
|
||||||
|
console.log("🟢");
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogOpen = () => {
|
||||||
|
studio.requestScreenshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@on("window:screenshot-ready")
|
||||||
|
async onScreenshotReady (event: ScreenshotReadyEvent) {
|
||||||
|
console.log("🟣",event.detail.data);
|
||||||
|
const file = new File(
|
||||||
|
[event.detail.data],
|
||||||
|
"studio-scene-screen.png",
|
||||||
|
{ type: "image/png" },
|
||||||
|
);
|
||||||
|
this.sourceBlob = file;
|
||||||
|
|
||||||
|
const prevSourceUrl = this.sourceImageUrl;
|
||||||
|
this.sourceImageUrl = URL.createObjectURL(this.sourceBlob);
|
||||||
|
|
||||||
|
if (prevSourceUrl)
|
||||||
|
URL.revokeObjectURL(prevSourceUrl);
|
||||||
|
|
||||||
|
this.resultImageUrl = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.resultBlob = await VideoStylePreview({
|
||||||
|
video: file,
|
||||||
|
request:
|
||||||
|
JSON.stringify({
|
||||||
|
"style": this.selectedStyle,
|
||||||
|
"positive_prompt": this.prompt,
|
||||||
|
"negative_prompt": this.negPrompt,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
console.log("🟡", this.resultBlob);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
// TODO: Show error message
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prevResultUrl = this.resultImageUrl;
|
||||||
|
this.resultImageUrl = URL.createObjectURL(this.resultBlob);
|
||||||
|
|
||||||
|
if (prevResultUrl)
|
||||||
|
URL.revokeObjectURL(prevResultUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTextInputBubbling(event: KeyboardEvent): void {
|
||||||
|
// We want modifier keys like "Alt" to always be received by the Bevy
|
||||||
|
// canvas because they affect the camera controller behavior, but we do
|
||||||
|
// NOT want Bevy to receive typing inputs like W, A, S, and D while the
|
||||||
|
// user is trying to type into the Prompt field.
|
||||||
|
if (/^(Alt|Control)$/.test(event.key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override render = (): TemplateResult => html`
|
||||||
|
<sts-dialog
|
||||||
|
${ref(this.#temporaryVideoPreviewRef)}
|
||||||
|
skipWrapper
|
||||||
|
.trigger=${ this.trigger ?? [] }
|
||||||
|
.template=${[this, html`
|
||||||
|
<sts-dialog-base
|
||||||
|
class="video-style-preview"
|
||||||
|
@close=${() => {
|
||||||
|
this.#temporaryVideoPreviewRef.value?.close();
|
||||||
|
}}
|
||||||
|
@keydown=${this.stopTextInputBubbling}
|
||||||
|
@keyup=${this.stopTextInputBubbling}
|
||||||
|
>
|
||||||
|
<sts-dialog-header icon="films">
|
||||||
|
Video Style Preview
|
||||||
|
</sts-dialog-header>
|
||||||
|
|
||||||
|
<style>${unsafeCSS(styles)}</style>
|
||||||
|
|
||||||
|
<div class="video-style-preview__side-by-side">
|
||||||
|
<div class="video-style-preview__panel">
|
||||||
|
<h2 class="video-style-preview__heading">
|
||||||
|
Raw Preview
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
video-style-preview__image
|
||||||
|
video-style-preview__image--source"
|
||||||
|
style=${styleMap({
|
||||||
|
"background-image": this.sourceImageUrl
|
||||||
|
? `url(${this.sourceImageUrl})`
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
src=${ifDefined(this.sourceImageUrl)}
|
||||||
|
>
|
||||||
|
${!this.sourceImageUrl ? html`
|
||||||
|
<h2 class="video-style-preview__heading">
|
||||||
|
Loading...
|
||||||
|
</h2>
|
||||||
|
` : nothing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="video-style-preview__panel">
|
||||||
|
<h2 class="video-style-preview__heading">
|
||||||
|
Styled Preview
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
video-style-preview__image
|
||||||
|
video-style-preview__image--result"
|
||||||
|
style=${styleMap({
|
||||||
|
"background-image": this.resultImageUrl
|
||||||
|
? `url(${this.resultImageUrl})`
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
${!this.resultImageUrl ? html`
|
||||||
|
<h2 class="video-style-preview__heading">
|
||||||
|
Loading...
|
||||||
|
</h2>
|
||||||
|
` : nothing}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="
|
||||||
|
video-style-preview__form-field
|
||||||
|
video-style-preview__form-field--base-style"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="video-style-preview__label"
|
||||||
|
for="video-style-preview-base-style"
|
||||||
|
>
|
||||||
|
Select Base Style
|
||||||
|
</label>
|
||||||
|
<sts-bubble-select
|
||||||
|
id="video-style-preview-base-style"
|
||||||
|
.options=${STYLE_OPTIONS}
|
||||||
|
.value=${bind(this, "selectedStyle")}
|
||||||
|
></sts-bubble-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="video-style-preview__prompts">
|
||||||
|
<div class="video-style-preview__form-field">
|
||||||
|
<label
|
||||||
|
class="video-style-preview__label"
|
||||||
|
for="video-style-preview-prompt"
|
||||||
|
>
|
||||||
|
Enter a Prompt
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="video-style-preview-prompt"
|
||||||
|
class="video-style-preview__input"
|
||||||
|
name="prompt"
|
||||||
|
cols="60"
|
||||||
|
rows="8"
|
||||||
|
.value=${this.prompt}
|
||||||
|
@input=${(event: InputEvent) => {
|
||||||
|
this.prompt = (event.target as HTMLTextAreaElement).value;
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="video-style-preview__form-field">
|
||||||
|
<label
|
||||||
|
class="video-style-preview__label"
|
||||||
|
for="video-style-preview-neg-prompt"
|
||||||
|
>
|
||||||
|
Negative Prompt
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
id="video-style-preview-neg-prompt"
|
||||||
|
class="video-style-preview__input"
|
||||||
|
name="neg-prompt"
|
||||||
|
cols="60"
|
||||||
|
rows="8"
|
||||||
|
placeholder=${"Type here to filter out the things you don't "
|
||||||
|
+ "want in your scene..."}
|
||||||
|
.value=${this.negPrompt}
|
||||||
|
@input=${(event: InputEvent) => {
|
||||||
|
this.negPrompt = (event.target as HTMLTextAreaElement).value;
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<sts-dialog-footer>
|
||||||
|
<sts-button
|
||||||
|
primary
|
||||||
|
icon="rotate"
|
||||||
|
@click=${this.requestScreen}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</sts-button>
|
||||||
|
</sts-dialog-footer>
|
||||||
|
</sts-dialog-base>
|
||||||
|
`] as const}
|
||||||
|
></sts-dialog>
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
background: #0006;
|
background: var(--background-color, #0006);
|
||||||
color: #FFFC;
|
color: #FFFC;
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
transition: {
|
transition: {
|
||||||
|
|||||||
@@ -16,16 +16,27 @@ export class ButtonElement extends LitElement {
|
|||||||
@property() icon?: string;
|
@property() icon?: string;
|
||||||
@property({ type: Boolean }) disabled = false;
|
@property({ type: Boolean }) disabled = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
primary?: boolean;
|
||||||
|
|
||||||
override tabIndex = 0;
|
override tabIndex = 0;
|
||||||
|
|
||||||
protected override willUpdate(changes: PropertyValues<this>): void {
|
protected override update(changes: PropertyValues<this>): void {
|
||||||
if (changes.has("disabled")) {
|
if (changes.has("disabled")) {
|
||||||
this.ariaDisabled = this.disabled ? "true" : null;
|
this.ariaDisabled = this.disabled ? "true" : null;
|
||||||
this.tabIndex = this.disabled ? -1 : 0;
|
this.tabIndex = this.disabled ? -1 : 0;
|
||||||
this.classList.toggle("disabled", this.disabled);
|
this.classList.toggle("disabled", this.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.willUpdate(changes);
|
if (changes.has("primary")) {
|
||||||
|
if (this.primary) {
|
||||||
|
this.style.setProperty("--background-color", "var(--color-accent)");
|
||||||
|
} else {
|
||||||
|
this.style.removeProperty("--background-color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.update(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@on("keydown")
|
@on("keydown")
|
||||||
|
|||||||
@@ -392,6 +392,11 @@ export class DialogElement extends LitElement {
|
|||||||
trigger.addEventListener("click", this.onTriggerClick);
|
trigger.addEventListener("click", this.onTriggerClick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changes.has("template")) {
|
||||||
|
if (this.#overlayHandle && this.template)
|
||||||
|
this._overlay.reRender(this.#overlayHandle, this.template);
|
||||||
|
}
|
||||||
|
|
||||||
super.updated(changes);
|
super.updated(changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
export * from "./form-field-base.element";
|
||||||
export * from "./text-field.element";
|
export * from "./text-field.element";
|
||||||
export * from "./scalar-field.element";
|
export * from "./scalar-field.element";
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import farMagnifyingGlass from "@fortawesome/fontawesome-pro/svgs/regular/magnif
|
|||||||
import fasHouse from "@fortawesome/fontawesome-pro/svgs/solid/house.svg?raw";
|
import fasHouse from "@fortawesome/fontawesome-pro/svgs/solid/house.svg?raw";
|
||||||
import fasRecord from "@fortawesome/fontawesome-pro/svgs/solid/circle.svg?raw";
|
import fasRecord from "@fortawesome/fontawesome-pro/svgs/solid/circle.svg?raw";
|
||||||
import fasUpload from "@fortawesome/fontawesome-pro/svgs/solid/upload.svg?raw";
|
import fasUpload from "@fortawesome/fontawesome-pro/svgs/solid/upload.svg?raw";
|
||||||
|
import fasFilms from "@fortawesome/fontawesome-pro/svgs/solid/films.svg?raw";
|
||||||
|
|
||||||
import customCylinder from "../assets/icons/cylinder.svg?raw";
|
import customCylinder from "../assets/icons/cylinder.svg?raw";
|
||||||
import customSphere from "../assets/icons/sphere.svg?raw";
|
import customSphere from "../assets/icons/sphere.svg?raw";
|
||||||
@@ -91,6 +92,7 @@ function renderSvg(iconId: string) {
|
|||||||
"figure": () => fasPersonRunning,
|
"figure": () => fasPersonRunning,
|
||||||
"file-import": () => farFileImport,
|
"file-import": () => farFileImport,
|
||||||
"film": () => farFilm,
|
"film": () => farFilm,
|
||||||
|
"films": () => fasFilms,
|
||||||
"floppy-disk": () => fasFloppyDisk,
|
"floppy-disk": () => fasFloppyDisk,
|
||||||
"fwd-fast": () => fasFwdFast,
|
"fwd-fast": () => fasFwdFast,
|
||||||
"fwd-step": () => fasFwdStep,
|
"fwd-step": () => fasFwdStep,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { inject, on } from "@storyteller/framework";
|
import { inject, on } from "@storyteller/framework";
|
||||||
import { LitElement, PropertyValues, css, html, unsafeCSS } from "lit";
|
import { LitElement, PropertyValues, TemplateResult, css, html, unsafeCSS } from "lit";
|
||||||
import { customElement, property } from "lit/decorators.js";
|
import { customElement, property } from "lit/decorators.js";
|
||||||
import { ifDefined } from "lit/directives/if-defined.js";
|
import { ifDefined } from "lit/directives/if-defined.js";
|
||||||
import { createRef, ref, type Ref } from "lit/directives/ref.js";
|
import { createRef, ref, type Ref } from "lit/directives/ref.js";
|
||||||
@@ -82,6 +82,8 @@ export class MenuElement extends LitElement {
|
|||||||
#panelRef = createRef<MenuPanelElement>();
|
#panelRef = createRef<MenuPanelElement>();
|
||||||
#handle?: WeakRef<OverlayHandle>;
|
#handle?: WeakRef<OverlayHandle>;
|
||||||
|
|
||||||
|
#template?: TemplateResult;
|
||||||
|
|
||||||
show(): void {
|
show(): void {
|
||||||
const trigger = this.trigger.value;
|
const trigger = this.trigger.value;
|
||||||
if (!trigger) return;
|
if (!trigger) return;
|
||||||
@@ -90,8 +92,7 @@ export class MenuElement extends LitElement {
|
|||||||
trigger.ariaExpanded = "true";
|
trigger.ariaExpanded = "true";
|
||||||
|
|
||||||
const rect = trigger.getBoundingClientRect();
|
const rect = trigger.getBoundingClientRect();
|
||||||
|
this.#template = html`
|
||||||
this.#handle = this._overlay.attach(this, html`
|
|
||||||
<sts-menu-panel
|
<sts-menu-panel
|
||||||
${ref(this.#panelRef)}
|
${ref(this.#panelRef)}
|
||||||
id=${this.#menuId}
|
id=${this.#menuId}
|
||||||
@@ -108,7 +109,9 @@ export class MenuElement extends LitElement {
|
|||||||
// <sts-menu-panel>'s slot
|
// <sts-menu-panel>'s slot
|
||||||
this.#slotRef.value?.assignedNodes()
|
this.#slotRef.value?.assignedNodes()
|
||||||
}</sts-menu-panel>
|
}</sts-menu-panel>
|
||||||
`);
|
`;
|
||||||
|
|
||||||
|
this.#handle = this._overlay.attach(this, this.#template);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide(): void {
|
hide(): void {
|
||||||
@@ -134,8 +137,9 @@ export class MenuElement extends LitElement {
|
|||||||
protected override update(changes: PropertyValues<this>): void {
|
protected override update(changes: PropertyValues<this>): void {
|
||||||
super.update(changes);
|
super.update(changes);
|
||||||
|
|
||||||
if (this.#handle)
|
|
||||||
this._overlay.reRender(this.#handle);
|
if (this.#handle && this.#template)
|
||||||
|
this._overlay.reRender(this.#handle, [this, this.#template]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override updated(changes: PropertyValues<this>): void {
|
protected override updated(changes: PropertyValues<this>): void {
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ export interface OverlayProvider {
|
|||||||
detach(handle: WeakRef<OverlayHandle>): void;
|
detach(handle: WeakRef<OverlayHandle>): void;
|
||||||
|
|
||||||
/** Re-render a currently-attached overlay. */
|
/** Re-render a currently-attached overlay. */
|
||||||
reRender(handle: WeakRef<OverlayHandle>): void;
|
reRender(handle: WeakRef<OverlayHandle>, template: [LitElement, TemplateResult]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AttachOptions {
|
export interface AttachOptions {
|
||||||
@@ -226,19 +226,14 @@ export class OverlayProviderElement
|
|||||||
|
|
||||||
// TODO: I'm not actually sure whether this serves a necessary function.
|
// TODO: I'm not actually sure whether this serves a necessary function.
|
||||||
// Needs further testing.
|
// Needs further testing.
|
||||||
reRender(handle: WeakRef<OverlayHandle>): void {
|
reRender(handle: WeakRef<OverlayHandle>, template: [LitElement, TemplateResult]): void {
|
||||||
const index = handle.deref()?.id;
|
const index = handle.deref()?.id;
|
||||||
if (index == null || index === -1)
|
if (index == null || index === -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const container = this.#containerRefs[index]?.value;
|
this.#templates[index] = template;
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const tuple = this.#templates[index];
|
this.#updateLightDom();
|
||||||
if (!tuple) return;
|
|
||||||
|
|
||||||
const [host, template] = tuple;
|
|
||||||
render(template, container, { host });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateLightDom(): void {
|
#updateLightDom(): void {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"@storyteller/studio-ui/progress": ["studio-ui/src/lib/progress.element.ts"],
|
"@storyteller/studio-ui/progress": ["studio-ui/src/lib/progress.element.ts"],
|
||||||
"@storyteller/studio-ui/toolbar": ["studio-ui/src/lib/toolbar.element.ts"],
|
"@storyteller/studio-ui/toolbar": ["studio-ui/src/lib/toolbar.element.ts"],
|
||||||
"@storyteller/studio-ui/tree": ["studio-ui/src/lib/tree.element.ts"],
|
"@storyteller/studio-ui/tree": ["studio-ui/src/lib/tree.element.ts"],
|
||||||
|
"@storyteller/studio-ui/forms/form-field-base": ["studio-ui/src/lib/forms/form-field-base.element.ts"],
|
||||||
"@storyteller/studio-ui/forms/text-field": ["studio-ui/src/lib/forms/text-field.element.ts"],
|
"@storyteller/studio-ui/forms/text-field": ["studio-ui/src/lib/forms/text-field.element.ts"],
|
||||||
"@storyteller/studio-ui/forms/scalar-field": ["studio-ui/src/lib/forms/scalar-field.element.ts"],
|
"@storyteller/studio-ui/forms/scalar-field": ["studio-ui/src/lib/forms/scalar-field.element.ts"],
|
||||||
"@storyteller/studio-web": ["studio-web/src/index.ts"],
|
"@storyteller/studio-web": ["studio-web/src/index.ts"],
|
||||||
|
|||||||
Reference in New Issue
Block a user