story for ShipItem

This commit is contained in:
Yanick Champoux 2023-03-20 11:59:50 -04:00
parent 0c0351cbb3
commit b18017d753
37 changed files with 209 additions and 147 deletions

6
histoire.config.js Normal file
View File

@ -0,0 +1,6 @@
import { defineConfig } from "histoire";
import { HstSvelte } from "@histoire/plugin-svelte";
export default defineConfig({
plugins: [HstSvelte()],
});

View File

@ -11,6 +11,7 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@histoire/plugin-svelte": "^0.15.9",
"@sveltejs/adapter-static": "^1.0.0-next.28", "@sveltejs/adapter-static": "^1.0.0-next.28",
"@sveltejs/kit": "^1.10.0", "@sveltejs/kit": "^1.10.0",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.38", "@sveltejs/vite-plugin-svelte": "^1.0.0-next.38",
@ -28,11 +29,13 @@
"vitest-svelte-kit": "^0.0.6" "vitest-svelte-kit": "^0.0.6"
}, },
"dependencies": { "dependencies": {
"@picocss/pico": "^1.5.7",
"@reduxjs/toolkit": "^1.9.3", "@reduxjs/toolkit": "^1.9.3",
"@sveltejs/adapter-node": "^1.0.0-next.0", "@sveltejs/adapter-node": "^1.0.0-next.0",
"@yanick/updeep-remeda": "^2.1.0", "@yanick/updeep-remeda": "^2.1.0",
"chota": "^0.8.0", "chota": "^0.8.0",
"effector": "^22.5.2", "effector": "^22.5.2",
"histoire": "^0.15.9",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"redux": "^4.1.2", "redux": "^4.1.2",
"remeda": "^1.1.0", "remeda": "^1.1.0",

View File

@ -0,0 +1,9 @@
<Hst.Story title="CostMass">
<CostMass mass={12} cost={21} />
</Hst.Story>
<script>
export let Hst;
import CostMass from "./index.svelte";
</script>

View File

@ -1,8 +1,8 @@
<div class="mass">{mass}<img src="{base}/mass.svg" alt="mass"/></div> <div class="mass">{mass}<img src="{base}/mass.svg" alt="mass" /></div>
<div class="cost">{cost}</div> <div class="cost">{cost}</div>
<script> <script>
import { base } from '$app/paths'; import { base } from "$app/paths";
export let mass; export let mass;
export let cost; export let cost;
</script> </script>

View File

@ -27,8 +27,8 @@
$: if (shipTypes.length > 0 && !shipTypes.includes(shipType)) $: if (shipTypes.length > 0 && !shipTypes.includes(shipType))
shipType = shipTypes[0]; shipType = shipTypes[0];
$: ship.dispatch.setShipType(shipType); $: ship.dispatch(ship.actions.setShipType(shipType));
$: ship.dispatch.setShipClass(shipClass); $: ship.dispatch(ship.actions.setShipClass(shipClass));
</script> </script>
<style> <style>

View File

@ -1,7 +1,14 @@
<ShipItem {...reqs}> <ShipItem {...reqs}>
<div> <div>
<Field label="thrust rating"> <Field label="thrust rating">
<input class="short" type="number" bind:value={rating} min="0" max="20" step="1" /> <input
class="short"
type="number"
bind:value={rating}
min="0"
max="20"
step="1"
/>
</Field> </Field>
<label><input type="checkbox" bind:checked={advanced} /> advanced</label> <label><input type="checkbox" bind:checked={advanced} /> advanced</label>
@ -19,7 +26,7 @@
const ship = getContext("ship"); const ship = getContext("ship");
$: ship.dispatch.setDrive({ rating, advanced }); $: ship.dispatch(ship.actions.setDrive({ rating, advanced }));
</script> </script>
<style> <style>

View File

@ -1,87 +1,87 @@
<div class="mass"> <div class="mass">
<Field label="ship tonnage"> <Field label="ship tonnage">
<input class="short" bind:value={mass} type="number" min="10" max="300" /> <input class="short" bind:value={mass} type="number" min="10" max="300" />
<img class="mass_symbol" src="{base}/mass.svg" alt="mass"/> <img class="mass_symbol" src="{base}/mass.svg" alt="mass" />
<div class="note" class:warning={!withinBudget}> <div class="note" class:warning={!withinBudget}>
{#if withinBudget} {#if withinBudget}
mass unused: {massUnused} mass unused: {massUnused}
{:else} {:else}
excessive mass: {-massUnused} excessive mass: {-massUnused}
{/if} {/if}
</div> </div>
</Field> </Field>
</div> </div>
<div class="cost"> <div class="cost">
<Field label="cost"> <Field label="cost">
<span class="cost">{cost}</span> <span class="cost">{cost}</span>
</Field> </Field>
</div> </div>
<script> <script>
import { base } from '$app/paths'; import { base } from "$app/paths";
import { getContext } from "svelte"; import { getContext } from "svelte";
import Field from "$lib/components/Field/index.svelte"; import Field from "$lib/components/Field/index.svelte";
export let ship = getContext("ship"); export let ship = getContext("ship");
export let mass = 10; export let mass = 10;
export let cost = 10; export let cost = 10;
export let usedMass = 5; export let usedMass = 5;
$: massUnused = mass - usedMass; $: massUnused = mass - usedMass;
$: withinBudget = massUnused >= 0; $: withinBudget = massUnused >= 0;
$: ship.dispatch.setShipMass(mass); $: ship.dispatch(ship.actions.setShipMass(mass));
/* const change_tonnage = ({ target: { value } }) => */ /* const change_tonnage = ({ target: { value } }) => */
/* ship.dispatch(ship.actions.set_ship_mass(parseInt(value))); */ /* ship.dispatch(ship.actions.set_ship_mass(parseInt(value))); */
/* let mass_unused; */ /* let mass_unused; */
/* $: mass_unused = $ship.general.mass - $ship.general.used_mass; */ /* $: mass_unused = $ship.general.mass - $ship.general.used_mass; */
/* let within_budget = true; */ /* let within_budget = true; */
/* $: within_budget = mass_unused >= 0; */ /* $: within_budget = mass_unused >= 0; */
</script> </script>
<style> <style>
.ship_cost { .ship_cost {
display: flex; display: flex;
grid-column: span 3; grid-column: span 3;
justify-content: space-around; justify-content: space-around;
} }
input { input {
width: 5em; width: 5em;
display: inline !important; display: inline !important;
} }
.mass_symbol { .mass_symbol {
width: 0.75em; width: 0.75em;
display: inline-block; display: inline-block;
margin-left: 0.5em; margin-left: 0.5em;
} }
.warning { .warning {
color: red; color: red;
} }
.note { .note {
font-size: smaller; font-size: smaller;
} }
.mass, .mass,
div.cost { div.cost {
padding: 0px 2em; padding: 0px 2em;
justify-self: right; justify-self: right;
} }
.mass { .mass {
width: 15em; width: 15em;
} }
div.cost { div.cost {
grid-column: 3; grid-column: 3;
} }
span.cost:after { span.cost:after {
content: "\00A4"; content: "\00A4";
margin-left: 0.5em; margin-left: 0.5em;
} }
</style> </style>

View File

@ -0,0 +1,14 @@
<Hst.Story>
<ShipItem {mass} {cost}>Thingy</ShipItem>
<svelte:fragment slot="controls">
<Hst.Number bind:value={mass} title="Mass" />
<Hst.Number bind:value={cost} title="Cost" />
</svelte:fragment>
</Hst.Story>
<script>
export let Hst;
import ShipItem from "./ShipItem.svelte";
let mass = 1;
let cost = 3;
</script>

View File

@ -2,14 +2,15 @@
<div><slot /></div> <div><slot /></div>
<div class="reqs"> <div class="reqs">
<div class="mass" bind:this={mass_el}>{mass} <img src="{base}/mass.svg" <div class="mass" bind:this={mass_el}>
alt="mass"/></div> {mass} <img src="{base}/mass.svg" alt="mass" />
</div>
<div class="cost" bind:this={cost_el}>{cost}</div> <div class="cost" bind:this={cost_el}>{cost}</div>
</div> </div>
</div> </div>
<script> <script>
import { base } from '$app/paths'; import { base } from "$app/paths";
import { tick } from "svelte"; import { tick } from "svelte";
export let mass; export let mass;

View File

@ -5,36 +5,38 @@ import u from "updeep";
import { Reqs, reqs } from "./reqs.js"; import { Reqs, reqs } from "./reqs.js";
const initialState = { const initialState = {
rating: 1, rating: 1,
advanced: false, advanced: false,
reqs, reqs,
}; };
const engine = createSlice({ const engine = createSlice({
name: "engine", name: "engine",
initialState, initialState,
reducers: { reducers: {
setDriveRating(state, action: PayloadAction<number>) { setDriveRating(state, action: PayloadAction<number>) {
state.rating = action.payload; state.rating = action.payload;
},
setDriveAdvanced(state, action: PayloadAction<boolean>) {
state.advanced = action.payload;
},
setDriverReqs(state, action: PayloadAction<Reqs>) {
state.reqs = action.payload;
},
}, },
setDriveAdvanced(state, action: PayloadAction<boolean>) {
state.advanced = action.payload;
},
setDriverReqs(state, action: PayloadAction<Reqs>) {
state.reqs = action.payload;
},
},
}); });
export function calcDriveReqs( export const { actions, reducer } = engine;
shipMass: number,
rating: number,
advanced = false
) {
const mass = Math.ceil(rating * 0.05 * shipMass);
const cost = mass * (advanced ? 3 : 2);
return { mass, cost }; export function calcDriveReqs(
shipMass: number,
rating: number,
advanced = false
) {
const mass = Math.ceil(rating * 0.05 * shipMass);
const cost = mass * (advanced ? 3 : 2);
return { mass, cost };
} }
export default engine; export default engine;

View File

@ -1,28 +1,32 @@
import { Updux } from "updux"; import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
import u from "updeep"; import * as carrier from "./carrier.js";
import carrier from "./carrier.js"; const initialState = {
shipType: "",
shipClass: "",
isCarrier: false,
mass: 10,
};
const dux = new Updux({ const identification = createSlice({
actions: { name: "identification",
setShipType: null, initialState,
setShipClass: null, reducers: {
setCarrierBays: carrier.actions.setCarrierBays, setShipType(state, action: PayloadAction<string>) {
state.shipType = action.payload;
},
setShipClass(state, action: PayloadAction<string>) {
state.shipClass = action.payload;
},
}, },
initial: { extraReducers(builder) {
shipType: "", builder.addCase(
shipClass: "", carrier.actions.setCarrierBays,
isCarrier: false, (state, action: PayloadAction<number>) => {
mass: 10, state.isCarrier = action.payload > 0;
}
);
}, },
}); });
dux.setMutation("setShipType", (shipType) => u({ shipType })); export const { actions, reducer } = identification;
dux.setMutation("setShipClass", (shipClass) => u({ shipClass }));
dux.setMutation("setCarrierBays", (bays) =>
u({
isCarrier: bays > 0,
})
);
export default dux;

View File

@ -5,6 +5,9 @@ import * as propulsion from "./propulsion";
import * as structure from "./structure"; import * as structure from "./structure";
import * as weaponry from "./weaponry/index.js"; import * as weaponry from "./weaponry/index.js";
import * as carrier from "./carrier"; import * as carrier from "./carrier";
import * as identification from "./identification.js";
import * as shipReqs from "./shipReqs";
import * as engine from "./engine";
const shipSlice = createSlice({ const shipSlice = createSlice({
name: "ship", name: "ship",
@ -14,10 +17,12 @@ const shipSlice = createSlice({
builder.addMatcher( builder.addMatcher(
() => true, () => true,
combineReducers({ combineReducers({
identification: identification.reducer,
propulsion: propulsion.reducer, propulsion: propulsion.reducer,
structure: structure.reducer, structure: structure.reducer,
weaponry: weaponry.reducer, weaponry: weaponry.reducer,
carrier: carrier.reducer, carrier: carrier.reducer,
engine: engine.reducer,
}) })
); );
}, },
@ -29,11 +34,16 @@ export function createStore() {
}); });
} }
export const actions = shipSlice.actions; export const actions = {
...shipReqs.actions,
...shipSlice.actions,
...identification.actions,
...engine.actions,
...propulsion.actions,
};
/** /**
import * as propulsion from "./propulsion/index.js"; import * as propulsion from "./propulsion/index.js";
import * as identification from "./identification.js";
import { calculateDriveReqs } from "./propulsion/drive.js"; import { calculateDriveReqs } from "./propulsion/drive.js";
import { ftlReqsReaction } from "./propulsion/ftl.js"; import { ftlReqsReaction } from "./propulsion/ftl.js";
import * as structure from "./structure/index.js"; import * as structure from "./structure/index.js";

View File

@ -11,3 +11,8 @@ export const initialState = {
drive: drive.initialState, drive: drive.initialState,
ftl: ftl.initialState, ftl: ftl.initialState,
}; };
export const actions = {
...drive.actions,
...ftl.actions,
};

View File

@ -22,3 +22,5 @@ const shipReqs = createSlice({
}, },
}, },
}); });
export const { actions, reducer } = shipReqs;

View File

@ -2,39 +2,38 @@ import { browser, dev } from "$app/environment";
import { readable, get, derived } from "svelte/store"; import { readable, get, derived } from "svelte/store";
import { compose, applyMiddleware } from "redux"; import { compose, applyMiddleware } from "redux";
import shipDux from "../shipDux/index"; import { createStore, actions } from "../shipDux/index";
import { initial } from "lodash"; import { initial } from "lodash";
let composeEnhancers = compose; let composeEnhancers = compose;
if (dev && browser && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { if (dev && browser && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
} }
export default (initialState = undefined) => { export default (initialState = undefined) => {
if (browser) { if (browser) {
const i = localStorage.getItem("ship"); const i = localStorage.getItem("ship");
if (i) initialState = JSON.parse(localStorage.getItem("ship")); if (i) initialState = JSON.parse(localStorage.getItem("ship"));
} }
const duxStore = shipDux.createStore(initialState, (mw) => const duxStore = createStore();
composeEnhancers(applyMiddleware(mw))
);
let previous; let previous;
const state = readable(duxStore.getState(), (set) => { const state = readable(duxStore.getState(), (set) => {
duxStore.subscribe(() => { duxStore.subscribe(() => {
if (previous === duxStore.getState()) return; if (previous === duxStore.getState()) return;
previous = duxStore.getState(); previous = duxStore.getState();
set(previous); set(previous);
if (browser) localStorage.setItem("ship", JSON.stringify(previous)); if (browser) localStorage.setItem("ship", JSON.stringify(previous));
});
}); });
});
return { return {
dispatch: duxStore.dispatch, dispatch: duxStore.dispatch,
state, state,
shipMass: derived(state, (state) => state.reqs.mass), actions,
}; shipMass: derived(state, (state) => state.reqs.mass),
};
}; };

View File

@ -3,10 +3,10 @@ import { sveltekit } from "@sveltejs/kit/vite";
/** @type {import('vite').UserConfig} */ /** @type {import('vite').UserConfig} */
const config = { const config = {
plugins: [sveltekit()], plugins: [sveltekit()],
publicDir: "./static",
ssr: {}, ssr: {},
optimizeDeps: {}, optimizeDeps: {},
}; };
export default config; export default config;