Merge branch 'import-component'

docks66-json-schema
Yanick Champoux 2023-05-09 12:07:03 -04:00
commit 6b999a5a82
51 changed files with 285 additions and 156 deletions

View File

@ -7,7 +7,7 @@
<div class="tabs in left-align"> <div class="tabs in left-align">
<a <a
class="spaced" class="spaced"
class:active={currentPath.startsWith("/editor")} class:active={!currentPath.startsWith("/about")}
href="/editor">Editor</a href="/editor">Editor</a
> >
<!-- <!--
@ -17,11 +17,6 @@
href="/import">Import</a href="/import">Import</a
> >
--> -->
<a
class="spaced"
class:active={currentPath.startsWith("/export")}
href="/export/print">Export</a
>
<a <a
class="spaced" class="spaced"
class:active={currentPath.startsWith("/about")} class:active={currentPath.startsWith("/about")}

View File

@ -8,6 +8,8 @@
{/if} {/if}
<script> <script>
import { getContext } from "svelte";
import Identification from "./ShipEdit/Identification.svelte"; import Identification from "./ShipEdit/Identification.svelte";
import Propulsion from "./ShipEdit/Propulsion.svelte"; import Propulsion from "./ShipEdit/Propulsion.svelte";
import shipDux from "$lib/store/ship"; import shipDux from "$lib/store/ship";
@ -20,6 +22,8 @@
export let structure = {}; export let structure = {};
export let weaponry = {}; export let weaponry = {};
export let carrier = {}; export let carrier = {};
const api = getContext("api");
</script> </script>
<style> <style>
@ -28,4 +32,6 @@
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
.reset {
}
</style> </style>

View File

@ -28,6 +28,7 @@
import Field from "$lib/components/Field.svelte"; import Field from "$lib/components/Field.svelte";
import { candidateShipTypes } from "./Identification/shipTypes.js"; import { candidateShipTypes } from "./Identification/shipTypes.js";
import ShipCost from "./Identification/ShipCost.svelte"; import ShipCost from "./Identification/ShipCost.svelte";
import shipDux from "$lib/store/ship";
export let shipClass = ""; export let shipClass = "";
export let shipType = ""; export let shipType = "";

View File

@ -19,117 +19,121 @@ import { adfcDux } from "./ship/weaponry/adfc";
import { weaponsDux } from "./ship/weaponry/weapons"; import { weaponsDux } from "./ship/weaponry/weapons";
if (typeof process !== "undefined") { if (typeof process !== "undefined") {
process.env.UPDEEP_MODE = "dangerously_never_freeze"; process.env.UPDEEP_MODE = "dangerously_never_freeze";
} }
const structure = new Updux({ const structure = new Updux({
initialState: {}, initialState: {},
subduxes: { subduxes: {
streamlining, streamlining,
cargo: cargoDux, cargo: cargoDux,
hull: hullDux, hull: hullDux,
screens: screensDux, screens: screensDux,
armor: armorDux, armor: armorDux,
carrier: carrierDux, carrier: carrierDux,
}, },
}); });
const propulsion = new Updux({ const propulsion = new Updux({
initialState: {}, initialState: {},
subduxes: { subduxes: {
ftl, ftl,
drive, drive,
}, },
}); });
const weaponry = new Updux({ const weaponry = new Updux({
initialState: {}, initialState: {},
subduxes: { subduxes: {
adfc: adfcDux, adfc: adfcDux,
firecons: fireconsDux, firecons: fireconsDux,
weapons: weaponsDux, weapons: weaponsDux,
}, },
}); });
const restore = createPayloadAction<typeof shipDux.initialState>("restore"); const restore = createPayloadAction<typeof shipDux.initialState>("restore");
const importShip =
createPayloadAction<typeof shipDux.initialState>("importShip");
const shipDux = new Updux({ const shipDux = new Updux({
actions: { actions: {
restore, restore,
}, importShip,
initialState: { },
schemaVersion: "1", initialState: {
}, schemaVersion: "1",
subduxes: { },
identification, subduxes: {
structure, identification,
propulsion, structure,
carrier: carrierDux, propulsion,
weaponry, carrier: carrierDux,
}, weaponry,
},
}); });
shipDux.addMutation(restore, (state) => () => state); shipDux.addMutation(restore, (state) => () => state);
shipDux.addMutation(importShip, (state) => () => state);
shipDux.addReaction((api) => { shipDux.addReaction((api) => {
return createSelector( return createSelector(
api.selectors.getFtlType, api.selectors.getFtlType,
api.selectors.getShipMass, api.selectors.getShipMass,
(type, mass) => api.dispatch.setFtlReqs(calcFtlReqs(type, mass)) (type, mass) => api.dispatch.setFtlReqs(calcFtlReqs(type, mass))
); );
}); });
shipDux.addReaction((api) => { shipDux.addReaction((api) => {
const setShipReqs = memoize((cost, usedMass) => const setShipReqs = memoize((cost, usedMass) =>
api.dispatch.setShipReqs({ cost, usedMass }) api.dispatch.setShipReqs({ cost, usedMass })
); );
return (state) => { return (state) => {
let cost = 0; let cost = 0;
let mass = 0; let mass = 0;
let subsystems = R.values(R.omit(state, ["identification"])); let subsystems = R.values(R.omit(state, ["identification"]));
while (subsystems.length > 0) { while (subsystems.length > 0) {
const subsystem = subsystems.shift(); const subsystem = subsystems.shift();
if (typeof subsystem !== "object") continue; if (typeof subsystem !== "object") continue;
if (subsystem.reqs) { if (subsystem.reqs) {
cost += subsystem.reqs.cost ?? 0; cost += subsystem.reqs.cost ?? 0;
mass += subsystem.reqs.mass ?? 0; mass += subsystem.reqs.mass ?? 0;
} }
subsystems.push(...Object.values(subsystem)); subsystems.push(...Object.values(subsystem));
} }
if (Number.isNaN(cost)) { if (Number.isNaN(cost)) {
console.log(state.weaponry.weapons); console.log(state.weaponry.weapons);
throw new Error(); throw new Error();
} }
setShipReqs(cost, mass); setShipReqs(cost, mass);
}; };
}); });
shipDux.addReaction((api) => shipDux.addReaction((api) =>
createSelector( createSelector(
api.selectors.getShipMass, api.selectors.getShipMass,
(state) => state.propulsion.drive.rating, (state) => state.propulsion.drive.rating,
(state) => state.propulsion.drive.advanced, (state) => state.propulsion.drive.advanced,
(mass, rating, advanced) => (mass, rating, advanced) =>
api.dispatch.setDriveReqs(calcDriveReqs(mass, rating, advanced)) api.dispatch.setDriveReqs(calcDriveReqs(mass, rating, advanced))
) )
); );
shipDux.addReaction((api) => shipDux.addReaction((api) =>
createSelector( createSelector(
// (state) => state, // (state) => state,
api.selectors.getShipMass, api.selectors.getShipMass,
api.selectors.getStreamlining, api.selectors.getStreamlining,
(mass, type) => { (mass, type) => {
api.dispatch.setStreamliningReqs(calcStreamliningReqs(type, mass)); api.dispatch.setStreamliningReqs(calcStreamliningReqs(type, mass));
} }
) )
); );
shipDux.addReaction(screensReqsReaction); shipDux.addReaction(screensReqsReaction);

