|
| 1 | +import { each, ensure, main, until } from "effection"; |
| 2 | +import { |
| 3 | + close, |
| 4 | + createTerm, |
| 5 | + fixed, |
| 6 | + grow, |
| 7 | + type InputEvent, |
| 8 | + type Op, |
| 9 | + open, |
| 10 | + rgba, |
| 11 | + text, |
| 12 | +} from "../mod.ts"; |
| 13 | +import { |
| 14 | + alternateBuffer, |
| 15 | + cursor, |
| 16 | + mouseTracking, |
| 17 | + settings, |
| 18 | +} from "../settings.ts"; |
| 19 | +import { useInput } from "./use-input.ts"; |
| 20 | +import { useStdin } from "./use-stdin.ts"; |
| 21 | + |
| 22 | +let SWATCHES = [ |
| 23 | + { name: "Rose", r: 255, g: 0, b: 127 }, |
| 24 | + { name: "Crimson", r: 220, g: 20, b: 60 }, |
| 25 | + { name: "Tomato", r: 255, g: 99, b: 71 }, |
| 26 | + { name: "Coral", r: 255, g: 127, b: 80 }, |
| 27 | + { name: "Salmon", r: 250, g: 128, b: 114 }, |
| 28 | + { name: "Scarlet", r: 255, g: 36, b: 0 }, |
| 29 | + { name: "Vermillion", r: 227, g: 66, b: 52 }, |
| 30 | + { name: "Rust", r: 183, g: 65, b: 14 }, |
| 31 | + { name: "Terracotta", r: 204, g: 78, b: 92 }, |
| 32 | + { name: "Brick", r: 203, g: 65, b: 84 }, |
| 33 | + { name: "Tangerine", r: 255, g: 159, b: 0 }, |
| 34 | + { name: "Amber", r: 255, g: 191, b: 0 }, |
| 35 | + { name: "Marigold", r: 234, g: 162, b: 33 }, |
| 36 | + { name: "Gold", r: 255, g: 215, b: 0 }, |
| 37 | + { name: "Honey", r: 235, g: 177, b: 52 }, |
| 38 | + { name: "Saffron", r: 244, g: 196, b: 48 }, |
| 39 | + { name: "Canary", r: 255, g: 239, b: 0 }, |
| 40 | + { name: "Lemon", r: 255, g: 247, b: 0 }, |
| 41 | + { name: "Butter", r: 255, g: 225, b: 128 }, |
| 42 | + { name: "Cream", r: 255, g: 253, b: 208 }, |
| 43 | + { name: "Lime", r: 0, g: 255, b: 0 }, |
| 44 | + { name: "Chartreuse", r: 127, g: 255, b: 0 }, |
| 45 | + { name: "Emerald", r: 80, g: 200, b: 120 }, |
| 46 | + { name: "Jade", r: 0, g: 168, b: 107 }, |
| 47 | + { name: "Mint", r: 152, g: 255, b: 152 }, |
| 48 | + { name: "Sage", r: 188, g: 184, b: 138 }, |
| 49 | + { name: "Forest", r: 34, g: 139, b: 34 }, |
| 50 | + { name: "Pine", r: 1, g: 121, b: 111 }, |
| 51 | + { name: "Olive", r: 128, g: 128, b: 0 }, |
| 52 | + { name: "Fern", r: 79, g: 121, b: 66 }, |
| 53 | + { name: "Teal", r: 0, g: 128, b: 128 }, |
| 54 | + { name: "Cyan", r: 0, g: 255, b: 255 }, |
| 55 | + { name: "Aqua", r: 0, g: 255, b: 255 }, |
| 56 | + { name: "Turquoise", r: 64, g: 224, b: 208 }, |
| 57 | + { name: "Seafoam", r: 159, g: 226, b: 191 }, |
| 58 | + { name: "Cerulean", r: 0, g: 123, b: 167 }, |
| 59 | + { name: "Azure", r: 0, g: 127, b: 255 }, |
| 60 | + { name: "Sky", r: 135, g: 206, b: 235 }, |
| 61 | + { name: "Cornflower", r: 100, g: 149, b: 237 }, |
| 62 | + { name: "Periwinkle", r: 204, g: 204, b: 255 }, |
| 63 | + { name: "Cobalt", r: 0, g: 71, b: 171 }, |
| 64 | + { name: "Royal", r: 65, g: 105, b: 225 }, |
| 65 | + { name: "Navy", r: 0, g: 0, b: 128 }, |
| 66 | + { name: "Midnight", r: 25, g: 25, b: 112 }, |
| 67 | + { name: "Sapphire", r: 15, g: 82, b: 186 }, |
| 68 | + { name: "Indigo", r: 75, g: 0, b: 130 }, |
| 69 | + { name: "Violet", r: 127, g: 0, b: 255 }, |
| 70 | + { name: "Amethyst", r: 153, g: 102, b: 204 }, |
| 71 | + { name: "Lavender", r: 230, g: 230, b: 250 }, |
| 72 | + { name: "Lilac", r: 200, g: 162, b: 200 }, |
| 73 | + { name: "Plum", r: 142, g: 69, b: 133 }, |
| 74 | + { name: "Orchid", r: 218, g: 112, b: 214 }, |
| 75 | + { name: "Magenta", r: 255, g: 0, b: 255 }, |
| 76 | + { name: "Fuchsia", r: 255, g: 0, b: 128 }, |
| 77 | + { name: "Mauve", r: 224, g: 176, b: 255 }, |
| 78 | + { name: "Berry", r: 142, g: 0, b: 82 }, |
| 79 | + { name: "Wine", r: 114, g: 47, b: 55 }, |
| 80 | + { name: "Burgundy", r: 128, g: 0, b: 32 }, |
| 81 | + { name: "Maroon", r: 128, g: 0, b: 0 }, |
| 82 | + { name: "Mahogany", r: 192, g: 64, b: 0 }, |
| 83 | + { name: "Sienna", r: 160, g: 82, b: 45 }, |
| 84 | + { name: "Chocolate", r: 123, g: 63, b: 0 }, |
| 85 | + { name: "Cinnamon", r: 210, g: 105, b: 30 }, |
| 86 | + { name: "Caramel", r: 255, g: 213, b: 128 }, |
| 87 | + { name: "Peach", r: 255, g: 218, b: 185 }, |
| 88 | + { name: "Apricot", r: 251, g: 206, b: 177 }, |
| 89 | + { name: "Sand", r: 194, g: 178, b: 128 }, |
| 90 | + { name: "Tan", r: 210, g: 180, b: 140 }, |
| 91 | + { name: "Khaki", r: 195, g: 176, b: 145 }, |
| 92 | + { name: "Taupe", r: 72, g: 60, b: 50 }, |
| 93 | + { name: "Ivory", r: 255, g: 255, b: 240 }, |
| 94 | + { name: "Pearl", r: 234, g: 224, b: 200 }, |
| 95 | + { name: "Linen", r: 250, g: 240, b: 230 }, |
| 96 | + { name: "Bone", r: 227, g: 218, b: 201 }, |
| 97 | + { name: "Ash", r: 178, g: 190, b: 181 }, |
| 98 | + { name: "Silver", r: 192, g: 192, b: 192 }, |
| 99 | + { name: "Pewter", r: 150, g: 150, b: 150 }, |
| 100 | + { name: "Slate", r: 112, g: 128, b: 144 }, |
| 101 | + { name: "Charcoal", r: 54, g: 69, b: 79 }, |
| 102 | + { name: "Graphite", r: 56, g: 56, b: 56 }, |
| 103 | + { name: "Onyx", r: 53, g: 56, b: 57 }, |
| 104 | + { name: "Jet", r: 52, g: 52, b: 52 }, |
| 105 | + { name: "Obsidian", r: 28, g: 28, b: 28 }, |
| 106 | + { name: "Smoke", r: 115, g: 130, b: 118 }, |
| 107 | + { name: "Steel", r: 113, g: 121, b: 126 }, |
| 108 | + { name: "Iron", r: 82, g: 82, b: 82 }, |
| 109 | + { name: "Gunmetal", r: 42, g: 52, b: 57 }, |
| 110 | + { name: "Titanium", r: 135, g: 134, b: 129 }, |
| 111 | + { name: "Chrome", r: 219, g: 226, b: 233 }, |
| 112 | + { name: "Platinum", r: 229, g: 228, b: 226 }, |
| 113 | + { name: "Quartz", r: 217, g: 217, b: 217 }, |
| 114 | + { name: "Opal", r: 168, g: 195, b: 188 }, |
| 115 | + { name: "Topaz", r: 255, g: 200, b: 124 }, |
| 116 | + { name: "Citrine", r: 228, g: 208, b: 10 }, |
| 117 | + { name: "Jasper", r: 215, g: 59, b: 62 }, |
| 118 | + { name: "Garnet", r: 115, g: 54, b: 53 }, |
| 119 | + { name: "Ruby", r: 224, g: 17, b: 95 }, |
| 120 | + { name: "Carmine", r: 150, g: 0, b: 24 }, |
| 121 | + { name: "Copper", r: 184, g: 115, b: 51 }, |
| 122 | + { name: "Bronze", r: 205, g: 127, b: 50 }, |
| 123 | +]; |
| 124 | + |
| 125 | +let DIM = rgba(80, 80, 90); |
| 126 | +let SELECT_BG = rgba(40, 80, 160); |
| 127 | +let FG = rgba(220, 220, 220); |
| 128 | +let STATUS_BG = rgba(30, 30, 40); |
| 129 | +let STATUS_FG = rgba(180, 180, 190); |
| 130 | + |
| 131 | +function clamp(v: number, min: number, max: number): number { |
| 132 | + if (v < min) { |
| 133 | + return min; |
| 134 | + } else { |
| 135 | + return v > max ? max : v; |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +await main(function* () { |
| 140 | + let { columns, rows } = Deno.stdout.isTerminal() |
| 141 | + ? Deno.consoleSize() |
| 142 | + : { columns: 80, rows: 24 }; |
| 143 | + |
| 144 | + Deno.stdin.setRaw(true); |
| 145 | + |
| 146 | + let stdin = yield* useStdin(); |
| 147 | + let input = useInput(stdin); |
| 148 | + |
| 149 | + let term = yield* until(createTerm({ width: columns, height: rows })); |
| 150 | + |
| 151 | + let tty = settings(alternateBuffer(), cursor(false), mouseTracking()); |
| 152 | + Deno.stdout.writeSync(tty.apply); |
| 153 | + |
| 154 | + yield* ensure(() => { |
| 155 | + Deno.stdout.writeSync(tty.revert); |
| 156 | + }); |
| 157 | + |
| 158 | + let selected = 0; |
| 159 | + let scrollY = 0; |
| 160 | + let viewHeight = rows - 1; |
| 161 | + let maxScroll = Math.max(SWATCHES.length - viewHeight, 0); |
| 162 | + |
| 163 | + function ensureVisible() { |
| 164 | + if (selected < scrollY) { |
| 165 | + scrollY = selected; |
| 166 | + } else if (selected >= scrollY + viewHeight) { |
| 167 | + scrollY = selected - viewHeight + 1; |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + function render(event?: InputEvent) { |
| 172 | + let ops: Op[] = [ |
| 173 | + open("root", { |
| 174 | + layout: { width: grow(), height: grow(), direction: "ttb" }, |
| 175 | + }), |
| 176 | + open("list", { |
| 177 | + layout: { width: grow(), height: grow(), direction: "ttb" }, |
| 178 | + clip: { y: -scrollY }, |
| 179 | + }), |
| 180 | + ]; |
| 181 | + |
| 182 | + for (let i = 0; i < SWATCHES.length; i++) { |
| 183 | + let s = SWATCHES[i]; |
| 184 | + let isSelected = i === selected; |
| 185 | + let bg = isSelected ? SELECT_BG : undefined; |
| 186 | + let idx = String(i + 1).padStart(3, " "); |
| 187 | + |
| 188 | + ops.push( |
| 189 | + open(`s${i}`, { |
| 190 | + layout: { |
| 191 | + direction: "ltr", |
| 192 | + height: fixed(1), |
| 193 | + width: grow(), |
| 194 | + padding: { left: 1 }, |
| 195 | + }, |
| 196 | + bg, |
| 197 | + }), |
| 198 | + open("", { layout: { width: fixed(4), height: fixed(1) } }), |
| 199 | + text(`${idx} `, { color: DIM }), |
| 200 | + close(), |
| 201 | + open("", { |
| 202 | + layout: { width: fixed(3), height: fixed(1) }, |
| 203 | + bg: rgba(s.r, s.g, s.b), |
| 204 | + }), |
| 205 | + text(" "), |
| 206 | + close(), |
| 207 | + open("", { |
| 208 | + layout: { width: grow(), height: fixed(1), padding: { left: 1 } }, |
| 209 | + }), |
| 210 | + text(s.name, { color: FG }), |
| 211 | + close(), |
| 212 | + open("", { layout: { width: fixed(14), height: fixed(1) } }), |
| 213 | + text( |
| 214 | + `rgb(${String(s.r).padStart(3)},${String(s.g).padStart(3)},${ |
| 215 | + String(s.b).padStart(3) |
| 216 | + })`, |
| 217 | + { color: DIM }, |
| 218 | + ), |
| 219 | + close(), |
| 220 | + close(), |
| 221 | + ); |
| 222 | + } |
| 223 | + |
| 224 | + ops.push(close()); // list |
| 225 | + |
| 226 | + let s = SWATCHES[selected]; |
| 227 | + let status = ` ${s.name} rgb(${s.r},${s.g},${s.b}) ${ |
| 228 | + selected + 1 |
| 229 | + }/${SWATCHES.length} j/k:\u2195 q:quit`; |
| 230 | + ops.push( |
| 231 | + open("status", { |
| 232 | + layout: { |
| 233 | + width: grow(), |
| 234 | + height: fixed(1), |
| 235 | + direction: "ltr", |
| 236 | + padding: { left: 1 }, |
| 237 | + }, |
| 238 | + bg: STATUS_BG, |
| 239 | + }), |
| 240 | + text(status, { color: STATUS_FG }), |
| 241 | + close(), |
| 242 | + ); |
| 243 | + |
| 244 | + ops.push(close()); // root |
| 245 | + |
| 246 | + let result = term.render(ops, event ? { event } : undefined); |
| 247 | + let list = result.info.get("list"); |
| 248 | + if (list && list.scrollDelta.y !== 0) { |
| 249 | + scrollY = clamp(scrollY - Math.round(list.scrollDelta.y), 0, maxScroll); |
| 250 | + render(); |
| 251 | + return; |
| 252 | + } |
| 253 | + Deno.stdout.writeSync(result.output); |
| 254 | + } |
| 255 | + |
| 256 | + render(); |
| 257 | + |
| 258 | + for (let event of yield* each(input)) { |
| 259 | + if (event.type === "keydown" && event.ctrl && event.key === "c") break; |
| 260 | + if (event.type === "keydown" && event.key === "q") break; |
| 261 | + |
| 262 | + if (event.type === "keydown") { |
| 263 | + switch (event.code) { |
| 264 | + case "j": |
| 265 | + case "ArrowDown": |
| 266 | + selected = clamp(selected + 1, 0, SWATCHES.length - 1); |
| 267 | + ensureVisible(); |
| 268 | + break; |
| 269 | + case "k": |
| 270 | + case "ArrowUp": |
| 271 | + selected = clamp(selected - 1, 0, SWATCHES.length - 1); |
| 272 | + ensureVisible(); |
| 273 | + break; |
| 274 | + case "d": |
| 275 | + case "PageDown": |
| 276 | + selected = clamp( |
| 277 | + selected + Math.floor(viewHeight / 2), |
| 278 | + 0, |
| 279 | + SWATCHES.length - 1, |
| 280 | + ); |
| 281 | + ensureVisible(); |
| 282 | + break; |
| 283 | + case "u": |
| 284 | + case "PageUp": |
| 285 | + selected = clamp( |
| 286 | + selected - Math.floor(viewHeight / 2), |
| 287 | + 0, |
| 288 | + SWATCHES.length - 1, |
| 289 | + ); |
| 290 | + ensureVisible(); |
| 291 | + break; |
| 292 | + case "g": |
| 293 | + case "Home": |
| 294 | + selected = 0; |
| 295 | + ensureVisible(); |
| 296 | + break; |
| 297 | + case "End": |
| 298 | + selected = SWATCHES.length - 1; |
| 299 | + ensureVisible(); |
| 300 | + break; |
| 301 | + } |
| 302 | + if ((event as InputEvent & { key: string }).key === "G") { |
| 303 | + selected = SWATCHES.length - 1; |
| 304 | + ensureVisible(); |
| 305 | + } |
| 306 | + } |
| 307 | + |
| 308 | + if (event.type === "resize") { |
| 309 | + columns = event.width; |
| 310 | + rows = event.height; |
| 311 | + viewHeight = rows - 1; |
| 312 | + maxScroll = Math.max(SWATCHES.length - viewHeight, 0); |
| 313 | + scrollY = clamp(scrollY, 0, maxScroll); |
| 314 | + ensureVisible(); |
| 315 | + term = yield* until(createTerm({ width: columns, height: rows })); |
| 316 | + } |
| 317 | + |
| 318 | + render(event); |
| 319 | + |
| 320 | + yield* each.next(); |
| 321 | + } |
| 322 | +}); |
0 commit comments