Made responsive
This commit is contained in:
35
.idea/workspace.xml
generated
35
.idea/workspace.xml
generated
@@ -26,14 +26,25 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="70635ef7-86ab-4681-b98d-dc8e4999995b" name="Default Changelist" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/public/ColorWheel.png" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/public/resources/favicon.png" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/ui/ActionButton.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/solve.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/solve.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/store.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/store.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/App.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/App.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/CubeInputSet.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/ExamplesList.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/InputParameters.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/InputParameters.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/List.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/List.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/Sidebar.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Sidebar.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/SolutionList.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolutionList.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/Tabs.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Tabs.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/SolutionViewer.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/ThreeDee.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/SolveButton.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/SolveButton.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/Stage.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/Stage.svelte" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/PolycubeScene.ts" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/ui/threedee/RotationControl.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ui/threedee/RotationControl.ts" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -59,6 +70,7 @@
|
||||
<option value="SolveButton.svelte" />
|
||||
<option value="ExamplesList.svelte" />
|
||||
<option value="List.svelte" />
|
||||
<option value="ActionButton.svelte" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
@@ -95,6 +107,13 @@
|
||||
<property name="vue.rearranger.settings.migration" value="true" />
|
||||
</component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/public" />
|
||||
<recent name="$PROJECT_DIR$/public/resources" />
|
||||
<recent name="$PROJECT_DIR$/public/build" />
|
||||
<recent name="$PROJECT_DIR$/public/solver" />
|
||||
<recent name="$PROJECT_DIR$/src/desktop" />
|
||||
</key>
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src" />
|
||||
<recent name="$PROJECT_DIR$/src/ui" />
|
||||
@@ -102,13 +121,6 @@
|
||||
<recent name="$PROJECT_DIR$/public" />
|
||||
<recent name="$PROJECT_DIR$/public/solver" />
|
||||
</key>
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/public" />
|
||||
<recent name="$PROJECT_DIR$/public/build" />
|
||||
<recent name="$PROJECT_DIR$/public/solver" />
|
||||
<recent name="$PROJECT_DIR$/src/desktop" />
|
||||
<recent name="$PROJECT_DIR$/src/solver/js" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
<configuration name="test.html" type="JavascriptDebugType" temporary="true" nameIsGenerated="true" uri="http://localhost:63343/soma/src/test.html" useBuiltInWebServerPort="true">
|
||||
@@ -156,6 +168,7 @@
|
||||
<workItem from="1625389803555" duration="21540000" />
|
||||
<workItem from="1625471453862" duration="708000" />
|
||||
<workItem from="1625489120282" duration="24129000" />
|
||||
<workItem from="1625561814270" duration="14382000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
@@ -218,10 +231,10 @@
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
</state>
|
||||
<state x="696" y="393" width="518" height="301" key="ANALYSIS_DLG_com.intellij.analysis.BaseAnalysisAction$1/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624963157966" />
|
||||
<state x="2012" y="43" width="1720" height="903" key="DiffContextDialog" timestamp="1625505778555">
|
||||
<screen x="1920" y="0" width="1920" height="1080" />
|
||||
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog" timestamp="1625575561214">
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
</state>
|
||||
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1624822125389" />
|
||||
<state x="92" y="69" width="1720" height="880" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@0.27.1920.1053" timestamp="1625575561214" />
|
||||
<state x="2012" y="43" width="1720" height="903" key="DiffContextDialog/1920.0.1920.1080/0.27.1920.1053@1920.0.1920.1080" timestamp="1625505778555" />
|
||||
<state x="743" y="301" width="716" height="623" key="FileChooserDialogImpl" timestamp="1621932819206">
|
||||
<screen x="0" y="27" width="1920" height="1053" />
|
||||
|
||||
@@ -49,10 +49,7 @@
|
||||
"linux": {
|
||||
"target": ["AppImage"]
|
||||
},
|
||||
"extraResources": [
|
||||
"public/solver"
|
||||
],
|
||||
"icon": "./public/resources/soma_icon.png",
|
||||
"icon": "./public/favicon.png",
|
||||
"directories": {
|
||||
"output": "desktop-dist"
|
||||
}
|
||||
|
||||
BIN
public/ColorWheel.png
Normal file
BIN
public/ColorWheel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
0
public/resources/favicon.png
Normal file
0
public/resources/favicon.png
Normal file
@@ -13,7 +13,7 @@ import {
|
||||
totalVolume
|
||||
} from "./store";
|
||||
|
||||
const worker = new Worker('./solver/main.js', {type: 'module'});
|
||||
const worker = new Worker('./worker.js', {type: 'module'});
|
||||
async function respondWasm(event: MessageEvent) {
|
||||
solutions.set(event.data.map((wasmSolution) => {
|
||||
const solnObj = new SomaSolution(somaDimX.currentVal(), somaDimY.currentVal(), somaDimZ.currentVal());
|
||||
|
||||
32
src/store.ts
32
src/store.ts
@@ -26,7 +26,14 @@ export const isMinPolycubes = derived(
|
||||
($polycubes: VoxelSpaceBigInt[]) => $polycubes.length <= 1
|
||||
);
|
||||
|
||||
export const examples = [
|
||||
type Save = {
|
||||
name: string,
|
||||
dimX: number,
|
||||
dimY: number,
|
||||
dimZ: number,
|
||||
cubes: {space: bigint | string, color: string}[],
|
||||
};
|
||||
const builtInExamples: Save[] = [
|
||||
{
|
||||
name: "Standard Soma Cube",
|
||||
dimX: 3,
|
||||
@@ -114,17 +121,34 @@ export const examples = [
|
||||
{space: 120n, color: "#0000ff"},
|
||||
],
|
||||
},
|
||||
].concat(deserealiseSaves());
|
||||
];
|
||||
|
||||
function deserealiseSaves() {
|
||||
export const examples = writable(builtInExamples.concat(deserealiseSaves()));
|
||||
|
||||
function deserealiseSaves(): Save[] {
|
||||
return localStorage.getItem("saves")?.split("@").map(save => JSON.parse(save)) ?? [];
|
||||
}
|
||||
|
||||
export function serialiseCurrentInput() {
|
||||
function serialiseCurrentInput(): Save {
|
||||
return {
|
||||
name: "",
|
||||
dimX: somaDimX.currentVal(),
|
||||
dimY: somaDimY.currentVal(),
|
||||
dimZ: somaDimZ.currentVal(),
|
||||
cubes: polycubes.currentVal().map(cube => ({space: cube.getRaw().toString(), color: cube.getColor()})),
|
||||
};
|
||||
}
|
||||
|
||||
export function save(name: string) {
|
||||
const save = serialiseCurrentInput();
|
||||
save.name = name;
|
||||
const saveString = JSON.stringify(save);
|
||||
let oldSaves = localStorage.getItem("saves");
|
||||
if (oldSaves !== null) {
|
||||
oldSaves += "@";
|
||||
} else {
|
||||
oldSaves = "";
|
||||
}
|
||||
localStorage.setItem("saves", oldSaves + saveString);
|
||||
examples.update(examples => examples.concat(save));
|
||||
}
|
||||
32
src/ui/ActionButton.svelte
Normal file
32
src/ui/ActionButton.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
export let onClick: () => void = () => {};
|
||||
export let tooltip: string | null = null;
|
||||
export let disabled: boolean = false;
|
||||
export let text: string;
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={onClick}
|
||||
title="{tooltip}"
|
||||
disabled={disabled}>
|
||||
{text}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
button {
|
||||
width: auto;
|
||||
color: white;
|
||||
background-color: #ff3e00;
|
||||
border-radius: 0.5em;
|
||||
border-style: none;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #ff6b3e;
|
||||
}
|
||||
button:disabled {
|
||||
color: #999999;
|
||||
background-color: #a36754;
|
||||
}
|
||||
</style>
|
||||
@@ -10,13 +10,14 @@
|
||||
<div class="sidebarContainer">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div class="solutionBodyContainer">
|
||||
<div class="stage">
|
||||
<Stage scene="{scene}" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
@@ -25,28 +26,41 @@
|
||||
background-color: black;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 20%;
|
||||
max-width: 20%;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.solutionBodyContainer {
|
||||
.stage {
|
||||
flex: 1;
|
||||
background-color: grey;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 80%;
|
||||
height: 100%;
|
||||
width: 80%;
|
||||
display: inline-block;
|
||||
}
|
||||
:global(body) {
|
||||
color: white;
|
||||
background: #333333;
|
||||
}
|
||||
@media(max-width: 1200px) {
|
||||
.solutionBodyContainer {
|
||||
width: calc(100% - 18em);
|
||||
}
|
||||
@media(width: 1200px) {
|
||||
.sidebarContainer {
|
||||
width: 18em;
|
||||
}
|
||||
.stage {
|
||||
width: calc(80% - 18em);
|
||||
}
|
||||
}
|
||||
@media(max-width: 1024px) {
|
||||
.stage {
|
||||
left: 2em;
|
||||
position: absolute;
|
||||
width: calc(100% - 2em);
|
||||
}
|
||||
.sidebarContainer {
|
||||
position: absolute;
|
||||
max-width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
}
|
||||
.colorPickerBtn {
|
||||
align-self: center;
|
||||
background-image: url("../resources/ColorWheel.png");
|
||||
background-image: url("./ColorWheel.png");
|
||||
background-size: cover;
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
margin: auto;
|
||||
}
|
||||
.container {
|
||||
flex: 1 1 auto;
|
||||
overflow-x: scroll;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-flow: row;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -1,67 +1,65 @@
|
||||
<script lang="ts">
|
||||
import List from "./List.svelte";
|
||||
import {polycubes, examples, serialiseCurrentInput} from "../store";
|
||||
import {polycubes, examples, save} from "../store";
|
||||
import VoxelSpaceBigInt from "../VoxelSpaceBigInt";
|
||||
import ActionButton from "./ActionButton.svelte";
|
||||
|
||||
const placeholder = "Give your puzzle a name";
|
||||
let untouchedInput = true;
|
||||
let currentName = placeholder;
|
||||
let lastClickedExample = 0;
|
||||
let untouchedInput: boolean = true;
|
||||
let currentName: string = placeholder;
|
||||
let lastClickedExample: number | null = null;
|
||||
|
||||
function hydrateExample(exNo: number) {
|
||||
const example = examples[exNo];
|
||||
const example = $examples[exNo];
|
||||
polycubes.setCubes(example.cubes.map((cube, i) => new VoxelSpaceBigInt({
|
||||
id: i,
|
||||
dims: [example.dimX, example.dimY, example.dimZ],
|
||||
space: cube.space,
|
||||
space: BigInt(cube.space),
|
||||
color: cube.color,
|
||||
cullEmpty: false,
|
||||
})));
|
||||
lastClickedExample = exNo;
|
||||
}
|
||||
|
||||
function checkUntouched() {
|
||||
if (currentName === "") {
|
||||
untouchedInput = true;
|
||||
} else {
|
||||
untouchedInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(e: InputEvent) {
|
||||
currentName = (e.target as HTMLInputElement).value;
|
||||
checkUntouched();
|
||||
}
|
||||
|
||||
function onFocusText(e: FocusEvent) {
|
||||
if (untouchedInput) {
|
||||
currentName = "";
|
||||
untouchedInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onBlurText(e: FocusEvent) {
|
||||
if (currentName === "") {
|
||||
if (untouchedInput) {
|
||||
currentName = placeholder;
|
||||
untouchedInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
const save = serialiseCurrentInput();
|
||||
save["name"] = currentName;
|
||||
const saveString = JSON.stringify(save);
|
||||
let oldSaves = window.localStorage.getItem("saves");
|
||||
if (oldSaves !== null) {
|
||||
oldSaves += "@";
|
||||
} else {
|
||||
oldSaves = "";
|
||||
}
|
||||
window.localStorage.setItem("saves", oldSaves + saveString);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<List
|
||||
defaultText="No examples found..."
|
||||
items="{examples.map(example => example.name)}"
|
||||
items="{$examples.map(example => example.name)}"
|
||||
activeItem={lastClickedExample}
|
||||
onClick={(i) => hydrateExample(i)}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<button on:click={save}>Save as...</button>
|
||||
<div class="save">
|
||||
<ActionButton
|
||||
onClick={() => save(currentName)}
|
||||
text={"Save as..."}
|
||||
disabled={untouchedInput}/>
|
||||
<input
|
||||
class:untouchedInput
|
||||
value="{currentName}"
|
||||
@@ -75,11 +73,22 @@
|
||||
.untouchedInput {
|
||||
color: grey;
|
||||
}
|
||||
button {
|
||||
.save {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.flex {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
row-gap: 1em;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
margin-top: 1em;
|
||||
}
|
||||
input {
|
||||
flex-basis: 10em;
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
min-width: 50%;
|
||||
margin: 0 0 0 0.5em;
|
||||
width: 100%;
|
||||
}
|
||||
.container {
|
||||
height: 10em;
|
||||
|
||||
@@ -32,9 +32,6 @@
|
||||
downDisabled="{$somaDimZ <= PolycubeStore.MIN_DIMS}"
|
||||
down="{() => somaDimZ.set($somaDimZ - 1)}"
|
||||
/>
|
||||
{#if $totalVolume > 32}
|
||||
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
@@ -49,7 +46,4 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.warn {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@@ -29,6 +29,10 @@
|
||||
list-style: none;
|
||||
height: 2em;
|
||||
line-height: 2em;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
ul {
|
||||
position: absolute;
|
||||
|
||||
@@ -7,32 +7,46 @@
|
||||
import ExamplesList from "./ExamplesList.svelte";
|
||||
import Tabs from "./Tabs.svelte";
|
||||
import SolveButton from "./SolveButton.svelte";
|
||||
import ActionButton from "./ActionButton.svelte";
|
||||
|
||||
let hidden: boolean = true;
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<div class="title">
|
||||
<img class="logo" src="../favicon.png"/><h1>Somaesque</h1>
|
||||
</div>
|
||||
<div class="widgets">
|
||||
<div>
|
||||
<Tabs
|
||||
selectedTab={"Parameters"}
|
||||
tabs={{
|
||||
"Parameters": InputParameters,
|
||||
"Examples": ExamplesList,
|
||||
}}/>
|
||||
<div class="controls" class:hidden>
|
||||
<div class="title">
|
||||
<img class="logo" src="./resources/favicon.png"/>
|
||||
<h1>Somaesque</h1>
|
||||
</div>
|
||||
<div>
|
||||
<SolveButton/>
|
||||
<div class="widgets">
|
||||
<div>
|
||||
<Tabs
|
||||
selectedTab={"Parameters"}
|
||||
tabs={{
|
||||
"Parameters": InputParameters,
|
||||
"Examples": ExamplesList,
|
||||
}}/>
|
||||
</div>
|
||||
<div>
|
||||
<SolveButton/>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Solutions: {$solutions.length}</h3>
|
||||
<div class="solns">
|
||||
<SolutionList/>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Solutions: {$solutions.length}</h3>
|
||||
<div class="solns">
|
||||
<SolutionList/>
|
||||
<div class="showHideBtn">
|
||||
<ActionButton text={hidden ? ">" : "<"} onClick={() => {hidden = !hidden}}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
flex-direction: row;
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -41,12 +55,16 @@
|
||||
margin-right: 1em;
|
||||
display: inline-block;
|
||||
height: 3em;
|
||||
align-self: center;
|
||||
background-image: url("../resources/favicon.png");
|
||||
background-size: cover;
|
||||
width: 3em;
|
||||
}
|
||||
.container {
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #333333;
|
||||
padding: 1em;
|
||||
@@ -76,4 +94,34 @@
|
||||
font-size: 3em;
|
||||
font-weight: 100;
|
||||
}
|
||||
.showHideBtn {
|
||||
background-color: #333333;
|
||||
text-align: center;
|
||||
line-height: 100%;
|
||||
width: 2em;
|
||||
height: 100%;
|
||||
padding: 0.25em;
|
||||
flex: 0 0 auto;
|
||||
display: none;
|
||||
}
|
||||
@media(max-width: 1600px) {
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
@media(max-width: 1024px) {
|
||||
.controls {
|
||||
overflow: scroll;
|
||||
padding-right: 0;
|
||||
}
|
||||
.showHideBtn {
|
||||
display: inline-block;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.container.hidden {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,5 +20,6 @@
|
||||
<style>
|
||||
.container {
|
||||
height: 100%;
|
||||
min-height: 10em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import {polycubes, solving, somaDimX, somaDimY, somaDimZ, totalVolume} from "../store";
|
||||
import {solve} from "../solve";
|
||||
import ActionButton from "./ActionButton.svelte";
|
||||
|
||||
$: cubes = $polycubes;
|
||||
let noEmpties: boolean;
|
||||
@@ -26,29 +27,40 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="solve"
|
||||
on:click={solve}
|
||||
title="{genTooltip(enoughSubcubes, noEmpties, size)}"
|
||||
disabled="{$solving || !readyToSolve}">
|
||||
{$solving ? "Solving..." : "Solve!"}
|
||||
</button>
|
||||
<div class="container">
|
||||
<div class="solve">
|
||||
<ActionButton
|
||||
onClick={solve}
|
||||
tooltip={genTooltip(enoughSubcubes, noEmpties, size)}
|
||||
disabled={$solving || !readyToSolve}
|
||||
text={$solving ? "Solving..." : "Solve!"}/>
|
||||
</div>
|
||||
{#if $totalVolume > 32}
|
||||
<p class="warn">The total number of units exceeds 32. Attempting to solve puzzles with more than 32 units results in significantly slower computation time.</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
button.solve {
|
||||
width: auto;
|
||||
color: white;
|
||||
background-color: #ff3e00;
|
||||
font-size: 2em;
|
||||
border-radius: 0.5em;
|
||||
border-style: none;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
button.solve:disabled {
|
||||
.warn {
|
||||
flex-basis: 7em;
|
||||
flex: 1;
|
||||
min-width: 7em;
|
||||
margin-left: 1em;
|
||||
color: red;
|
||||
text-align: left;
|
||||
}
|
||||
.solve {
|
||||
height: min-content;
|
||||
width: auto;
|
||||
color: #999999;
|
||||
background-color: #a36754;
|
||||
font-size: 2em;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import SolutionViewer from "./SolutionViewer.svelte";
|
||||
import ThreeDee from "./ThreeDee.svelte";
|
||||
import CubeInputSet from "./CubeInputSet.svelte";
|
||||
import PolycubeScene from "./threedee/PolycubeScene";
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
const queryListWidth = window.matchMedia("(max-width: 1200px)");
|
||||
const queryListHeight = window.matchMedia("(max-height: 920px)");
|
||||
const queryListHeight = window.matchMedia("(max-height: 900px)");
|
||||
queryListWidth.addEventListener("change", onMediaChange);
|
||||
queryListHeight.addEventListener("change", onMediaChange);
|
||||
onMediaChange();
|
||||
@@ -27,11 +27,11 @@
|
||||
{#if showInput}
|
||||
<CubeInputSet/>
|
||||
{:else}
|
||||
<SolutionViewer scene="{scene}"/>
|
||||
<ThreeDee scene="{scene}"/>
|
||||
{/if}
|
||||
{:else}
|
||||
<CubeInputSet/>
|
||||
<SolutionViewer scene="{scene}"/>
|
||||
<ThreeDee scene="{scene}"/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -59,9 +59,9 @@
|
||||
border-width: 1px 0 0 0;
|
||||
}
|
||||
.viewport {
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import {onMount} from "svelte";
|
||||
import {polycubes, solutions, activeSolution, showingSolution} from "../store";
|
||||
import Solution2D from "./Solution2D.svelte";
|
||||
import CubeInput from "./CubeInput.svelte";
|
||||
|
||||
export let scene: PolycubeScene;
|
||||
const selectedStore = polycubes.selected();
|
||||
@@ -10,17 +11,32 @@
|
||||
$: cube = $polycubes[selectedCube];
|
||||
$: soln = $solutions[$activeSolution];
|
||||
let el: HTMLDivElement;
|
||||
let containerEl: HTMLDivElement;
|
||||
let loaded: boolean = false;
|
||||
|
||||
const canvasStyle: Partial<CSSStyleDeclaration> = {
|
||||
borderRadius: "1em",
|
||||
};
|
||||
|
||||
function updateDims() {
|
||||
if (window.innerWidth < 1200) {
|
||||
if (containerEl.clientHeight < containerEl.clientWidth * (3 / 4)) {
|
||||
scene.updateDims({width: (containerEl.clientHeight - 50) * (4 / 3), height: containerEl.clientHeight - 50});
|
||||
} else {
|
||||
scene.updateDims({width: containerEl.scrollWidth - 50, height: (containerEl.scrollWidth - 50) * (3 / 4)});
|
||||
}
|
||||
} else {
|
||||
scene.resetDims();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
scene.onLoaded(() => {
|
||||
scene.mount(el);
|
||||
Object.assign((el.children.item(0) as HTMLElement).style, canvasStyle);
|
||||
loaded = true;
|
||||
window.addEventListener("resize", () => updateDims());
|
||||
updateDims();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,11 +51,15 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
{#if $activeSolution !== null}
|
||||
<div class="container" bind:this={containerEl}>
|
||||
{#if $showingSolution && ($activeSolution !== null)}
|
||||
<div class="soln2d-container">
|
||||
<Solution2D/>
|
||||
</div>
|
||||
{:else if !$showingSolution}
|
||||
<div class="soln2d-container">
|
||||
<CubeInput cubeNo="{selectedCube}"/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="stage" bind:this={el}></div>
|
||||
</div>
|
||||
@@ -49,6 +69,11 @@
|
||||
flex: 0 1 auto;
|
||||
display: inline-block;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.soln2d-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
@@ -57,5 +82,6 @@
|
||||
justify-content: space-evenly;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -21,8 +21,10 @@ export default class PolycubeScene {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private loadedCb: () => void = () => {};
|
||||
private loaded: boolean = false;
|
||||
private windowDims: {width: number, height: number};
|
||||
|
||||
constructor() {
|
||||
constructor(windowDims?: {width: number, height: number}) {
|
||||
this.windowDims = windowDims ?? {width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT};
|
||||
this.init().then(() => this.loadedCb()).catch(e => console.log(e));
|
||||
}
|
||||
|
||||
@@ -56,12 +58,24 @@ export default class PolycubeScene {
|
||||
}
|
||||
|
||||
mount(el: HTMLDivElement) {
|
||||
this.canvas.width = DEFAULT_WIDTH;
|
||||
this.canvas.height = DEFAULT_HEIGHT;
|
||||
this.updateDims(this.windowDims);
|
||||
el.append(this.canvas);
|
||||
}
|
||||
|
||||
updateDims(windowDims: {width: number, height: number}) {
|
||||
this.windowDims.width = windowDims.width;
|
||||
this.windowDims.height = windowDims.height;
|
||||
this.canvas.width = this.windowDims.width;
|
||||
this.canvas.height = this.windowDims.height;
|
||||
this.camera.aspect = this.canvas.width / this.canvas.height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(this.canvas.width, this.canvas.height);
|
||||
el.append(this.canvas);
|
||||
}
|
||||
|
||||
resetDims() {
|
||||
this.windowDims.width = DEFAULT_WIDTH;
|
||||
this.windowDims.height = DEFAULT_HEIGHT;
|
||||
this.updateDims(this.windowDims);
|
||||
}
|
||||
|
||||
onLoaded(cb: () => void) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type * as THREE from 'three';
|
||||
import {MOUSE} from "three";
|
||||
|
||||
interface Fliable {
|
||||
flyBy(factor: number);
|
||||
@@ -17,6 +18,10 @@ export default class RotationControls {
|
||||
private start: THREE.Euler;
|
||||
private fliables: Fliable[];
|
||||
private hovered: boolean = false;
|
||||
private scrolling: boolean = false;
|
||||
private lastTouch: {x: number, y: number} = {x: 0, y: 0};
|
||||
private lastScroll1: {x: number, y: number} = {x: 0, y: 0};
|
||||
private lastScroll2: {x: number, y: number} = {x: 0, y: 0};
|
||||
|
||||
constructor(object: THREE.Object3D, fliables: Fliable[], camera: THREE.Camera, element: HTMLCanvasElement) {
|
||||
this.object = object;
|
||||
@@ -28,23 +33,29 @@ export default class RotationControls {
|
||||
this.xAxis = this.xAxis.clone().cross(this.yAxis.clone());
|
||||
this.start = this.object.rotation.clone();
|
||||
|
||||
this.element.addEventListener('touchstart', (ev) => this.handleTouchStart(ev));
|
||||
this.element.addEventListener("touchcancel", (ev) => this.handleTouchEnd(ev));
|
||||
window.addEventListener('touchmove', (ev) => this.handleTouchMove(ev));
|
||||
window.addEventListener('touchend', (ev) => this.handleTouchEnd(ev));
|
||||
this.element.addEventListener('wheel', (ev) => this.handleScroll(ev));
|
||||
this.element.addEventListener('mouseover', () => this.hovered = true);
|
||||
this.element.addEventListener('mouseout', () => this.hovered = false);
|
||||
this.element.addEventListener('wheel', (ev) => this.handleScroll(ev));
|
||||
this.element.addEventListener('mousedown', (event) => {
|
||||
if (event.button === 1) {
|
||||
this.object.setRotationFromEuler(this.start);
|
||||
}
|
||||
if (!this.dragging) {
|
||||
this.lastX = event.x;
|
||||
this.lastY = event.y;
|
||||
this.dragging = true;
|
||||
}
|
||||
});
|
||||
this.element.addEventListener('mousedown', (ev) => this.handleMouseDown(ev));
|
||||
window.addEventListener('mousemove', (ev) => this.handleMove(ev));
|
||||
window.addEventListener('mouseup', () => this.dragging = false);
|
||||
}
|
||||
|
||||
private handleMouseDown(event: MouseEvent) {
|
||||
if (event.button === 1) {
|
||||
this.object.setRotationFromEuler(this.start);
|
||||
}
|
||||
if (!this.dragging) {
|
||||
this.lastX = event.x;
|
||||
this.lastY = event.y;
|
||||
this.dragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
private handleMove(event: MouseEvent) {
|
||||
if (this.dragging) {
|
||||
const xDiff = event.movementX * RotationControls.ROTATION_FACTOR;
|
||||
@@ -63,6 +74,56 @@ export default class RotationControls {
|
||||
}
|
||||
}
|
||||
|
||||
private handleTouchMove(event: TouchEvent) {
|
||||
if (this.dragging) {
|
||||
const newTouchX = event.touches.item(0).clientX;
|
||||
const newTouchY = event.touches.item(0).clientY;
|
||||
const touchDiffX = newTouchX - this.lastTouch.x;
|
||||
const touchDiffY = newTouchY - this.lastTouch.y;
|
||||
const xDiff = touchDiffX * RotationControls.ROTATION_FACTOR;
|
||||
const yDiff = touchDiffY * RotationControls.ROTATION_FACTOR;
|
||||
this.object.rotateOnAxis(this.yAxis, xDiff);
|
||||
this.object.rotateOnWorldAxis(this.xAxis, yDiff);
|
||||
this.lastTouch.x = newTouchX;
|
||||
this.lastTouch.y = newTouchY;
|
||||
} else if (this.scrolling) {
|
||||
if (this.flyingEnabled && this.hovered) {
|
||||
const newTouchX1 = event.touches.item(0).clientX;
|
||||
const newTouchX2 = event.touches.item(1).clientX;
|
||||
const newTouchY1 = event.touches.item(0).clientY;
|
||||
const newTouchY2 = event.touches.item(1).clientY;
|
||||
const lastDist = Math.sqrt((this.lastScroll1.x - this.lastScroll2.x) ** 2 + (this.lastScroll1.y - this.lastScroll2.y) ** 2);
|
||||
const newDist = Math.sqrt((newTouchX1 - newTouchX2) ** 2 + (newTouchY1 - newTouchY2) ** 2)
|
||||
const delta = newDist - lastDist;
|
||||
for (const fliable of this.fliables) {
|
||||
const direction = delta / Math.abs(delta);
|
||||
fliable.flyBy(direction / 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleTouchStart(event: TouchEvent) {
|
||||
if (event.touches.length === 1) {
|
||||
this.lastTouch.x = event.touches.item(0).clientX;
|
||||
this.lastTouch.y = event.touches.item(0).clientY;
|
||||
this.dragging = true;
|
||||
} else if (event.touches.length === 2) {
|
||||
this.lastScroll1.x = event.touches.item(0).clientX;
|
||||
this.lastScroll1.y = event.touches.item(0).clientY;
|
||||
this.lastScroll2.x = event.touches.item(1).clientX;
|
||||
this.lastScroll2.y = event.touches.item(1).clientY;
|
||||
this.scrolling = true;
|
||||
}
|
||||
this.hovered = true;
|
||||
}
|
||||
|
||||
private handleTouchEnd(event: TouchEvent) {
|
||||
this.dragging = false;
|
||||
this.scrolling = false;
|
||||
this.hovered = false;
|
||||
}
|
||||
|
||||
enableFly() {
|
||||
this.flyingEnabled = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user