180 |
192 |
193 |
227 |
228 | );
229 | }
230 |
231 | function Toolbar({
232 | day,
233 | startedAt,
234 | epochDuration,
235 | theme,
236 | palette,
237 | colorIndex,
238 | dispatch,
239 | onSave,
240 | drawMode,
241 | eraserMode,
242 | }: {
243 | day: number;
244 | startedAt: bigint;
245 | epochDuration: bigint;
246 | theme: string;
247 | palette: string[];
248 | colorIndex: number;
249 | dispatch: (action: Action) => void;
250 | onSave: () => void;
251 | drawMode: boolean;
252 | eraserMode: boolean;
253 | }) {
254 | return (
255 |
256 |
257 |
258 | Day {day}: {theme}
259 |
260 |
261 | Canvas flips in{" "}
262 |
263 |
264 |
265 |
268 |
271 |
274 |
277 |
283 |
290 |
291 | {palette.map((color, index) => (
292 |
301 | ))}
302 |
303 |
304 | );
305 | }
306 |
307 | type Point2D = { x: number; y: number };
308 |
309 | type State = {
310 | size: number;
311 | down: boolean;
312 | erasing: boolean;
313 | pixelSize: number;
314 | colorIndex: number;
315 | pixels: Pixels;
316 | drawMode: boolean;
317 | eraserMode: boolean;
318 | };
319 |
320 | const initialState: State = {
321 | size: 256,
322 | down: false,
323 | erasing: false,
324 | pixelSize: 3,
325 | colorIndex: 0,
326 | pixels: new Pixels(),
327 | drawMode: false,
328 | eraserMode: false,
329 | };
330 |
331 | type Action =
332 | | { type: "init"; size: number }
333 | | { type: "pick"; index: number }
334 | | { type: "down"; where: Point2D; erase: boolean }
335 | | { type: "move"; where: Point2D }
336 | | { type: "up" }
337 | | { type: "leave" }
338 | | { type: "zoom-in" }
339 | | { type: "zoom-out" }
340 | | { type: "reset" }
341 | | { type: "toggle-draw-mode" }
342 | | { type: "toggle-eraser-mode" };
343 |
344 | function reducer(state: State, action: Action): State {
345 | switch (action.type) {
346 | case "pick":
347 | return {
348 | ...state,
349 | colorIndex: action.index,
350 | };
351 |
352 | case "down":
353 | case "move":
354 | if (
355 | action.where.x < 0 ||
356 | action.where.x >= state.size ||
357 | action.where.y < 0 ||
358 | action.where.y >= state.size
359 | ) {
360 | return state;
361 | }
362 |
363 | if (action.type === "move" && !state.down) {
364 | return state;
365 | }
366 |
367 | const erasing = action.type === "down" ? action.erase : state.erasing;
368 |
369 | return {
370 | ...state,
371 | erasing,
372 | down: true,
373 | pixels: state.pixels.set(
374 | action.where.x,
375 | action.where.y,
376 | erasing ? null : state.colorIndex
377 | ),
378 | };
379 |
380 | case "up":
381 | case "leave":
382 | return { ...state, down: false, erasing: false };
383 |
384 | case "reset":
385 | return { ...state, pixels: new Pixels() };
386 |
387 | case "zoom-in":
388 | return { ...state, pixelSize: Math.min(20, state.pixelSize + 1) };
389 |
390 | case "zoom-out":
391 | return { ...state, pixelSize: Math.max(1, state.pixelSize - 1) };
392 |
393 | case "toggle-draw-mode":
394 | return { ...state, drawMode: !state.drawMode };
395 |
396 | case "toggle-eraser-mode":
397 | return { ...state, eraserMode: !state.eraserMode };
398 |
399 | default:
400 | return state;
401 | }
402 | }
403 |
404 | const RULES = `\
405 | BasePaint Rules:
406 |
407 | 😊 Be Kind: Be patient with each other. We're all here to learn and create together.
408 | 🖌️ Be Original: Don't copy another artist's pixel artwork.
409 | 🥸 Be Yourself: One brush per painter. Use your brush invites on new artists!
410 | 🧠 Be Creative: Help others but don't trace or spam unnecessary pixels (blobs, checkers or borders).
411 | ⚠️ Keep It Clean: No QR Codes, project names, logos, offensive symbols, etc.
412 | 🎨 CC0: Your artwork on this canvas will be released under a CC0 license in the public domain.
413 | `;
414 |
415 | export default memo(Canvas);
416 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import "./style.css";
2 | import { render } from "preact";
3 | import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
4 | import {
5 | createWalletClient,
6 | custom,
7 | parseAbi,
8 | parseAbiItem,
9 | publicActions,
10 | } from "viem";
11 | import { Address } from "viem";
12 | import Canvas from "./Canvas";
13 | import {
14 | BASEPAINT_ADDRESS,
15 | BRUSH_ADDRESS,
16 | METADATA_ADDRESS,
17 | } from "./constants";
18 | import Withdraw from "./Withdraw";
19 | import Mint from "./Mint";
20 | import Button from "./Button";
21 | import { base } from "viem/chains";
22 |
23 | export type Client = NonNullable