View File

@ -0,0 +1,42 @@
<nav class="m l left">
<!-- set them as active -->
<a href="/editor">
<i>edit</i>
<span>editor</span>
</a>
<a href="/export/print">
<i>print</i>
<span>print</span>
</a>
<a href="/export">
<i>output</i>
<span>export</span>
</a>
<a href="/import">
<i>input</i>
<span>import</span>
</a>
<a on:click={handleReset}>
<i>restart_alt</i>
<span>reset</span>
</a>
</nav>
<slot />
<script>
import { getContext } from "svelte";
import shipDux from "$lib/store/ship";
export let api = getContext("api");
async function handleReset() {
if (!(await window.confirm("really reset the ship?"))) return;
api?.dispatch?.importShip(shipDux.initialState);
}
</script>
<style>
nav {
margin-top: 70px;
}
</style>

View File

@ -0,0 +1,11 @@
<Serialized data={$shipData} />
<script>
import { getContext } from "svelte";
import Serialized from "./Serialized.svelte";
const api = getContext("api");
let shipData = api.svelteStore;
</script>

View File

@ -6,16 +6,25 @@
on:error={copyError}>{copyLabel} <i>content_paste</i></button on:error={copyError}>{copyLabel} <i>content_paste</i></button
> >
<button on:click={handleSave}>download <i>download</i></button> <button on:click={handleSave}>download <i>download</i></button>
<div class="field suffix border">
<select bind:value={format}>
<option>json</option>
<option>yaml</option>
</select>
<i>arrow_drop_down</i>
</div>
</nav> </nav>
<pre><code>{data}</code></pre> <pre><code>{serialized}</code></pre>
<a hidden {href} {download} bind:this={fileDownload} /> <a hidden {href} {download} bind:this={fileDownload} />
</article> </article>
<script> <script>
import { clipboard } from "$lib/actions/clipboard.js"; import { clipboard } from "$lib/actions/clipboard.js";
import yaml from "yaml";
export let data = "Loading..."; export let data = {};
export let format; export let serialized = "Loading...";
export let format = "json";
let copyLabel = "clipboard"; let copyLabel = "clipboard";
@ -37,6 +46,14 @@
function handleSave() { function handleSave() {
fileDownload?.click(); fileDownload?.click();
} }
const serialize = async (data, format) => {
if (format === "json") return JSON.stringify(data, null, 2);
return yaml.stringify(data);
};
$: serialize(data, format).then((s) => (serialized = s));
</script> </script>
<style> <style>
@ -50,4 +67,15 @@
position: absolute; position: absolute;
right: 5em; right: 5em;
} }
.my-switch {
display: flex;
align-items: center;
}
.my-switch > span {
display: inline-block;
font-size: var(--font-scale-10);
}
label {
margin: 0px 0.5rem;
}
</style> </style>

