forked from MapComplete/MapComplete
Studio: first draft of layer editing
This commit is contained in:
parent
9661ade80c
commit
069767b9c7
43 changed files with 45374 additions and 5403 deletions
|
@ -1,116 +1,118 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ValidatorType } from "./Validators"
|
||||
import Validators from "./Validators"
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import { Validator } from "./Validator"
|
||||
import { Unit } from "../../Models/Unit"
|
||||
import UnitInput from "../Popup/UnitInput.svelte"
|
||||
import {UIEventSource} from "../../Logic/UIEventSource"
|
||||
import type {ValidatorType} from "./Validators"
|
||||
import Validators from "./Validators"
|
||||
import {ExclamationIcon} from "@rgossiaux/svelte-heroicons/solid"
|
||||
import {Translation} from "../i18n/Translation"
|
||||
import {createEventDispatcher, onDestroy} from "svelte"
|
||||
import {Validator} from "./Validator"
|
||||
import {Unit} from "../../Models/Unit"
|
||||
import UnitInput from "../Popup/UnitInput.svelte"
|
||||
|
||||
export let type: ValidatorType
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
||||
export let getCountry: () => string | undefined
|
||||
export let placeholder: string | Translation | undefined
|
||||
export let unit: Unit = undefined
|
||||
export let type: ValidatorType
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
||||
export let getCountry: () => string | undefined
|
||||
export let placeholder: string | Translation | undefined
|
||||
export let unit: Unit = undefined
|
||||
|
||||
export let value: UIEventSource<string>
|
||||
/**
|
||||
* Internal state bound to the input element.
|
||||
*
|
||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||
* Additionally, the unit is added when copying
|
||||
*/
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
export let value: UIEventSource<string>
|
||||
/**
|
||||
* Internal state bound to the input element.
|
||||
*
|
||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||
* Additionally, the unit is added when copying
|
||||
*/
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
|
||||
let validator: Validator = Validators.get(type ?? "string")
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
let validator: Validator = Validators.get(type ?? "string")
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
|
||||
function initValueAndDenom() {
|
||||
if (unit && value.data) {
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
||||
if (denom) {
|
||||
_value.setData(v)
|
||||
selectedUnit.setData(denom.canonical)
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
function initValueAndDenom() {
|
||||
if (unit && value.data) {
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
||||
if (denom) {
|
||||
_value.setData(v)
|
||||
selectedUnit.setData(denom.canonical)
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initValueAndDenom()
|
||||
|
||||
$: {
|
||||
// The type changed -> reset some values
|
||||
validator = Validators.get(type ?? "string")
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
||||
|
||||
initValueAndDenom()
|
||||
}
|
||||
|
||||
function setValues() {
|
||||
// Update the value stores
|
||||
const v = _value.data
|
||||
if (!validator.isValid(v, getCountry) || v === "") {
|
||||
value.setData(undefined)
|
||||
feedback?.setData(validator.getFeedback(v, getCountry))
|
||||
return
|
||||
$: {
|
||||
// The type changed -> reset some values
|
||||
validator = Validators.get(type ?? "string")
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
feedback = feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
||||
|
||||
initValueAndDenom()
|
||||
}
|
||||
|
||||
if (unit && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
function setValues() {
|
||||
// Update the value stores
|
||||
const v = _value.data
|
||||
if (!validator.isValid(v, getCountry) || v === "") {
|
||||
value.setData(undefined)
|
||||
feedback?.setData(validator.getFeedback(v, getCountry))
|
||||
return
|
||||
}
|
||||
|
||||
if (unit && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
value.setData(v + (selectedUnit.data ?? ""))
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
value.setData(v + (selectedUnit.data ?? ""))
|
||||
}
|
||||
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
||||
if (validator === undefined) {
|
||||
throw "Not a valid type for a validator:" + type
|
||||
}
|
||||
|
||||
const isValid = _value.map((v) => validator.isValid(v, getCountry))
|
||||
|
||||
let htmlElem: HTMLInputElement
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected }>()
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
||||
if (validator === undefined) {
|
||||
throw "Not a valid type for a validator:" + type
|
||||
}
|
||||
|
||||
const isValid = _value.map((v) => validator.isValid(v, getCountry))
|
||||
|
||||
let htmlElem: HTMLInputElement
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected, submit }>()
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if validator.textArea}
|
||||
<form on:submit|preventDefault={() => dispatch("submit")}>
|
||||
|
||||
<textarea
|
||||
class="w-full"
|
||||
bind:value={$_value}
|
||||
inputmode={validator.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
class="w-full"
|
||||
bind:value={$_value}
|
||||
inputmode={validator.inputmode ?? "text"}
|
||||
placeholder={_placeholder}></textarea>
|
||||
</form>
|
||||
{:else}
|
||||
<span class="inline-flex">
|
||||
<input
|
||||
bind:this={htmlElem}
|
||||
bind:value={$_value}
|
||||
class="w-full"
|
||||
inputmode={validator.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="-ml-6 h-6 w-6" />
|
||||
{/if}
|
||||
<form class="inline-flex" on:submit={() => dispatch("submit")}>
|
||||
<input
|
||||
bind:this={htmlElem}
|
||||
bind:value={$_value}
|
||||
class="w-full"
|
||||
inputmode={validator.inputmode ?? "text"}
|
||||
placeholder={_placeholder}
|
||||
/>
|
||||
{#if !$isValid}
|
||||
<ExclamationIcon class="-ml-6 h-6 w-6"/>
|
||||
{/if}
|
||||
|
||||
{#if unit !== undefined}
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} />
|
||||
{/if}
|
||||
</span>
|
||||
{#if unit !== undefined}
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value}/>
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
* If given, this function will be called to embed the given tags hint into this translation
|
||||
*/
|
||||
export let embedIn: ((string: string) => Translation) | undefined = undefined
|
||||
const userDetails = state.osmConnection.userDetails
|
||||
const userDetails = state?.osmConnection?.userDetails
|
||||
let tagsExplanation = ""
|
||||
$: tagsExplanation = tags?.asHumanString(true, false, currentProperties)
|
||||
</script>
|
||||
|
||||
{#if $userDetails.loggedIn}
|
||||
{#if !userDetails || $userDetails.loggedIn}
|
||||
<div>
|
||||
{#if tags === undefined}
|
||||
<slot name="no-tags">No tags</slot>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
export let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected }>()
|
||||
let dispatch = createEventDispatcher<{ selected, submit }>()
|
||||
onDestroy(
|
||||
value.addCallbackD(() => {
|
||||
dispatch("selected")
|
||||
|
@ -46,6 +46,8 @@
|
|||
{getCountry}
|
||||
{unit}
|
||||
on:selected={() => dispatch("selected")}
|
||||
on:submit={() => dispatch("submit")}
|
||||
|
||||
type={config.freeform.type}
|
||||
{placeholder}
|
||||
{value}
|
||||
|
@ -57,6 +59,7 @@
|
|||
{getCountry}
|
||||
{unit}
|
||||
on:selected={() => dispatch("selected")}
|
||||
on:submit={() => dispatch("submit")}
|
||||
type={config.freeform.type}
|
||||
{placeholder}
|
||||
{value}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
export let state: SpecialVisualizationState
|
||||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let feature: Feature
|
||||
export let layer: LayerConfig
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
let txt: string
|
||||
$: onDestroy(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import TagRenderingConfig from "../../../Models/ThemeConfig/TagRenderingConfig"
|
||||
import { UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import {Store, UIEventSource} from "../../../Logic/UIEventSource"
|
||||
import type { Feature } from "geojson"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import TagRenderingAnswer from "./TagRenderingAnswer.svelte"
|
||||
|
@ -18,7 +18,7 @@
|
|||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig
|
||||
|
||||
export let editingEnabled = state.featureSwitchUserbadge
|
||||
export let editingEnabled : Store<boolean> | undefined = state?.featureSwitchUserbadge
|
||||
|
||||
export let highlightedRendering: UIEventSource<string> = undefined
|
||||
export let showQuestionIfUnknown: boolean = false
|
||||
|
@ -67,7 +67,7 @@
|
|||
</script>
|
||||
|
||||
<div bind:this={htmlElem} class="">
|
||||
{#if config.question && $editingEnabled}
|
||||
{#if config.question && (!editingEnabled || $editingEnabled)}
|
||||
{#if editMode}
|
||||
<TagRenderingQuestion {config} {tags} {selectedElement} {state} {layer}>
|
||||
<button
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { Store, UIEventSource } from "../../../Logic/UIEventSource"
|
||||
import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource"
|
||||
import type { SpecialVisualizationState } from "../../SpecialVisualization"
|
||||
import Tr from "../../Base/Tr.svelte"
|
||||
import type { Feature } from "geojson"
|
||||
|
@ -27,11 +27,11 @@
|
|||
export let tags: UIEventSource<Record<string, string>>
|
||||
export let selectedElement: Feature
|
||||
export let state: SpecialVisualizationState
|
||||
export let layer: LayerConfig
|
||||
export let layer: LayerConfig | undefined
|
||||
|
||||
let feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||
|
||||
let unit: Unit = layer.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
let unit: Unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
|
||||
// Will be bound if a freeform is available
|
||||
let freeformInput = new UIEventSource<string>(tags?.[config.freeform?.key])
|
||||
|
@ -45,7 +45,7 @@
|
|||
return m.hideInAnswer.matchesProperties(tags.data)
|
||||
})
|
||||
// We received a new config -> reinit
|
||||
unit = layer.units.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
|
||||
|
||||
if (
|
||||
config.mappings?.length > 0 &&
|
||||
|
@ -96,7 +96,7 @@
|
|||
if (selectedTags === undefined) {
|
||||
return
|
||||
}
|
||||
if (layer.source === null) {
|
||||
if (layer === undefined || layer?.source === null) {
|
||||
/**
|
||||
* This is a special, priviliged layer.
|
||||
* We simply apply the tags onto the records
|
||||
|
@ -128,12 +128,12 @@
|
|||
.catch(console.error)
|
||||
}
|
||||
|
||||
let featureSwitchIsTesting = state.featureSwitchIsTesting
|
||||
let featureSwitchIsDebugging = state.featureSwitches.featureSwitchIsDebugging
|
||||
let showTags = state.userRelatedState.showTags
|
||||
let featureSwitchIsTesting = state.featureSwitchIsTesting ?? new ImmutableStore(false)
|
||||
let featureSwitchIsDebugging = state.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||
let showTags = state.userRelatedState?.showTags ?? new ImmutableStore(undefined)
|
||||
let numberOfCs = state.osmConnection.userDetails.data.csCount
|
||||
onDestroy(
|
||||
state.osmConnection.userDetails.addCallbackAndRun((ud) => {
|
||||
state.osmConnection?.userDetails?.addCallbackAndRun((ud) => {
|
||||
numberOfCs = ud.csCount
|
||||
})
|
||||
)
|
||||
|
@ -176,6 +176,7 @@
|
|||
{unit}
|
||||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
{:else if mappings !== undefined && !config.multiAnswer}
|
||||
<!-- Simple radiobuttons as mapping -->
|
||||
|
@ -215,6 +216,7 @@
|
|||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:selected={() => (selectedMapping = config.mappings?.length)}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
@ -254,6 +256,7 @@
|
|||
feature={selectedElement}
|
||||
value={freeformInput}
|
||||
on:selected={() => (checkedMappings[config.mappings.length] = true)}
|
||||
on:submit={onSave}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
|
|
45
UI/Studio/EditLayer.svelte
Normal file
45
UI/Studio/EditLayer.svelte
Normal file
|
@ -0,0 +1,45 @@
|
|||
<script lang="ts">
|
||||
|
||||
import EditLayerState from "./EditLayerState";
|
||||
import layerSchemaRaw from "../../assets/layerconfigmeta.json"
|
||||
import Region from "./Region.svelte";
|
||||
import TabbedGroup from "../Base/TabbedGroup.svelte";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {ConfigMeta} from "./configMeta";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
|
||||
let state = new EditLayerState()
|
||||
let layer = state.layer
|
||||
|
||||
const layerSchema: ConfigMeta[] = layerSchemaRaw
|
||||
const regions = Utils.Dedup(layerSchema.map(meta => meta.hints.group))
|
||||
.filter(region => region !== undefined)
|
||||
|
||||
const perRegion: Record<string, ConfigMeta[]> = {}
|
||||
for (const region of regions) {
|
||||
perRegion[region] = layerSchema.filter(meta => meta.hints.group === region)
|
||||
}
|
||||
console.log({perRegion})
|
||||
</script>
|
||||
|
||||
<h3>Edit layer {$layer?.id}</h3>
|
||||
|
||||
<TabbedGroup tab={new UIEventSource(0)}>
|
||||
<div slot="title0">General properties</div>
|
||||
<div class="flex flex-col" slot="content0">
|
||||
{#each regions as region}
|
||||
<Region {state} configs={perRegion[region]} title={region}/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div slot="title1">Information panel (questions and answers)</div>
|
||||
<div slot="content1">
|
||||
Information panel (todo)
|
||||
</div>
|
||||
|
||||
<div slot="title2">Rendering on the map</div>
|
||||
<div slot="content2">
|
||||
TODO: rendering on the map
|
||||
</div>
|
||||
</TabbedGroup>
|
8
UI/Studio/EditLayerState.ts
Normal file
8
UI/Studio/EditLayerState.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
|
||||
|
||||
export default class EditLayerState {
|
||||
public readonly osmConnection: OsmConnection
|
||||
constructor() {
|
||||
this.osmConnection = new OsmConnection({})
|
||||
}
|
||||
}
|
21
UI/Studio/Region.svelte
Normal file
21
UI/Studio/Region.svelte
Normal file
|
@ -0,0 +1,21 @@
|
|||
<script lang="ts">/***
|
||||
* A 'region' is a collection of properties that can be edited which are somewhat related.
|
||||
* They will typically be a subset of some properties
|
||||
*/
|
||||
import SchemaBasedField from "./SchemaBasedField.svelte";
|
||||
import type {ConfigMeta} from "./configMeta";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
|
||||
export let state : EditLayerState
|
||||
export let configs: ConfigMeta[]
|
||||
export let title: string
|
||||
|
||||
</script>
|
||||
|
||||
<h3>{title}</h3>
|
||||
<div class="pl-2 border border-black flex flex-col gap-y-1">
|
||||
|
||||
{#each configs as config}
|
||||
<SchemaBasedField {state} schema={config} title={config.path.at(-1)}></SchemaBasedField>
|
||||
{/each}
|
||||
</div>
|
6
UI/Studio/SchemaBasedArray.svelte
Normal file
6
UI/Studio/SchemaBasedArray.svelte
Normal file
|
@ -0,0 +1,6 @@
|
|||
<script lang="ts">
|
||||
export let title
|
||||
export let description
|
||||
|
||||
</script>
|
||||
|
55
UI/Studio/SchemaBasedField.svelte
Normal file
55
UI/Studio/SchemaBasedField.svelte
Normal file
|
@ -0,0 +1,55 @@
|
|||
<script lang="ts">
|
||||
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import type {ConfigMeta} from "./configMeta";
|
||||
import TagRenderingEditable from "../Popup/TagRendering/TagRenderingEditable.svelte";
|
||||
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig";
|
||||
import type {
|
||||
QuestionableTagRenderingConfigJson
|
||||
} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
|
||||
import EditLayerState from "./EditLayerState";
|
||||
|
||||
|
||||
export let state: EditLayerState
|
||||
export let schema: ConfigMeta
|
||||
export let title: string | undefined
|
||||
|
||||
let value = new UIEventSource<string>(undefined)
|
||||
let feedback = new UIEventSource<Translation>(undefined)
|
||||
|
||||
const configJson: QuestionableTagRenderingConfigJson = {
|
||||
id: schema.path.join("."),
|
||||
render: schema.path.at(-1) + ": <b>{value}</b>",
|
||||
question: schema.hints.question,
|
||||
questionHint: schema.description,
|
||||
freeform: {
|
||||
key: "value",
|
||||
type: schema.hints.typehint ?? "string"
|
||||
}
|
||||
}
|
||||
|
||||
if (!schema.required) {
|
||||
configJson.mappings = [{
|
||||
if: "value=",
|
||||
then: schema.path.at(-1) + " is not set. " + (schema.hints.ifunset ?? ""),
|
||||
}]
|
||||
}
|
||||
let config: TagRenderingConfig
|
||||
let err: string = undefined
|
||||
try {
|
||||
config = new TagRenderingConfig(configJson, "config based on " + schema.path.join("."))
|
||||
} catch (e) {
|
||||
console.error(e, config)
|
||||
err = e
|
||||
}
|
||||
let tags = new UIEventSource<Record<string, string>>({})
|
||||
</script>
|
||||
|
||||
{#if err !== undefined}
|
||||
<span class="alert">{err}</span>
|
||||
{:else}
|
||||
<div>
|
||||
<TagRenderingEditable {config} showQuestionIfUnknown={true} {state} {tags}/>
|
||||
</div>
|
||||
{/if}
|
14
UI/Studio/configMeta.ts
Normal file
14
UI/Studio/configMeta.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { JsonSchema, JsonSchemaType } from "./jsonSchema"
|
||||
|
||||
export interface ConfigMeta {
|
||||
path: string[]
|
||||
type: JsonSchemaType | JsonSchema[]
|
||||
hints: {
|
||||
group?: string
|
||||
typehint?: string
|
||||
question?: string
|
||||
ifunset?: string
|
||||
}
|
||||
required: boolean
|
||||
description: string
|
||||
}
|
20
UI/Studio/jsonSchema.ts
Normal file
20
UI/Studio/jsonSchema.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* Extracts the data from the scheme file and writes them in a flatter structure
|
||||
*/
|
||||
|
||||
export type JsonSchemaType =
|
||||
| string
|
||||
| { $ref: string; description: string }
|
||||
| { type: string }
|
||||
| JsonSchemaType[]
|
||||
export interface JsonSchema {
|
||||
description?: string
|
||||
type?: JsonSchemaType
|
||||
properties?: any
|
||||
items?: JsonSchema
|
||||
allOf?: JsonSchema[]
|
||||
anyOf: JsonSchema[]
|
||||
enum: JsonSchema[]
|
||||
$ref: string
|
||||
required: string[]
|
||||
}
|
7
UI/StudioGUI.svelte
Normal file
7
UI/StudioGUI.svelte
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script lang="ts">
|
||||
|
||||
|
||||
import EditLayer from "./Studio/EditLayer.svelte";
|
||||
</script>
|
||||
|
||||
<EditLayer/>
|
10
UI/StudioGui.ts
Normal file
10
UI/StudioGui.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import StudioGUI from "./StudioGUI.svelte"
|
||||
|
||||
export default class StudioGui {
|
||||
public setup() {
|
||||
new SvelteUIElement(StudioGUI, {}).AttachTo("main")
|
||||
}
|
||||
}
|
||||
|
||||
new StudioGui().setup()
|
Loading…
Add table
Add a link
Reference in a new issue