mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-09 19:42:48 +00:00
lite layout
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "amacc1ng",
|
"name": "charachorder-device-manager",
|
||||||
"version": "0.6.5",
|
"version": "0.6.5",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Theaninova/amacc1ng.git"
|
"url": "https://github.com/CharaChorder/DeviceManager.git"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Theaninova/amacc1ng",
|
"homepage": "https://github.com/CharaChorder/DeviceManager",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Theaninova/amacc1ng/issues"
|
"url": "https://github.com/CharaChorder/DeviceManager/issues"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm-run-all --parallel vite typesafe-i18n",
|
"dev": "npm-run-all --parallel vite typesafe-i18n",
|
||||||
|
|||||||
87
src/lib/assets/layouts/lite.yml
Normal file
87
src/lib/assets/layouts/lite.yml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
name: Lite
|
||||||
|
row:
|
||||||
|
- col:
|
||||||
|
- id: 53
|
||||||
|
- id: 54
|
||||||
|
- id: 55
|
||||||
|
- id: 56
|
||||||
|
- id: 57
|
||||||
|
- id: 58
|
||||||
|
- id: 59
|
||||||
|
- id: 60
|
||||||
|
- id: 61
|
||||||
|
- id: 62
|
||||||
|
- id: 63
|
||||||
|
- id: 64
|
||||||
|
- id: 65
|
||||||
|
- id: 66
|
||||||
|
size: [ 2, 1 ]
|
||||||
|
- col:
|
||||||
|
- id: 39
|
||||||
|
size: [ 1.5, 1 ]
|
||||||
|
- id: 40
|
||||||
|
- id: 41
|
||||||
|
- id: 42
|
||||||
|
- id: 43
|
||||||
|
- id: 44
|
||||||
|
- id: 45
|
||||||
|
- id: 46
|
||||||
|
- id: 47
|
||||||
|
- id: 48
|
||||||
|
- id: 49
|
||||||
|
- id: 50
|
||||||
|
- id: 51
|
||||||
|
- id: 52
|
||||||
|
size: [ 1.5, 1 ]
|
||||||
|
- col:
|
||||||
|
- id: 26
|
||||||
|
size: [ 1.75, 1 ]
|
||||||
|
- id: 27
|
||||||
|
- id: 28
|
||||||
|
- id: 29
|
||||||
|
- id: 30
|
||||||
|
- id: 31
|
||||||
|
- id: 32
|
||||||
|
- id: 33
|
||||||
|
- id: 34
|
||||||
|
- id: 35
|
||||||
|
- id: 36
|
||||||
|
- id: 37
|
||||||
|
- id: 38
|
||||||
|
size: [ 2.25, 1 ]
|
||||||
|
- col:
|
||||||
|
- id: 12
|
||||||
|
size: [ 2, 1 ]
|
||||||
|
- id: 13
|
||||||
|
- id: 14
|
||||||
|
- id: 15
|
||||||
|
- id: 16
|
||||||
|
- id: 17
|
||||||
|
- id: 18
|
||||||
|
- id: 19
|
||||||
|
- id: 20
|
||||||
|
- id: 21
|
||||||
|
- id: 22
|
||||||
|
- id: 23
|
||||||
|
- id: 24
|
||||||
|
- id: 25
|
||||||
|
- col:
|
||||||
|
- id: 0
|
||||||
|
- id: 1
|
||||||
|
size: [ 1.25, 1 ]
|
||||||
|
- id: 2
|
||||||
|
size: [ 1.25, 1 ]
|
||||||
|
- id: 3
|
||||||
|
size: [ 2, 1 ]
|
||||||
|
- id: 4
|
||||||
|
- id: 5
|
||||||
|
- id: 6
|
||||||
|
size: [ 2, 1 ]
|
||||||
|
- id: 7
|
||||||
|
size: [ 1.25, 1 ]
|
||||||
|
- id: 8
|
||||||
|
size: [ 1.25, 1 ]
|
||||||
|
- id: 9
|
||||||
|
- id: 10
|
||||||
|
- id: 11
|
||||||
|
|
||||||
@@ -165,6 +165,7 @@
|
|||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transform-origin: top left;
|
||||||
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
296
src/lib/components/layout/GenericLayout.svelte
Normal file
296
src/lib/components/layout/GenericLayout.svelte
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import rawLayout from "$lib/assets/layouts/lite.yml"
|
||||||
|
import {compileLayout} from "$lib/serialization/visual-layout"
|
||||||
|
import type {VisualLayout, CompiledLayoutKey} from "$lib/serialization/visual-layout"
|
||||||
|
import {changes, layout} from "$lib/serial/connection"
|
||||||
|
import type {Change} from "$lib/serial/connection"
|
||||||
|
import {dev} from "$app/environment"
|
||||||
|
import {KEYMAP_CODES} from "$lib/serial/keymap-codes.js"
|
||||||
|
import type {KeyInfo} from "$lib/serial/keymap-codes.js"
|
||||||
|
import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
|
||||||
|
import {get} from "svelte/store"
|
||||||
|
import type {CharaLayout} from "$lib/serialization/layout"
|
||||||
|
|
||||||
|
const scale = 50
|
||||||
|
export let inactiveScale = 0.6
|
||||||
|
export let inactiveOpacity = 0.4
|
||||||
|
export let strokeWidth = 1
|
||||||
|
export let margin = 5
|
||||||
|
export let fontSize = 9
|
||||||
|
export let iconFontSize = 14
|
||||||
|
export let activeLayer: number
|
||||||
|
|
||||||
|
if (dev) {
|
||||||
|
// you have absolutely no idea what a difference this makes for performance
|
||||||
|
console.assert(scale % 1 === 0, "Scale must be an integer")
|
||||||
|
console.assert((scale / 2) % 1 === 0, "Scale must be divisible by 2")
|
||||||
|
console.assert(strokeWidth % 1 === 0, "Stroke must be an integer")
|
||||||
|
console.assert(margin % 1 === 0, "Margin must be an integer")
|
||||||
|
console.assert(fontSize % 1 === 0, "Font size must be an integer")
|
||||||
|
console.assert(iconFontSize % 1 === 0, "Icon font size must be an integer")
|
||||||
|
}
|
||||||
|
|
||||||
|
const layoutInfo = compileLayout(rawLayout as VisualLayout)
|
||||||
|
|
||||||
|
function getCenter(key: CompiledLayoutKey): [x: number, y: number] {
|
||||||
|
return [key.pos[0] + key.size[0] / 2, key.pos[1] + key.size[1] / 2]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDistance(a: CompiledLayoutKey, b: CompiledLayoutKey) {
|
||||||
|
const x1 = a.pos[0] + margin
|
||||||
|
const y1 = a.pos[1] + margin
|
||||||
|
const x1b = x1 + a.size[0] - margin
|
||||||
|
const y1b = y1 + a.size[1] - margin
|
||||||
|
const x2 = b.pos[0] + margin
|
||||||
|
const y2 = b.pos[1] + margin
|
||||||
|
const x2b = x2 + b.size[0] - margin
|
||||||
|
const y2b = y2 + b.size[1] - margin
|
||||||
|
|
||||||
|
const left = x2b < x1
|
||||||
|
const right = x1b < x2
|
||||||
|
const bottom = y2b < y1
|
||||||
|
const top = y1b < y2
|
||||||
|
|
||||||
|
return top && left
|
||||||
|
? Math.sqrt((x1 - x2b) ** 2 + (y1b - y2) ** 2)
|
||||||
|
: left && bottom
|
||||||
|
? Math.sqrt((x1 - x2b) ** 2 + (y1 - y2b) ** 2)
|
||||||
|
: bottom && right
|
||||||
|
? Math.sqrt((x1b - x2) ** 2 + (y1 - y2b) ** 2)
|
||||||
|
: right && top
|
||||||
|
? Math.sqrt((x1b - x2) ** 2 + (y1b - y2) ** 2)
|
||||||
|
: left
|
||||||
|
? x1 - x2b
|
||||||
|
: right
|
||||||
|
? x2 - x1b
|
||||||
|
: bottom
|
||||||
|
? y1 - y2b
|
||||||
|
: top
|
||||||
|
? y2 - y1b
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function navigate(event: KeyboardEvent) {
|
||||||
|
if (event.altKey || event.ctrlKey || event.shiftKey || event.metaKey) return
|
||||||
|
|
||||||
|
let wantedAngle: number
|
||||||
|
const angleThreshold = Math.PI
|
||||||
|
|
||||||
|
if (event.key === "ArrowUp") wantedAngle = Math.PI
|
||||||
|
else if (event.key === "ArrowDown") wantedAngle = 0
|
||||||
|
else if (event.key === "ArrowRight") wantedAngle = Math.PI / 2
|
||||||
|
else if (event.key === "ArrowLeft") wantedAngle = -Math.PI / 2
|
||||||
|
else return
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
if (!focusKey) (groupParent.firstChild as SVGGElement).focus()
|
||||||
|
const [focusX, focusY] = getCenter(focusKey)
|
||||||
|
|
||||||
|
let bestDistance = Infinity
|
||||||
|
let bestCandidate = 0
|
||||||
|
let isOptimalAngle = false
|
||||||
|
|
||||||
|
for (const [i, key] of layoutInfo.keys.entries()) {
|
||||||
|
if (key === focusKey) continue
|
||||||
|
const [keyX, keyY] = getCenter(key)
|
||||||
|
const deltaX = keyX - focusX
|
||||||
|
const deltaY = keyY - focusY
|
||||||
|
const angle = Math.atan2(deltaX, deltaY)
|
||||||
|
const distance = getDistance(key, focusKey)
|
||||||
|
|
||||||
|
const angleDelta = Math.abs(wantedAngle - angle)
|
||||||
|
|
||||||
|
if (isOptimalAngle ? angleDelta > Number.EPSILON : angleDelta >= angleThreshold) continue
|
||||||
|
if (distance > bestDistance) continue
|
||||||
|
|
||||||
|
bestDistance = distance
|
||||||
|
bestCandidate = i
|
||||||
|
isOptimalAngle = angleDelta <= Number.EPSILON
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = groupParent.children.item(bestCandidate)
|
||||||
|
if (node instanceof SVGGElement) {
|
||||||
|
node.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActions(layer: number, id: number, layout: CharaLayout, changes: Change[]): [KeyInfo, boolean] {
|
||||||
|
const actionId = layout?.[layer][id]
|
||||||
|
const changedId = changes.findLast(it => it?.layout?.[layer]?.[id] !== undefined)?.layout?.[layer]?.[id]
|
||||||
|
if (changedId !== undefined) {
|
||||||
|
return [KEYMAP_CODES[changedId], true]
|
||||||
|
} else {
|
||||||
|
return [KEYMAP_CODES[actionId], false]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function edit(index: number) {
|
||||||
|
const keyInfo = layoutInfo.keys[index]
|
||||||
|
const clickedGroup = groupParent.children.item(index) as SVGGElement
|
||||||
|
const component = new ActionSelector({
|
||||||
|
target: document.body,
|
||||||
|
props: {currentAction: get(layout)[activeLayer][keyInfo.id]},
|
||||||
|
})
|
||||||
|
const dialog = document.querySelector("dialog > div") as HTMLDivElement
|
||||||
|
const backdrop = document.querySelector("dialog") as HTMLDialogElement
|
||||||
|
const dialogRect = dialog.getBoundingClientRect()
|
||||||
|
const groupRect = clickedGroup.getBoundingClientRect()
|
||||||
|
|
||||||
|
const scale = 0.5
|
||||||
|
const dialogScale = `${1 - scale * (1 - groupRect.width / dialogRect.width)} ${
|
||||||
|
1 - scale * (1 - groupRect.height / dialogRect.height)
|
||||||
|
}`
|
||||||
|
const dialogTranslate = `${scale * (groupRect.x - dialogRect.x)}px ${
|
||||||
|
scale * (groupRect.y - dialogRect.y)
|
||||||
|
}px`
|
||||||
|
|
||||||
|
const duration = 150
|
||||||
|
const options = {duration, easing: "ease"}
|
||||||
|
const dialogAnimation = dialog.animate(
|
||||||
|
[
|
||||||
|
{scale: dialogScale, translate: dialogTranslate},
|
||||||
|
{translate: "0 0", scale: "1"},
|
||||||
|
],
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
const backdropAnimation = backdrop.animate([{opacity: 0}, {opacity: 1}], options)
|
||||||
|
|
||||||
|
async function closed() {
|
||||||
|
dialogAnimation.reverse()
|
||||||
|
backdropAnimation.reverse()
|
||||||
|
|
||||||
|
await dialogAnimation.finished
|
||||||
|
|
||||||
|
component.$destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
component.$on("close", closed)
|
||||||
|
component.$on("select", ({detail}) => {
|
||||||
|
changes.update(changes => {
|
||||||
|
changes.push({layout: {[activeLayer]: {[keyInfo.id]: detail}}})
|
||||||
|
return changes
|
||||||
|
})
|
||||||
|
closed()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let focusKey: CompiledLayoutKey
|
||||||
|
let groupParent: SVGElement
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:keydown={navigate} />
|
||||||
|
|
||||||
|
<p>{layoutInfo.name}</p>
|
||||||
|
<svg viewBox="0 0 {layoutInfo.size[0] * scale} {layoutInfo.size[1] * scale}" bind:this={groupParent}>
|
||||||
|
{#each layoutInfo.keys as key, i}
|
||||||
|
{@const posX = key.pos[0] * scale}
|
||||||
|
{@const posY = key.pos[1] * scale}
|
||||||
|
{@const sizeX = key.size[0] * scale}
|
||||||
|
{@const sizeY = key.size[1] * scale}
|
||||||
|
{@const middleX = sizeX / 2}
|
||||||
|
{@const middleY = sizeY / 2}
|
||||||
|
<g
|
||||||
|
class="key-group"
|
||||||
|
on:click={() => edit(i)}
|
||||||
|
on:keypress={({key}) => {
|
||||||
|
if (key === "Enter") {
|
||||||
|
edit(i)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:focusin={() => (focusKey = key)}
|
||||||
|
role="button"
|
||||||
|
tabindex={i + 1}
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x={posX + margin}
|
||||||
|
y={posY + margin}
|
||||||
|
rx={margin}
|
||||||
|
width={sizeX - margin * 2}
|
||||||
|
height={sizeY - margin * 2}
|
||||||
|
stroke="currentcolor"
|
||||||
|
stroke-width={strokeWidth}
|
||||||
|
/>
|
||||||
|
{#each [1, 2, 0] as layer, i}
|
||||||
|
{@const [action, changed] = getActions(layer, key.id, $layout, $changes)}
|
||||||
|
{@const isActive = layer === activeLayer}
|
||||||
|
{@const direction = [
|
||||||
|
(middleX - margin * 3) / (i % 2 === 0 ? -1 : 1),
|
||||||
|
(middleY - margin * 3) / (i < 2 ? -1 : 1),
|
||||||
|
]}
|
||||||
|
{@const layerFontSize = action?.icon ? iconFontSize : fontSize}
|
||||||
|
<g
|
||||||
|
style="transform: {isActive
|
||||||
|
? `translate3d(0, 0, 0) scale(1)`
|
||||||
|
: `translate3d(${direction[0]}px, ${direction[1]}px, 0) scale(${inactiveScale})`}"
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
fill={changed ? "var(--md-sys-color-primary)" : "currentcolor"}
|
||||||
|
text-anchor="middle"
|
||||||
|
alignment-baseline="central"
|
||||||
|
x={posX + middleX + (changed ? fontSize / 3 : 0)}
|
||||||
|
y={posY + middleY}
|
||||||
|
font-size={layerFontSize}
|
||||||
|
font-family={action?.icon ? "Material Symbols Rounded" : undefined}
|
||||||
|
opacity={isActive ? 1 : inactiveOpacity}
|
||||||
|
>
|
||||||
|
{action?.icon || action?.id || action?.code || `{${key.id}}`}
|
||||||
|
{#if changed}
|
||||||
|
<tspan font-weight="bold">•</tspan>
|
||||||
|
{/if}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
{/each}
|
||||||
|
</g>
|
||||||
|
{/each}
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
$focus-transition: 10ms;
|
||||||
|
$transition: 200ms;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
overflow: visible;
|
||||||
|
width: calc(min(100%, 35cm));
|
||||||
|
max-height: calc(100% - 170px);
|
||||||
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
transition:
|
||||||
|
fill #{$focus-transition} ease,
|
||||||
|
opacity #{$transition} ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect {
|
||||||
|
fill: var(--md-sys-color-background);
|
||||||
|
transition:
|
||||||
|
fill #{$focus-transition} ease,
|
||||||
|
stroke #{$focus-transition} ease,
|
||||||
|
fill-opacity #{$focus-transition} ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
g {
|
||||||
|
transform-origin: center;
|
||||||
|
transform-box: fill-box;
|
||||||
|
transition:
|
||||||
|
fill #{$focus-transition} ease,
|
||||||
|
opacity #{$transition} ease,
|
||||||
|
transform #{$transition} ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-group:hover {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity #{$transition} ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key-group:focus-within {
|
||||||
|
color: var(--md-sys-color-primary);
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
> rect {
|
||||||
|
outline: none;
|
||||||
|
fill: currentcolor;
|
||||||
|
fill-opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import {serialPort} from "$lib/serial/connection"
|
import {serialPort} from "$lib/serial/connection"
|
||||||
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
|
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
|
||||||
import {action} from "$lib/title"
|
import {action} from "$lib/title"
|
||||||
|
import GenericLayout from "$lib/components/layout/GenericLayout.svelte"
|
||||||
|
|
||||||
$: device = $serialPort?.device ?? "ONE"
|
$: device = $serialPort?.device ?? "ONE"
|
||||||
let activeLayer = 0
|
let activeLayer = 0
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
] as const
|
] as const
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div class="container">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{#each layers as [title, icon, value]}
|
{#each layers as [title, icon, value]}
|
||||||
<button
|
<button
|
||||||
@@ -28,13 +29,25 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{#if device === "ONE"}
|
{#if device === "ONE"}
|
||||||
<LayoutCC1 bind:activeLayer />
|
<GenericLayout bind:activeLayer />
|
||||||
|
<!-- <LayoutCC1 bind:activeLayer /> -->
|
||||||
{:else}
|
{:else}
|
||||||
<p>Unsupported device ({$serialPort?.device})</p>
|
<p>Unsupported device ({$serialPort?.device})</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-bottom: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|||||||
58
src/lib/serialization/visual-layout.ts
Normal file
58
src/lib/serialization/visual-layout.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
export interface VisualLayout {
|
||||||
|
name: string
|
||||||
|
row: VisualLayoutRow[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualLayoutRow {
|
||||||
|
col: VisualLayoutKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualLayoutKey {
|
||||||
|
id: number
|
||||||
|
size?: [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompiledLayout {
|
||||||
|
name: string
|
||||||
|
size: [number, number]
|
||||||
|
keys: CompiledLayoutKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompiledLayoutKey {
|
||||||
|
id: number
|
||||||
|
type: "key" | "dpad"
|
||||||
|
size: [number, number]
|
||||||
|
pos: [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileLayout(layout: VisualLayout): CompiledLayout {
|
||||||
|
const compiled: CompiledLayout = {
|
||||||
|
name: layout.name,
|
||||||
|
size: [0, 0],
|
||||||
|
keys: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
let y = 0
|
||||||
|
for (const {col} of layout.row) {
|
||||||
|
let x = 0
|
||||||
|
let maxHeight = 0
|
||||||
|
for (const {id, size} of col) {
|
||||||
|
const [width, height] = size ?? [1, 1]
|
||||||
|
|
||||||
|
compiled.keys.push({
|
||||||
|
id,
|
||||||
|
type: "key",
|
||||||
|
size: [width, height],
|
||||||
|
pos: [x, y],
|
||||||
|
})
|
||||||
|
|
||||||
|
x += width
|
||||||
|
maxHeight = Math.max(maxHeight, height)
|
||||||
|
}
|
||||||
|
y += maxHeight
|
||||||
|
compiled.size[0] = Math.max(compiled.size[0], x)
|
||||||
|
}
|
||||||
|
compiled.size[1] = y
|
||||||
|
|
||||||
|
return compiled
|
||||||
|
}
|
||||||
@@ -59,20 +59,24 @@
|
|||||||
<svelte:window use:share={shareLayout} />
|
<svelte:window use:share={shareLayout} />
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<label class="icon"
|
<!-- <label class="icon"
|
||||||
>upload_file<input
|
>upload_file<input
|
||||||
bind:this={fileInput}
|
bind:this={fileInput}
|
||||||
on:input={importLayout}
|
on:input={importLayout}
|
||||||
type="file"
|
type="file"
|
||||||
accept="text/csv, application/json"
|
accept="text/csv, application/json"
|
||||||
/></label
|
/></label
|
||||||
>
|
> -->
|
||||||
<Layout />
|
<Layout />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
section {
|
section {
|
||||||
margin: auto;
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
|
|||||||
Reference in New Issue
Block a user