Merge branch 'carrier-identification'

docks66-json-schema
Yanick Champoux 2023-04-22 11:05:40 -04:00
commit d28ef38b2b
16 changed files with 201 additions and 136 deletions

View File

@ -3,7 +3,6 @@
version: "3" version: "3"
vars: vars:
GREETING: Hello, World!
PARENT_BRANCH: main PARENT_BRANCH: main
tasks: tasks:
@ -14,6 +13,12 @@ tasks:
cmds: cmds:
- vitest src - vitest src
test:update:
deps:
- histoire:build
cmds:
- vitest run -u src
check: check:
deps: deps:
- histoire:build - histoire:build

View File

@ -3,17 +3,23 @@
<Structure {...structure} /> <Structure {...structure} />
<Weaponry {...weaponry} /> <Weaponry {...weaponry} />
{#if identification.isCarrier}
<Carrier {...carrier} />
{/if}
<script> <script>
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";
import Structure from "./ShipEdit/Structure.svelte"; import Structure from "./ShipEdit/Structure.svelte";
import Weaponry from "./ShipEdit/Weaponry.svelte"; import Weaponry from "./ShipEdit/Weaponry.svelte";
import Carrier from "./ShipEdit/Carrier.svelte";
export let identification = {}; export let identification = {};
export let propulsion = {}; export let propulsion = {};
export let structure = {}; export let structure = {};
export let weaponry = {}; export let weaponry = {};
export let carrier = {};
</script> </script>
<style> <style>

View File

@ -1,16 +1,18 @@
<Section label="carrier"> <Section label="carrier">
<ShipItem {...reqs}> <div>
<Field label="bays"> <ShipItem {...reqs}>
<input class="short" type="number" min="0" bind:value={bays} /> <Field label="bays">
</Field> <input class="short" type="number" min="1" bind:value={bays} />
</ShipItem> </Field>
</ShipItem>
</div>
{#each squadrons as squadron, id (id)} {#each squadrons as squadron, id (id)}
<Squadron {...squadron} id={id + 1} /> <Squadron {...squadron} id={id + 1} />
{/each} {/each}
</Section> </Section>
<script lang="ts"> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
import Field from "$lib/components/Field.svelte"; import Field from "$lib/components/Field.svelte";
@ -18,13 +20,15 @@
import Section from "$lib/components/Section.svelte"; import Section from "$lib/components/Section.svelte";
import Squadron from "./Carrier/Squadron.svelte"; import Squadron from "./Carrier/Squadron.svelte";
export let bays = 0; export let bays = 1;
export let reqs = { cost: 0, mass: 0 }; export let reqs = { cost: 0, mass: 0 };
export let squadrons = []; export let squadrons = [];
if (bays < 1) bays = 1;
export let api = getContext("api"); export let api = getContext("api");
$: api?.dispatch?.setCarrierBays?.(bays); $: api?.dispatch.setNbrCarrierBays(bays);
</script> </script>
<style> <style>

View File

@ -1,12 +1,14 @@
<ShipItem {...reqs}> <ShipItem {...reqs}>
<Field label={`squadron ${id}`}> <div class="field label suffix">
<select bind:value={type}> <select bind:value={type}>
{#each types as type (type)} {#each types as type (type)}
<option>{type}</option> <option>{type}</option>
{/each} {/each}
</select> </select>
</Field> <label class="active">squadron {id}</label>
</ShipItem> <i>arrow_drop_down</i>
</div></ShipItem
>
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
@ -14,7 +16,7 @@
import Section from "$lib/components/Section.svelte"; import Section from "$lib/components/Section.svelte";
import Field from "$lib/components/Field.svelte"; import Field from "$lib/components/Field.svelte";
import ShipItem from "$lib/components/ShipItem.svelte"; import ShipItem from "$lib/components/ShipItem.svelte";
// import { squadronTypes } from "$lib/shipDux/carrier"; import { squadronTypes } from "$lib/store/ship/carrier.ts";
const types = squadronTypes.map(({ type }) => type); const types = squadronTypes.map(({ type }) => type);
@ -30,6 +32,9 @@
<style> <style>
select { select {
width: inherit; /* width: inherit; */
}
div {
display: inline-block;
} }
</style> </style>

View File

@ -8,10 +8,18 @@
mass: 13, mass: 13,
usedMass: 9, usedMass: 9,
}} }}
{api}
/> />
</Hst.Story> </Hst.Story>
<script lang="ts"> <script lang="ts">
export let Hst; export let Hst;
import { logEvent } from "histoire/client";
import Identification from "./Identification.svelte"; import Identification from "./Identification.svelte";
const api = {
dispatch: {
setCarrier: (isCarrier) => logEvent("isCarrier", { isCarrier }),
},
};
</script> </script>

