|
| 1 | +{ |
| 2 | + "name": "Scoreboard", |
| 3 | + "description": "A simple standard counter example", |
| 4 | + "files": [ |
| 5 | + { |
| 6 | + "name": "main", |
| 7 | + "content": [ |
| 8 | + "import {", |
| 9 | + "\tcreateMemo,", |
| 10 | + "\tcreateSignal,", |
| 11 | + "\tcreateComputed,", |
| 12 | + "\tonCleanup,", |
| 13 | + "\tFor", |
| 14 | + "} from \"solid-js\";", |
| 15 | + "import { createStore } from \"solid-js/store\";", |
| 16 | + "import { render } from \"solid-js/web\";", |
| 17 | + "", |
| 18 | + "const App = () => {", |
| 19 | + "\tlet newName, newScore;", |
| 20 | + "\tconst [state, setState] = createStore({", |
| 21 | + "\t\t\tplayers: [", |
| 22 | + "\t\t\t\t{ name: \"Mark\", score: 3 },", |
| 23 | + "\t\t\t\t{ name: \"Troy\", score: 2 },", |
| 24 | + "\t\t\t\t{ name: \"Jenny\", score: 1 },", |
| 25 | + "\t\t\t\t{ name: \"David\", score: 8 }", |
| 26 | + "\t\t\t]", |
| 27 | + "\t\t}),", |
| 28 | + "\t\tlastPos = new WeakMap(),", |
| 29 | + "\t\tcurPos = new WeakMap(),", |
| 30 | + "\t\tgetSorted = createMemo((list = []) => {", |
| 31 | + "\t\t\tlist.forEach((p, i) => lastPos.set(p, i));", |
| 32 | + "\t\t\tconst newList = state.players.slice().sort((a, b) => {", |
| 33 | + "\t\t\t\tif (b.score === a.score) return a.name.localeCompare(b.name); // stabalize the sort", |
| 34 | + "\t\t\t\treturn b.score - a.score;", |
| 35 | + "\t\t\t});", |
| 36 | + "\t\t\tlet updated = newList.length !== list.length;", |
| 37 | + "\t\t\tnewList.forEach(", |
| 38 | + "\t\t\t\t(p, i) => lastPos.get(p) !== i && (updated = true) && curPos.set(p, i)", |
| 39 | + "\t\t\t);", |
| 40 | + "\t\t\treturn updated ? newList : list;", |
| 41 | + "\t\t}),", |
| 42 | + "\t\thandleAddClick = () => {", |
| 43 | + "\t\t\tconst name = newName.value,", |
| 44 | + "\t\t\t\tscore = +newScore.value;", |
| 45 | + "\t\t\tif (!name.length || isNaN(score)) return;", |
| 46 | + "\t\t\tsetState(\"players\", (p) => [...p, { name: name, score: score }]);", |
| 47 | + "\t\t\tnewName.value = newScore.value = \"\";", |
| 48 | + "\t\t},", |
| 49 | + "\t\thandleDeleteClick = (player) => {", |
| 50 | + "\t\t\tconst idx = state.players.indexOf(player);", |
| 51 | + "\t\t\tsetState(\"players\", (p) => [...p.slice(0, idx), ...p.slice(idx + 1)]);", |
| 52 | + "\t\t},", |
| 53 | + "\t\thandleScoreChange = (player, { target }) => {", |
| 54 | + "\t\t\tconst score = +target.value;", |
| 55 | + "\t\t\tconst idx = state.players.indexOf(player);", |
| 56 | + "\t\t\tif (isNaN(+score) || idx < 0) return;", |
| 57 | + "\t\t\tsetState(\"players\", idx, \"score\", score);", |
| 58 | + "\t\t},", |
| 59 | + "\t\tcreateStyles = (player) => {", |
| 60 | + "\t\t\tconst [style, setStyle] = createSignal();", |
| 61 | + "\t\t\tcreateComputed(() => {", |
| 62 | + "\t\t\t\tgetSorted();", |
| 63 | + "\t\t\t\tconst offset = lastPos.get(player) * 18 - curPos.get(player) * 18,", |
| 64 | + "\t\t\t\t\tt = setTimeout(() =>", |
| 65 | + "\t\t\t\t\t\tsetStyle({ transition: \"250ms\", transform: null })", |
| 66 | + "\t\t\t\t\t);", |
| 67 | + "\t\t\t\tsetStyle({", |
| 68 | + "\t\t\t\t\ttransform: `translateY(${offset}px)`,", |
| 69 | + "\t\t\t\t\ttransition: null", |
| 70 | + "\t\t\t\t});", |
| 71 | + "\t\t\t\tonCleanup(() => clearTimeout(t));", |
| 72 | + "\t\t\t});", |
| 73 | + "\t\t\treturn style;", |
| 74 | + "\t\t};", |
| 75 | + "", |
| 76 | + "\treturn (", |
| 77 | + "\t\t<div id=\"scoreboard\">", |
| 78 | + "\t\t\t<div class=\"board\">", |
| 79 | + "\t\t\t\t<For each={getSorted()}>", |
| 80 | + "\t\t\t\t\t{(player) => {", |
| 81 | + "\t\t\t\t\t\tconst getStyles = createStyles(player),", |
| 82 | + "\t\t\t\t\t\t\t{ name } = player;", |
| 83 | + "\t\t\t\t\t\treturn (", |
| 84 | + "\t\t\t\t\t\t\t<div class=\"player\" style={getStyles()}>", |
| 85 | + "\t\t\t\t\t\t\t\t<div class=\"name\">{name}</div>", |
| 86 | + "\t\t\t\t\t\t\t\t<div class=\"score\">{player.score}</div>", |
| 87 | + "\t\t\t\t\t\t\t</div>", |
| 88 | + "\t\t\t\t\t\t);", |
| 89 | + "\t\t\t\t\t}}", |
| 90 | + "\t\t\t\t</For>", |
| 91 | + "\t\t\t</div>", |
| 92 | + "\t\t\t<form class=\"admin\">", |
| 93 | + "\t\t\t\t<For each={state.players}>", |
| 94 | + "\t\t\t\t\t{(player) => {", |
| 95 | + "\t\t\t\t\t\tconst { name, score } = player;", |
| 96 | + "\t\t\t\t\t\treturn (", |
| 97 | + "\t\t\t\t\t\t\t<div class=\"player\">", |
| 98 | + "\t\t\t\t\t\t\t\t{name}", |
| 99 | + "\t\t\t\t\t\t\t\t<input", |
| 100 | + "\t\t\t\t\t\t\t\t\tclass=\"score\"", |
| 101 | + "\t\t\t\t\t\t\t\t\ttype=\"number\"", |
| 102 | + "\t\t\t\t\t\t\t\t\tvalue={score}", |
| 103 | + "\t\t\t\t\t\t\t\t\tonInput={[handleScoreChange, player]}", |
| 104 | + "\t\t\t\t\t\t\t\t/>", |
| 105 | + "\t\t\t\t\t\t\t\t<button type=\"button\" onClick={[handleDeleteClick, player]}>", |
| 106 | + "\t\t\t\t\t\t\t\t\tx", |
| 107 | + "\t\t\t\t\t\t\t\t</button>", |
| 108 | + "\t\t\t\t\t\t\t</div>", |
| 109 | + "\t\t\t\t\t\t);", |
| 110 | + "\t\t\t\t\t}}", |
| 111 | + "\t\t\t\t</For>", |
| 112 | + "\t\t\t\t<div class=\"player\">", |
| 113 | + "\t\t\t\t\t<input", |
| 114 | + "\t\t\t\t\t\ttype=\"text\"", |
| 115 | + "\t\t\t\t\t\tname=\"name\"", |
| 116 | + "\t\t\t\t\t\tplaceholder=\"New player...\"", |
| 117 | + "\t\t\t\t\t\tref={newName}", |
| 118 | + "\t\t\t\t\t/>", |
| 119 | + "\t\t\t\t\t<input class=\"score\" type=\"number\" name=\"score\" ref={newScore} />", |
| 120 | + "\t\t\t\t\t<button type=\"button\" onClick={handleAddClick}>", |
| 121 | + "\t\t\t\t\t\tAdd", |
| 122 | + "\t\t\t\t\t</button>", |
| 123 | + "\t\t\t\t</div>", |
| 124 | + "\t\t\t</form>", |
| 125 | + "\t\t</div>", |
| 126 | + "\t);", |
| 127 | + "};", |
| 128 | + "", |
| 129 | + "render(App, document.getElementById(\"app\"));" |
| 130 | + ] |
| 131 | + } |
| 132 | + ] |
| 133 | +} |
0 commit comments