View File

@ -1,4 +1,4 @@
import { render } from "@testing-library/svelte"; import { render, waitFor } from "@testing-library/svelte";
import "@testing-library/jest-dom"; import "@testing-library/jest-dom";
import Serialized from "./Serialized.svelte"; import Serialized from "./Serialized.svelte";
@ -6,9 +6,8 @@ import Serialized from "./Serialized.svelte";
test("basic", () => { test("basic", () => {
const { getByText } = render(Serialized, { const { getByText } = render(Serialized, {
props: { props: {
data: "hello world", data: { greeting: "hello world" },
format: "json",
}, },
}); });
expect(getByText("hello world")).toBeInTheDocument(); waitFor(() => expect(getByText("hello world")).toBeInTheDocument());
}); });

View File

@ -0,0 +1,102 @@
<article>
<nav>
<button class="primary" disabled={error} on:click={handleSet}>set </button>
<div class="max" />
<div class="my-error">
{#if error}
invalid
{/if}
</div>
<button class="primary" on:click={() => fileInput.click()}
><i>upload</i> upload</button
>
</nav>
<div class="support">
both <code>json</code> and <code>yaml</code> formats are supported
</div>
<textarea rows="20" bind:value={stringData} />
<input
bind:this={fileInput}
bind:files
type="file"
on:change={handleUpload}
/>
</article>
<script>
import { clipboard } from "$lib/actions/clipboard.js";
import { getContext } from "svelte";
import yaml from "yaml";
import { goto } from "$app/navigation";
export let format = "json";
let fileInput;
let files;
const handleUpload = async () => {
stringData = await files[0].text();
};
const api = getContext("api");
let shipData = api.svelteStore;
let stringData = JSON.stringify($shipData, null, 2);
let serialized = {};
let error = false;
$: try {
error = false;
serialized = yaml.parse(stringData);
} catch (e) {
error = true;
}
async function handleSet() {
if (await window.confirm("import ship (and clobber the current one)?")) {
api?.dispatch?.importShip?.(serialized);
goto("/editor");
}
}
</script>
<style>
article {
background-color: var(--primary-container);
}
button {
font-size: var(--font-scale-10);
}
nav {
margin-bottom: 1em;
}
.my-switch {
display: flex;
align-items: center;
}
.my-switch > span {
display: inline-block;
font-size: var(--font-scale-10);
}
label {
margin: 0px 0.5rem;
}
textarea {
width: 100%;
height: 70vh;
}
.support {
margin-bottom: 1rem;
text-align: right;
}
.my-error {
color: red;
font-size: var(--font-scale-11);
}
input[type="file"] {
position: inherit;
visibility: hidden;
}
</style>

View File

@ -1,32 +0,0 @@
<nav class="m l left">
<!-- set them as active -->
<a href="/export/print">
<i>print</i>
<span>print</span>
</a>
<a href="/export/json">
<i>output</i>
<span>json</span>
</a>
<a href="/export/yaml">
<i>output</i>
<span>yaml</span>
</a>
<!-- TODO
<a>
<i>Quiz</i>
<span>See also</span>
</a>
<a>
<i>Format_List_Bulleted</i>
<span>Changelog</span>
</a>
-->
</nav>
<slot />
<style>
nav {
margin-top: 70px;
}
</style>

View File

@ -1,6 +0,0 @@
/** @type {import('./$types').PageLoad} */
export function load({ params }) {
return {
format: params.format,
};
}

View File

@ -1,21 +0,0 @@
<Serialized data={serializedShip} format={data.format} />
<script>
import { getContext } from "svelte";
import Serialized from "./Serialized.svelte";
const api = getContext("api");
let shipData = api.svelteStore;
export let data;
const serialize = async (data, format) => {
if (format === "json") return JSON.stringify(data, null, 2);
return import("yaml").then(({ stringify }) => stringify(data));
};
let serializedShip;
$: serialize($shipData, data.format).then((s) => (serializedShip = s));
</script>

View File

@ -4,14 +4,14 @@ import packageJson from "./package.json";
/** @type {import('vite').UserConfig} */ /** @type {import('vite').UserConfig} */
const config = { const config = {
plugins: [sveltekit()], plugins: [sveltekit()],
publicDir: "./static", // publicDir: "./static",
ssr: {}, ssr: {},
optimizeDeps: {}, optimizeDeps: {},
define: { define: {
"import.meta.env.PACKAGE_VERSION": JSON.stringify(packageJson.version), "import.meta.env.PACKAGE_VERSION": JSON.stringify(packageJson.version),
"import.meta.env.HOMEPAGE": JSON.stringify(packageJson.homepage), "import.meta.env.HOMEPAGE": JSON.stringify(packageJson.homepage),
}, },
}; };
export default config; export default config;