263 lines
7.2 KiB
Svelte
263 lines
7.2 KiB
Svelte
<script lang='ts'>
|
|
import { GetFilePath, OpenFileBytes } from '../../wailsjs/go/main/App.js'
|
|
import { data } from '../../wailsjs/go/models.js'
|
|
import { onMount } from 'svelte'
|
|
|
|
import { Button, NumberInput, Checkbox, RadioButtonGroup, RadioButton } from 'carbon-components-svelte'
|
|
import { Form, FormGroup, InlineNotification, Tile, Truncate } from 'carbon-components-svelte'
|
|
import { Grid, Row, Column } from "carbon-components-svelte"
|
|
|
|
import {
|
|
StructuredList,
|
|
StructuredListHead,
|
|
StructuredListRow,
|
|
StructuredListCell,
|
|
StructuredListBody,
|
|
} from "carbon-components-svelte"
|
|
|
|
import {
|
|
ComposedModal,
|
|
ModalHeader,
|
|
ModalBody,
|
|
ModalFooter,
|
|
} from "carbon-components-svelte"
|
|
|
|
export let valid: boolean = false
|
|
export let open: boolean = false
|
|
|
|
let width: number = 16
|
|
let height: number = 16
|
|
let rowBasedFrames: boolean = true
|
|
export let file: data.StackistFileV1
|
|
export let filepath: string = ''
|
|
export let img: HTMLImageElement
|
|
let path: string = ''
|
|
let error: string = ""
|
|
let error2: string = ""
|
|
let cols: number = 0
|
|
let rows: number = 0
|
|
let importFramesAs: 'groups' | 'animations' = 'groups'
|
|
let groups: number = 0
|
|
let animations: number = 0
|
|
|
|
function loadImage(base64: number[]): Promise<HTMLImageElement> {
|
|
return new Promise((resolve, reject) => {
|
|
img = new Image()
|
|
img.onload = () => resolve(img)
|
|
img.onerror = reject
|
|
img.src = `data:image/png;base64,${base64}`
|
|
})
|
|
}
|
|
|
|
async function openFile() {
|
|
try {
|
|
filepath = await GetFilePath()
|
|
let bytes = await OpenFileBytes(filepath)
|
|
path = /[^/\\]*$/.exec(filepath)[0]
|
|
img = await loadImage(bytes)
|
|
recalc()
|
|
} catch(err) {
|
|
error = "open"
|
|
error2 = err
|
|
}
|
|
}
|
|
|
|
function recalc() {
|
|
error = ""
|
|
error2 = ""
|
|
if (!img || !img.width || !img.height) {
|
|
error = "no image"
|
|
error2 = "please load an image"
|
|
return
|
|
}
|
|
|
|
rows = img.height / height
|
|
cols = img.width / width
|
|
|
|
if (rows % 1 !== 0) {
|
|
error = 'layer size'
|
|
error2 = 'invalid row count of: ' + rows
|
|
return
|
|
}
|
|
if (cols % 1 !== 0) {
|
|
error = 'layer size'
|
|
error2 = 'invalid cols count of: ' + cols
|
|
return
|
|
}
|
|
|
|
groups = 0
|
|
animations = 0
|
|
if (importFramesAs === 'groups') {
|
|
groups = rowBasedFrames ? rows : cols
|
|
animations = 1
|
|
} else {
|
|
groups = 1
|
|
animations = rowBasedFrames ? rows : cols
|
|
}
|
|
remakeFile()
|
|
}
|
|
|
|
function remakeFile() {
|
|
file = data.StackistFileV1.createFrom({
|
|
width: width,
|
|
height: height,
|
|
groups: {}
|
|
})
|
|
let cx = 0
|
|
let cy = 0
|
|
for (let gi = 0; gi < groups; gi++) {
|
|
let group: data.Group = data.Group.createFrom({
|
|
animations: {},
|
|
})
|
|
for (let ai = 0; ai < animations; ai++) {
|
|
let frames = []
|
|
let layers = []
|
|
for (let i = 0; i < (!rowBasedFrames ? rows : cols); i++) {
|
|
layers.push({
|
|
x: cx*width,
|
|
y: cy*height,
|
|
shadingMultiplier: 1.0,
|
|
})
|
|
if (rowBasedFrames) {
|
|
cx++
|
|
} else {
|
|
cy++
|
|
}
|
|
}
|
|
frames.push({
|
|
layers,
|
|
})
|
|
|
|
if (rowBasedFrames) {
|
|
cy++
|
|
cx = 0
|
|
} else {
|
|
cx++
|
|
cy = 0
|
|
}
|
|
|
|
let animation: data.Animation = data.Animation.createFrom({
|
|
frames,
|
|
time: 100,
|
|
})
|
|
group.animations["animation "+ai] = animation
|
|
}
|
|
file.groups["group "+gi] = group
|
|
}
|
|
}
|
|
|
|
$: valid = error === ""
|
|
|
|
onMount(() => {
|
|
recalc()
|
|
})
|
|
</script>
|
|
|
|
<ModalHeader label="Import from PNG"/>
|
|
<ModalBody>
|
|
<Form on:submit={e=>e.preventDefault()}>
|
|
<FormGroup legendText="Import" invalid={!img||!img.width||!img.height}>
|
|
<Grid condensed>
|
|
<Row>
|
|
<Column>
|
|
<Button kind='secondary' on:click={openFile}>Open Image</Button>
|
|
<Truncate clamp="front">{path}</Truncate>
|
|
</Column>
|
|
<Column>
|
|
<img src={img?.src} alt=''>
|
|
<Tile> {img?.width} x {img?.height} </Tile>
|
|
</Column>
|
|
</Row>
|
|
</Grid>
|
|
</FormGroup>
|
|
<FormGroup legendText="Options">
|
|
<Grid condensed narrow>
|
|
<Row condensed narrow>
|
|
<Column>
|
|
<NumberInput
|
|
size="sm"
|
|
min={1}
|
|
bind:value={width}
|
|
on:change={recalc}
|
|
invalidText="Minimum of 1, yo."
|
|
label="Layer Width"
|
|
/>
|
|
<NumberInput
|
|
size="sm"
|
|
min={1}
|
|
bind:value={height}
|
|
on:change={recalc}
|
|
invalidText="Minimum of 1, yo."
|
|
label="Layer Height"
|
|
/>
|
|
<Checkbox
|
|
bind:checked={rowBasedFrames}
|
|
on:change={recalc}
|
|
labelText="row based animation frames"
|
|
/>
|
|
<RadioButtonGroup
|
|
legendText="Import animation frames as"
|
|
bind:selected={importFramesAs}
|
|
on:change={recalc}
|
|
>
|
|
<RadioButton labelText="groups" value="groups" />
|
|
<RadioButton labelText="animations" value="animations" />
|
|
</RadioButtonGroup>
|
|
</Column>
|
|
<Column>
|
|
<StructuredList condensed>
|
|
<StructuredListHead>
|
|
<StructuredListRow head>
|
|
<StructuredListCell head>Columns</StructuredListCell>
|
|
<StructuredListCell head>Rows</StructuredListCell>
|
|
</StructuredListRow>
|
|
</StructuredListHead>
|
|
<StructuredListBody>
|
|
<StructuredListRow>
|
|
<StructuredListCell noWrap>{cols}</StructuredListCell>
|
|
<StructuredListCell>{rows}</StructuredListCell>
|
|
</StructuredListRow>
|
|
</StructuredListBody>
|
|
<StructuredListHead>
|
|
<StructuredListRow head>
|
|
<StructuredListCell head>Groups</StructuredListCell>
|
|
<StructuredListCell head>Animations</StructuredListCell>
|
|
</StructuredListRow>
|
|
</StructuredListHead>
|
|
<StructuredListBody>
|
|
<StructuredListRow>
|
|
<StructuredListCell noWrap>{groups}</StructuredListCell>
|
|
<StructuredListCell>{animations}</StructuredListCell>
|
|
</StructuredListRow>
|
|
</StructuredListBody>
|
|
</StructuredList>
|
|
</Column>
|
|
</Row>
|
|
</Grid>
|
|
</FormGroup>
|
|
</Form>
|
|
{#if error}
|
|
<InlineNotification
|
|
kind="error"
|
|
title={error}
|
|
subtitle={error2}
|
|
/>
|
|
{/if}
|
|
</ModalBody>
|
|
<ModalFooter
|
|
primaryButtonText="Import"
|
|
secondaryButtonText="Cancel"
|
|
primaryButtonDisabled={!valid}
|
|
on:click:button--secondary={() => open = false}
|
|
/>
|
|
|
|
<style>
|
|
img {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
border: .5em solid black;
|
|
background-image: linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%);
|
|
background-size: 16px 16px;
|
|
background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
|
|
}
|
|
</style> |