[+] Upload to server

This commit is contained in:
2024-12-17 03:44:51 -05:00
parent c9e3ad68da
commit 927eb984ff
10 changed files with 278 additions and 42 deletions
BIN
View File
Binary file not shown.
+1
View File
@@ -10,6 +10,7 @@
"check": "svelte-check --tsconfig ./tsconfig.json && tsc -p tsconfig.node.json"
},
"devDependencies": {
"@iconify/svelte": "^4.1.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tsconfig/svelte": "^5.0.4",
"sass-embedded": "^1.83.0",
+22 -8
View File
@@ -2,15 +2,17 @@
import svelteLogo from './assets/svelte.svg'
import viteLogo from '/vite.svg'
import Number from './lib/Number.svelte'
import { cfg, eStates, Fmt, JsonTy, Misc, nStates, randInt, range, zero8, type Checkpoint, type i8s } from "./utils";
import { Backend, cfg, eStates, Fmt, JsonTy, Misc, nStates, randInt, range, zero8, type Checkpoint, type i8s, type MetaCheckpoint } from "./utils";
import Line from "./lib/Line.svelte";
import { solve } from "./solver";
import Upload from './lib/Upload.svelte';
import PuzzleInfo from './lib/PuzzleInfo.svelte';
const params = new URLSearchParams(location.search)
const hasTouch = Misc.hasTouch()
// Can pass in puzzle data from props
interface Props { puzzleId?: string, puzzleData?: Checkpoint }
interface Props { puzzleId?: string, puzzleData?: MetaCheckpoint }
export let { puzzleId, puzzleData }: Props = {}
const pid = puzzleId ?? 'slitherlink'
@@ -53,6 +55,7 @@
colors = pt.colors
updateColors()
}
let upload: Checkpoint | null
// Run something on the edges of a cell
function updateEdges(x: number, y: number, condition: (st: number) => boolean, op: (st: number) => number) {
@@ -198,7 +201,7 @@
updateArea.forEach(([dx, dy, _]) => checkPos(x + dx, y + dy))
}
async function editModeReduce(zeros: boolean = false, n = 20) {
async function editModeReduce(zeros: boolean = false, n = 10) {
[solutionHStates, solutionVStates] = [hStates.slice(), vStates.slice()]
if (n === 0) return
// On each iteration, randomly mask n numbers and try to solve the puzzle
@@ -221,8 +224,9 @@
vertStates.some((st, idx) => (st === eStates.selected) !== (solutionVStates[idx] === eStates.selected))) {
masked.forEach(idx => nMask[idx] = 0)
console.log('Solution does not match')
setTimeout(() => editModeReduce(zeros, n - 1), 100)
} else setTimeout(() => editModeReduce(zeros, n), 100)
}
// setTimeout(() => editModeReduce(zeros, n - 1), 100)
// } else setTimeout(() => editModeReduce(zeros, n), 100)
console.log(nMask)
}
@@ -284,14 +288,18 @@
<img src={svelteLogo} alt="Logo"/>
</div>
<div class="rules">
Welcome to SlitherLink! 🧩 The rules are simple: Draw lines between the dots to create one big loop (no crossings, no branches). The numbers are your hints they tell you how many lines should surround them. Left-click to draw, right-click to mark with an X. Can you crack the perfect path?
Welcome to SlitherLink! 🧩 The rules are simple: Draw lines between the dots to create one big loop (no crossings, no branches). The numbers are your hints they tell you how many lines should surround them. Left-click to draw, right-click to mark with an X. Can you crack the perfect path? <a href="https://www.youtube.com/watch?v=fqwE-CpeGS4&list=PLH_elo2OIwaBk44COgFhnJRt0-8xKMBQn">YouTube Tutorial</a>
</div>
{#if statusMsg}
<div class="status" class:error={!complete}>{statusMsg}</div>
{/if}
{#if !editMode}<div class="timer">{Fmt.duration(elapsed)}</div>{/if}
<div>
{#if puzzleData && !editMode}<div class="timer-line">
<PuzzleInfo {puzzleData}></PuzzleInfo>
<div class="timer">{Fmt.duration(elapsed)}</div>
</div>{/if}
<div class="puzzle-grid" style={`height: ${rows * cfg.totalW}px; width: ${cols * cfg.totalW}px;`}
on:click={clickDiv} on:contextmenu={clickDiv} on:keypress={console.log} role="grid" tabindex="0"
on:mousedown={startDrag} on:mousemove={e => dragging && clickDiv(e)} on:mouseup={() => dragging = false}
@@ -313,6 +321,7 @@
{/each}
{/each}
</div>
</div>
{#if editMode}
<div class="btn-div">
@@ -338,7 +347,6 @@
}}>Clear Numbers</button>
<button on:click={() => genNumbers()}>Gen Numbers</button>
<button on:click={() => JsonTy.download(ckpt(), 'slitherlink-checkpoint.json')}>Download</button>
{/if}
<button on:click={() => mode = modes[(modes.indexOf(mode) + 1) % modes.length]}>Mode: {mode}</button>
</div>
@@ -354,6 +362,10 @@
{/each}
</div>
{/if}
<div class="btn-div">
<button on:click={() => JsonTy.download(ckpt(), 'slitherlink-checkpoint.json')}>Download</button>
<button on:click={() => upload = ckpt()}>Upload</button>
</div>
{/if}
<!-- Add Checkpoint -->
@@ -386,4 +398,6 @@
<button on:click={() => completedOverlay = false}>Close</button>
</div></div>
{/if}
{#if upload} <Upload data={upload} callback={() => upload = null}/> {/if}
</main>
+7 -2
View File
@@ -5,15 +5,20 @@
// Get puzzle id from params
const urlParams = new URLSearchParams(window.location.search);
const puzzleId = urlParams.get("puzzle");
const edit = urlParams.get("edit");
let puzzleData: Checkpoint | null = null;
let error: string | null = null;
if (!puzzleId) window.location.href = "/?puzzle=meow";
else Backend.get(puzzleId).then((data) => (puzzleData = data)).catch(e => (error = e));
if (!edit) {
if (!puzzleId) window.location.href = "/?puzzle=meow";
else Backend.get(puzzleId).then((data) => (puzzleData = data)).catch(e => (error = e));
}
</script>
{#if puzzleData !== null && puzzleId !== null}
<App {puzzleData} {puzzleId} />
{:else if edit}
<App></App>
{:else}
<div class="overlay">
<div>
+7 -31
View File
@@ -89,8 +89,8 @@ main
height: 1em
.rules
max-width: 900px
opacity: 0.9
font-size: 0.9em
max-width: 800px
.btn-div
display: flex
@@ -118,9 +118,6 @@ main
font-family: 'Teko', sans-serif
font-size: 2em
color: colors.$accent
margin-bottom: -24px
// Larger gap between letters
letter-spacing: 0.3em
.status
@@ -129,30 +126,9 @@ main
&.error
color: colors.$cross
.overlay
position: fixed
inset: 0
background-color: rgba(0, 0, 0, 0.5)
.timer-line
// Two items, left and right
display: flex
justify-content: center
align-items: center
z-index: 1000
backdrop-filter: blur(5px)
h2, p
user-select: none
margin: 0
> div
background-color: colors.$background
padding: 2rem
border-radius: 8px
display: flex
flex-direction: column
gap: 1rem
max-width: 400px
justify-content: space-between
gap: 10px
margin-bottom: -12px
+148
View File
@@ -0,0 +1,148 @@
@use "colors"
$font: Quicksand, Inter, LXGW Wenkai, Microsoft YaHei, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, Avenir, Helvetica, Arial, sans-serif
$c-main: #b3c6ff
$c-sub: rgba(0, 0, 0, 0.77)
$c-good: #b3ffb9
$c-darker: #646cff
$c-bg: #242424
$c-error: #ff6b6b
$c-shadow: rgba(0, 0, 0, 0.1)
$ov-light: rgba(white, 0.04)
$ov-lighter: rgba(white, 0.08)
$ov-dark: rgba(black, 0.1)
$ov-darker: rgba(black, 0.18)
$nav-height: 4rem
$w-mobile: 560px
$w-max: 900px
$grad-special: linear-gradient(90deg, #ffee94, #ffb798, #ffa3e5, #ebff94)
$border-radius: 12px
$gap: 20px
$transition: all 0.25s
.card
display: flex
flex-direction: column
border-radius: $border-radius
padding: 12px 16px
background: $ov-light
blockquote
$c1: rgba(255, 149, 149, 0.05)
$c2: rgba(255, 152, 152, 0.12)
background: repeating-linear-gradient(45deg, $c1, $c1 10px, $c2 10px, $c2 20px)
padding: 10px 20px 10px 20px
margin: 16px 0
border-left: solid #ff7c7c 3px
border-radius: $border-radius
input, textarea
border-radius: $border-radius
border: 1px solid transparent
padding: 0.6em 1.2em
font-size: 1em
font-weight: 500
font-family: inherit
background-color: $ov-lighter
transition: $transition
box-sizing: border-box
resize: vertical
&:focus, &:focus-visible
border: 1px solid $c-main
outline: none
&.error
border: 1px solid $c-error
select
border-radius: $border-radius
border: 1px solid transparent
padding: 0.6em 1.2em
font-size: 1em
font-weight: 500
font-family: inherit
background-color: $ov-lighter
transition: $transition
box-sizing: border-box
input[type="checkbox"]
width: 1.2em
height: 1.2em
margin: 0
padding: 0
border: 1px solid $c-main
background-color: $ov-lighter
appearance: none
cursor: pointer
flex-shrink: 0
&:checked
background-color: $c-main
border-color: $c-main
label
cursor: pointer
.overlay
position: fixed
inset: 0
background-color: rgba(0, 0, 0, 0.5)
display: flex
justify-content: center
align-items: center
z-index: 1000
backdrop-filter: blur(5px)
h2, p
user-select: none
margin: 0
> div
background-color: colors.$background
padding: 2rem
border-radius: 8px
display: flex
flex-direction: column
gap: 1rem
min-width: 300px
max-width: 400px
.actions
display: flex
flex-direction: column
gap: 16px
button
width: 100%
.loading.overlay
font-size: 28rem
:global(.icon)
opacity: 0.5
> span
position: absolute
inset: 0
display: flex
justify-content: center
align-items: center
background: transparent
letter-spacing: 20px
margin-left: 20px
font-size: 1.5rem
.error
color: $c-error
+28
View File
@@ -0,0 +1,28 @@
<script lang="ts">
export let puzzleData = {}
</script>
<div class="puzzle-desc">
<div class="head">
<span class="puzzle">{puzzleData?.name}</span>
<span class="by">by {puzzleData?.author}</span>
</div>
<div class="desc">
{puzzleData?.description}
</div>
</div>
<style lang="sass">
.puzzle-desc
text-align: left
.puzzle
font-weight: bold
.by
font-size: 0.8em
color: #666
.desc
font-size: 0.8em
color: #eee
</style>
+58
View File
@@ -0,0 +1,58 @@
<script lang="ts">
import { Backend, cfg, nStates, pos, sty, type Checkpoint } from "../utils";
interface Props {
data?: Checkpoint;
callback?: () => void;
}
export let { data, callback }: Props = {}
let name = ""
let author = ""
let description = ""
let error = ""
let id = ""
$: url = id ? `https://${window.location.host}/?puzzle=${id}` : ""
function upload() {
if (!data) return
if (!name || !author || !description) {
error = "Please fill in all fields"
return
}
Backend.post({name, author, description, ...data}).then(res => {
id = res.id
console.log(res);
}).catch(e => alert(e))
}
</script>
{#if data}
<div class="overlay">
<div>
{#if !id}
<h2>Upload Puzzle</h2>
{#if error}
<p class="error">{error}</p>
{:else}
<p>Upload your puzzle to the server</p>
{/if}
<input type="text" placeholder="Puzzle Name" bind:value={name}>
<input type="text" placeholder="Author" bind:value={author}>
<textarea placeholder="Description" bind:value={description}></textarea>
<button on:click={upload}>Upload</button>
{:else}
<h2>Uploaded 🥳</h2>
<span>Your puzzle has been uploaded to the server</span>
<p>You can share it by the following link: </p>
<p><a href={url}>{url}</a></p>
<p>Thank you for your creation!</p>
<button on:click={callback}>Close</button>
{/if}
</div>
</div>
{/if}
+1
View File
@@ -1,5 +1,6 @@
import { mount } from 'svelte'
import './app.sass'
import './aqua.sass'
import './puzzle.sass'
import './fonts/caveat.sass'
import Launcher from './Launcher.svelte'
+6 -1
View File
@@ -3,6 +3,8 @@ export type i8s = Int8Array
export interface Checkpoint { rows: number, cols: number,
hStates: i8s, vStates: i8s, hColors: i8s, vColors: i8s, numbers: i8s, nMask: i8s, colors: string[] }
export interface MetaCheckpoint extends Checkpoint { name: string, author: string, description: string, date?: string }
export const cfg = {
cellW: 20,
lineW: 4,
@@ -75,7 +77,10 @@ export const Backend = {
if (!r.ok) throw new Error(`Failed to get puzzle: ${r.status}`)
return r.text()
}).then(JsonTy.parse),
post: (data: Checkpoint) => fetch(`${cfg.backend}/`, { method: "post", body: JsonTy.stringify(data) }),
post: (data: MetaCheckpoint) => fetch(`${cfg.backend}/`, { method: "post", body: JsonTy.stringify(data) }).then(r => {
if (!r.ok) throw new Error(`Failed to post puzzle: ${r.status}`)
return r.text()
}).then(JsonTy.parse),
}
export const Misc = {