View File

@ -10,6 +10,14 @@
<label class="active">ship type</label> <label class="active">ship type</label>
<i>arrow_drop_down</i> <i>arrow_drop_down</i>
</Field> </Field>
<label class="switch icon">
<div>carrier</div>
<input type="checkbox" bind:checked={isCarrier} />
<span>
<i>airplanemode_inactive</i>
<i>airplanemode_active</i>
</span>
</label>
</div> </div>
<ShipCost {...reqs} /> <ShipCost {...reqs} />
</div> </div>
@ -26,6 +34,8 @@
export let isCarrier = false; export let isCarrier = false;
export let reqs = {}; export let reqs = {};
$: console.log("in the comp", isCarrier, shipClass);
export let api = getContext("api"); export let api = getContext("api");
$: shipTypes = candidateShipTypes(reqs.mass, isCarrier).map( $: shipTypes = candidateShipTypes(reqs.mass, isCarrier).map(
@ -36,6 +46,8 @@
shipType = shipTypes[0]; shipType = shipTypes[0];
$: api?.dispatch?.updateIdentification?.({ shipType, shipClass }); $: api?.dispatch?.updateIdentification?.({ shipType, shipClass });
$: api?.dispatch.setCarrier?.(isCarrier);
</script> </script>
<style> <style>
@ -48,8 +60,18 @@
display: flex; display: flex;
align-items: start; align-items: start;
} }
select {
min-width: 10em;
}
.identification-row :global(> *:first-child) { .identification-row :global(> *:first-child) {
flex: 1; flex: 1;
} }
label div {
font-size: var(--font-scale-10);
margin-right: 1em;
}
label.switch {
margin-left: 2em;
}
</style> </style>

View File

@ -0,0 +1,10 @@
import { render, fireEvent } from "@testing-library/svelte";
import "@testing-library/jest-dom";
import { tick } from "svelte";
import Identification from "./Identification.svelte";
test("carrier", () => {
const { getByText } = render(Identification);
expect(getByText("carrier")).toBeInTheDocument();
});

View File

@ -1,8 +1,9 @@
<Hst.Story> <Hst.Story title="ShipEdit/Propulsion/index">
<Propulsion /> <Propulsion />
</Hst.Story> </Hst.Story>
<script> <script>
export let Hst; export let Hst;
import Propulsion from "./Propulsion.svelte"; import Propulsion from "./Propulsion.svelte";
</script> </script>

View File

