|
<script lang="ts"> |
|
import { createEventDispatcher, getContext, onMount, tick } from "svelte"; |
|
import { DropdownArrow } from "@gradio/icons"; |
|
import { click_outside } from "../utils/events"; |
|
import { layer_manager, type LayerScene } from "./utils"; |
|
import { EDITOR_KEY, type EditorContext } from "../ImageEditor.svelte"; |
|
import type { FileData } from "@gradio/client"; |
|
|
|
let show_layers = false; |
|
|
|
export let layer_files: (FileData | null)[] | null = []; |
|
|
|
const { pixi, current_layer, dimensions, register_context } = |
|
getContext<EditorContext>(EDITOR_KEY); |
|
|
|
const LayerManager = layer_manager(); |
|
let layers: LayerScene[] = []; |
|
|
|
register_context("layers", { |
|
init_fn: () => { |
|
new_layer(); |
|
}, |
|
reset_fn: () => { |
|
LayerManager.reset(); |
|
} |
|
}); |
|
|
|
async function new_layer(): Promise<void> { |
|
if (!$pixi) return; |
|
|
|
const [active_layer, all_layers] = LayerManager.add_layer( |
|
$pixi.layer_container, |
|
$pixi.renderer, |
|
...$dimensions |
|
); |
|
|
|
$current_layer = active_layer; |
|
layers = all_layers; |
|
} |
|
|
|
$: render_layer_files(layer_files); |
|
|
|
function is_not_null<T>(x: T | null): x is T { |
|
return x !== null; |
|
} |
|
|
|
async function render_layer_files( |
|
_layer_files: typeof layer_files |
|
): Promise<void> { |
|
await tick(); |
|
if (!_layer_files || _layer_files.length == 0) return; |
|
if (!$pixi) return; |
|
|
|
const fetch_promises = await Promise.all( |
|
_layer_files.map((f) => { |
|
if (!f || !f.url) return null; |
|
|
|
return fetch(f.url); |
|
}) |
|
); |
|
|
|
const blobs = await Promise.all( |
|
fetch_promises.map((p) => { |
|
if (!p) return null; |
|
return p.blob(); |
|
}) |
|
); |
|
|
|
LayerManager.reset(); |
|
|
|
let last_layer: [LayerScene, LayerScene[]] | null = null; |
|
for (const blob of blobs.filter(is_not_null)) { |
|
last_layer = await LayerManager.add_layer_from_blob( |
|
$pixi.layer_container, |
|
$pixi.renderer, |
|
blob |
|
); |
|
} |
|
|
|
if (!last_layer) return; |
|
|
|
$current_layer = last_layer[0]; |
|
layers = last_layer[1]; |
|
} |
|
|
|
onMount(async () => { |
|
await tick(); |
|
if (!$pixi) return; |
|
|
|
$pixi = { ...$pixi!, get_layers: LayerManager.get_layers }; |
|
}); |
|
</script> |
|
|
|
<div |
|
class="layer-wrap" |
|
class:closed={!show_layers} |
|
use:click_outside={() => (show_layers = false)} |
|
> |
|
<button aria-label="Show Layers" on:click={() => (show_layers = !show_layers)} |
|
>Layers<span class="layer-toggle"><DropdownArrow /></span></button |
|
> |
|
{#if show_layers} |
|
<ul> |
|
{#each layers as layer, i (i)} |
|
<li> |
|
<button |
|
class:selected_layer={$current_layer === layer} |
|
on:click={() => |
|
($current_layer = LayerManager.change_active_layer(i))} |
|
>Layer {i + 1}</button |
|
> |
|
</li> |
|
{/each} |
|
<li> |
|
<button aria-label="Add Layer" on:click={new_layer}> +</button> |
|
</li> |
|
</ul> |
|
{/if} |
|
</div> |
|
|
|
<style> |
|
.layer-toggle { |
|
width: 20px; |
|
transform: rotate(0deg); |
|
} |
|
|
|
.closed .layer-toggle { |
|
transform: rotate(-90deg); |
|
} |
|
|
|
.layer-wrap { |
|
position: absolute; |
|
bottom: 0; |
|
left: 0; |
|
display: inline-block; |
|
border: 1px solid var(--block-border-color); |
|
border-radius: var(--radius-md); |
|
|
|
transition: var(--button-transition); |
|
box-shadow: var(--button-shadow); |
|
|
|
text-align: left; |
|
border-bottom: none; |
|
border-left: none; |
|
border-bottom-right-radius: 0; |
|
border-top-left-radius: 0; |
|
background-color: var(--background-fill-primary); |
|
overflow: hidden; |
|
} |
|
|
|
.layer-wrap button { |
|
display: inline-flex; |
|
justify-content: flex-start; |
|
align-items: flex-start; |
|
padding: var(--size-2) var(--size-4); |
|
width: 100%; |
|
border-bottom: 1px solid var(--block-border-color); |
|
} |
|
|
|
.layer-wrap li:last-child button { |
|
border-bottom: none; |
|
text-align: center; |
|
} |
|
|
|
.closed > button { |
|
border-bottom: none; |
|
} |
|
|
|
.layer-wrap button:hover { |
|
background-color: var(--background-fill-secondary); |
|
} |
|
|
|
.selected_layer { |
|
background-color: var(--color-accent) !important; |
|
color: white; |
|
font-weight: bold; |
|
} |
|
</style> |
|
|