@ -1,44 +1,46 @@
<main> <main>
<div class="identification-row"> <div class="identification-row">
<Identification {...$shipState.identification} /> <Identification {...$shipState.identification} />
<ShipCost {...$shipState.reqs} /> <ShipCost {...$shipState.reqs} />
</div> </div>
<Propulsion propulsion={$shipState.propulsion}/> <Propulsion propulsion={$shipState.propulsion} />
<Structure {...$shipState.structure} /> <Structure {...$shipState.structure} />
<Weaponry {...$shipState.weaponry}/> <Weaponry {...$shipState.weaponry} />
<Carrier {...$shipState.carrier} /> {#if $shipState.identification.isCarrier}
</main> <Carrier {...$shipState.carrier} />
{/if}
</main>
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
import Identification from "./Identification/index.svelte"; import Identification from "./Identification/index.svelte";
import ShipCost from "./ShipCost.svelte"; import ShipCost from "./ShipCost.svelte";
import Propulsion from "./Propulsion/index.svelte"; import Propulsion from "./Propulsion/index.svelte";
import Structure from "./Structure/index.svelte"; import Structure from "./Structure/index.svelte";
import Carrier from "./Carrier/index.svelte"; import Carrier from "./Carrier/index.svelte";
import Weaponry from "./Weaponry/index.svelte"; import Weaponry from "./Weaponry/index.svelte";
const { state: shipState } = getContext("ship"); const { state: shipState } = getContext("ship");
</script> </script>
<style> <style>
.identification-row { .identification-row {
display: flex; display: flex;
} }
.identification-row :global(> *:first-child) { .identification-row :global(> *:first-child) {
flex: 1; flex: 1;
} }
main { main {
width: var(--main-width); width: var(--main-width);
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
</style> </style>

View File

@ -53,6 +53,14 @@ exports[`state has the expected shape 1`] = `
}, },
"space": 0, "space": 0,
}, },
"carrier": {
"nbrBays": 0,
"reqs": {
"cost": 0,
"mass": 0,
},
"squadrons": [],
},
"hull": { "hull": {
"max": 0, "max": 0,
"min": 0, "min": 0,

View File

@ -8,10 +8,15 @@ export const createApi = () => {
? JSON.parse(localStorage.getItem("ship") || "null") ? JSON.parse(localStorage.getItem("ship") || "null")
: undefined; : undefined;
const api = ship.createStore(state || undefined); const api = ship.createStore({
preloadedState: state,
});
api.dispatch.restore(state);
if (browser) { if (browser) {
api.subscribe(() => { api.subscribe(() => {
console.log("saving...", api.getState());
localStorage.setItem("ship", JSON.stringify(api.getState())); localStorage.setItem("ship", JSON.stringify(api.getState()));
}); });
} }

View File

@ -17,11 +17,13 @@ test("kicking the tires", () => {
store.dispatch.setNbrCarrierBays(2); store.dispatch.setNbrCarrierBays(2);
expect(store.getState().carrier.nbrBays).toEqual(2); expect(store.getState().carrier.nbrBays).toEqual(2);
expect(store.getState.isCarrier()).toBe(false);
store.dispatch.setCarrier(true);
store.dispatch.setSquadronType(2, "fast"); store.dispatch.setSquadronType(2, "fast");
expect(store.getState().carrier.squadrons[1]).toHaveProperty("type", "fast");
expect(store.getState.isCarrier()).toBe(true); expect(store.getState.isCarrier()).toBe(true);
expect(store.getState().carrier.squadrons[1]).toHaveProperty("type", "fast");
store.dispatch.setStreamlining("partial"); store.dispatch.setStreamlining("partial");

View File

@ -1,5 +1,5 @@
import { createSelector } from "@reduxjs/toolkit"; import { createSelector } from "@reduxjs/toolkit";
import Updux from "updux"; import Updux, { createPayloadAction } from "updux";
import * as R from "remeda"; import * as R from "remeda";
import memoize from "memoize-one"; import memoize from "memoize-one";
@ -30,6 +30,7 @@ const structure = new Updux({
hull: hullDux, hull: hullDux,
screens: screensDux, screens: screensDux,
armor: armorDux, armor: armorDux,
carrier: carrierDux,
}, },
}); });
@ -50,7 +51,12 @@ const weaponry = new Updux({
}, },
}); });
const restore = createPayloadAction<typeof shipDux.initialState>("restore");
const shipDux = new Updux({ const shipDux = new Updux({
actions: {
restore,
},
initialState: { initialState: {
schemaVersion: "1", schemaVersion: "1",
}, },
@ -63,6 +69,8 @@ const shipDux = new Updux({
}, },
}); });
shipDux.addMutation(restore, (state) => () => state);
shipDux.addReaction((api) => { shipDux.addReaction((api) => {
return createSelector( return createSelector(
api.selectors.getFtlType, api.selectors.getFtlType,

View File

@ -3,116 +3,89 @@ import u from "@yanick/updeep-remeda";
import { reqs, type Reqs } from "$lib/shipDux/reqs"; import { reqs, type Reqs } from "$lib/shipDux/reqs";
type Squadron = { type Squadron = {
type: string; type: string;
reqs: Reqs; reqs: Reqs;
}; };
const initialState = { const initialState = {
nbrBays: 0, nbrBays: 0,
squadrons: [] as Squadron[], squadrons: [] as Squadron[],
reqs, reqs,
}; };
export const squadronTypes = [ export const squadronTypes = [
{ type: "standard", cost: 3 }, { type: "standard", cost: 3 },
{ type: "fast", cost: 4 }, { type: "fast", cost: 4 },
{ type: "heavy", cost: 5 }, { type: "heavy", cost: 5 },
{ type: "interceptor", cost: 3 }, { type: "interceptor", cost: 3 },
{ type: "attack", cost: 4 }, { type: "attack", cost: 4 },
{ type: "long range", cost: 4 }, { type: "long range", cost: 4 },
{ type: "torpedo", cost: 6 }, { type: "torpedo", cost: 6 },
]; ];
const setNbrCarrierBays = createPayloadAction<number>("setNbrCarrierBays"); const setNbrCarrierBays = createPayloadAction<number>("setNbrCarrierBays");
const setSquadronType = createPayloadAction( const setSquadronType = createPayloadAction(
"setSquadronType", "setSquadronType",
(id: number, type: string) => ({ id, type }) (id: number, type: string) => ({ id, type })
); );
export const carrierDux = new Updux({ export const carrierDux = new Updux({
initialState, initialState,
actions: { setNbrCarrierBays, setSquadronType }, actions: { setNbrCarrierBays, setSquadronType },
}); });
function calcBaysReqs(bays) { function calcBaysReqs(bays) {
return { return {
mass: 9 * bays, mass: 9 * bays,
cost: 18 * bays, cost: 18 * bays,
}; };
} }
const adjustSquadrons = (bays: number) => (squadrons) => { const adjustSquadrons = (bays: number) => (squadrons) => {
if (squadrons.length === bays) return squadrons; if (squadrons.length === bays) return squadrons;
if (squadrons.length > bays) { if (squadrons.length > bays) {
return squadrons.slice(0, bays); return squadrons.slice(0, bays);
} }
return [ return [
...squadrons, ...squadrons,
...Array.from({ length: bays - squadrons.length }) ...Array.from({ length: bays - squadrons.length })
.fill({ .fill({
type: squadronTypes[0].type, type: squadronTypes[0].type,
reqs: { reqs: {
cost: 6 * squadronTypes[0].cost, cost: 6 * squadronTypes[0].cost,
mass: 6, mass: 6,
}, },
}) })
.map((s, i) => ({ ...s, id: squadrons.length + i + 1 })), .map((s, i) => ({ ...s, id: squadrons.length + i + 1 })),
]; ];
}; };
carrierDux.addMutation(setNbrCarrierBays, (nbrBays) => carrierDux.addMutation(setNbrCarrierBays, (nbrBays) =>
u({ u({
nbrBays, nbrBays,
reqs: calcBaysReqs(nbrBays), reqs: calcBaysReqs(nbrBays),
squadrons: adjustSquadrons(nbrBays), squadrons: adjustSquadrons(nbrBays),
}) })
); );
carrierDux.addMutation(setSquadronType, ({ id, type }) => { carrierDux.addMutation(setSquadronType, ({ id, type }) => {
return u({ return u({
squadrons: u.map( squadrons: u.map(
u.if(u.matches({ id }), (state) => { u.if(u.matches({ id }), (state) => {
return u(state, { return u(state, {
type, type,
reqs: squadronReqs(type), reqs: squadronReqs(type),
}); });
}) })
), ),
}); });
}); });
function squadronReqs(type: string) { function squadronReqs(type: string) {
return { return {
mass: 6, mass: 6,
cost: 6 * squadronTypes.find((s) => s.type === type)?.cost, cost: 6 * squadronTypes.find((s) => s.type === type)?.cost,
}; };
} }
/*
export const { actions, reducer } = createSlice({
name: "carrier",
initialStateState,
reducers: {
setCarrierBays: (state, action: PayloadAction<number>) => {
state.bays = action.payload;
state.reqs = calcBaysReqs(action.payload);
state.squadrons = adjustSquadrons(action.payload)(state.squadrons);
},
setSquadronType: (
state,
action: PayloadAction<{ type: string; id: number }>
) => {
state.squadrons[action.payload.id - 1] = {
type: action.payload.type,
reqs: squadronReqs(action.payload.type),
};
},
},
});
*/

View File

@ -1,6 +1,5 @@
import Updux, { createAction, withPayload } from "updux"; import Updux, { createAction, withPayload } from "updux";
import u from "@yanick/updeep-remeda"; import u from "@yanick/updeep-remeda";
import * as R from "remeda";
import { carrierDux } from "./carrier"; import { carrierDux } from "./carrier";
const initialState = { const initialState = {
@ -17,6 +16,7 @@ const initialState = {
const setShipClass = createAction("setShipClass", withPayload<string>()); const setShipClass = createAction("setShipClass", withPayload<string>());
const updateIdentification = createAction("updateIdentification"); const updateIdentification = createAction("updateIdentification");
const setShipReqs = createAction("setShipReqs", withPayload()); const setShipReqs = createAction("setShipReqs", withPayload());
const setCarrier = createAction("setCarrier", withPayload<boolean>());
export const identificationDux = new Updux({ export const identificationDux = new Updux({
initialState, initialState,
@ -24,6 +24,7 @@ export const identificationDux = new Updux({
setShipClass, setShipClass,
updateIdentification, updateIdentification,
setShipReqs, setShipReqs,
setCarrier,
}, },
selectors: { selectors: {
getShipMass: (state) => state.reqs.mass, getShipMass: (state) => state.reqs.mass,
@ -33,12 +34,15 @@ export const identificationDux = new Updux({
identificationDux.addMutation(setShipClass, (shipClass) => u({ shipClass })); identificationDux.addMutation(setShipClass, (shipClass) => u({ shipClass }));
identificationDux.addMutation(updateIdentification, (update) => u(update)); identificationDux.addMutation(updateIdentification, (update) => u(update));
identificationDux.addMutation(setCarrier, (isCarrier) => u({ isCarrier }));
identificationDux.addEffect(setCarrier, (api) => (next) => (action) => {
next(action);
if (!action.payload) {
api.dispatch(carrierDux.actions.setNbrCarrierBays(0));
}
});
identificationDux.addMutation(setShipReqs, (reqs) => u({ reqs })); identificationDux.addMutation(setShipReqs, (reqs) => u({ reqs }));
identificationDux.addMutation(carrierDux.actions.setNbrCarrierBays, (nbrBays) =>
u({
isCarrier: nbrBays > 0,
})
);
export default identificationDux; export default identificationDux;

View File

@ -1,4 +1,6 @@
<ShipEdit {...ship} /> {#if ship}
<ShipEdit {...ship} />
{/if}
<script> <script>
import { getContext } from "svelte"; import { getContext } from "svelte";
@ -7,7 +9,7 @@
export let api = getContext("api"); export let api = getContext("api");
let ship = {}; let ship = api?.getState() ?? {};
api?.subscribe(() => { api?.subscribe(() => {
ship = api.getState(); ship = api.getState();
}); });