PhoenixKitProjects.Whiteboards (PhoenixKitProjects v0.23.3)

Copy Markdown View Source

Project whiteboards (Step 11): freeform drawing boards on core's Fresco/Etcher/annotations stack.

Since core V183 an annotation can anchor to any target_type + target_uuid, and Fresco renders a scene with zero images, so a board is just its row: create/3 inserts it and core's MediaCanvasViewer draws it in board mode (target_type: "projects_whiteboard", target_uuid: board.uuid) on an empty, infinite canvas — no file, no Storage, no folder. The blank-background bridge this module used to run (a salted solid-white PNG per board, registered as a Storage file, drawn over as if it were a photo — Max, 2026-09-05: "the dev added support to the package so that hack wouldn't be needed") is gone from the create path. Boards made by it keep their file and keep working: a board WITH a file_uuid renders through the file viewer as before, a board without one through board mode. create_board_for_file/3 (a board over a real image) still exists for the former shape.

Deleting a board deletes the ROW; a board's shapes go with it (the annotations are keyed by its uuid), and a file-backed board's background file stays in the project folder — consistent with the hub's disable-hides-never-deletes philosophy.

Summary

Functions

Creates a whiteboard: one row, no file (chain V16 / core V183 — the canvas is drawn from the board's dimensions and its shapes anchor to target_type/0 + the board's uuid). opts[:actor_uuid] is REQUIRED — it is the board's creator and the actor on the activity row.

The DB-side composition: board row for an EXISTING file + project-folder filing + activity/broadcast. Split from create/3 so the row logic is exercisable without configured storage buckets (tests, and any future "board from an existing image" flow).

Deletes the board row. A file-less board's shapes go with it (nothing else holds them). A file-backed board's background file — and the drawings living in its annotation rows — stays in the project folder, still reachable from the Files page as an annotated image.

Drops the shapes of every FILE-LESS board of a project — for the project delete, whose ON DELETE CASCADE removes the board rows but cannot reach core's annotations table (a file-less board's shapes have no FK to anything; the sweep, 2026-09-05). Returns the number of shapes removed.

Fetches a board scoped to its project (nil on cross-project uuids).

Boards for a project, in position/creation order.

The target_type this module's boards anchor annotations to.

The :board assign core's MediaCanvasViewer takes for a file-less board: the target pair and the canvas extent. nil for a board that still has a file — render that one through the file path.

The curated file map MediaCanvasViewer expects (same shape core's MediaViewer.curate_file/1 builds — that one is private). Nil when the file is gone or Storage errors: the LV renders a fallback card instead of a canvas.

Functions

create(project, name, opts \\ [])

@spec create(map(), String.t(), keyword()) ::
  {:ok, PhoenixKitProjects.Schemas.Whiteboard.t()} | {:error, term()}

Creates a whiteboard: one row, no file (chain V16 / core V183 — the canvas is drawn from the board's dimensions and its shapes anchor to target_type/0 + the board's uuid). opts[:actor_uuid] is REQUIRED — it is the board's creator and the actor on the activity row.

Options: :width/:height (default 1920×1080, capped 8000).

create_board_for_file(project, file_uuid, attrs)

@spec create_board_for_file(map(), binary(), map()) ::
  {:ok, PhoenixKitProjects.Schemas.Whiteboard.t()} | {:error, term()}

The DB-side composition: board row for an EXISTING file + project-folder filing + activity/broadcast. Split from create/3 so the row logic is exercisable without configured storage buckets (tests, and any future "board from an existing image" flow).

delete(board, opts \\ [])

@spec delete(PhoenixKitProjects.Schemas.Whiteboard.t(), keyword()) ::
  :ok | {:error, term()}

Deletes the board row. A file-less board's shapes go with it (nothing else holds them). A file-backed board's background file — and the drawings living in its annotation rows — stays in the project folder, still reachable from the Files page as an annotated image.

delete_shapes_for_project(project_uuid)

@spec delete_shapes_for_project(String.t()) :: non_neg_integer()

Drops the shapes of every FILE-LESS board of a project — for the project delete, whose ON DELETE CASCADE removes the board rows but cannot reach core's annotations table (a file-less board's shapes have no FK to anything; the sweep, 2026-09-05). Returns the number of shapes removed.

get(project_uuid, board_uuid)

Fetches a board scoped to its project (nil on cross-project uuids).

list_for_project(project_uuid)

@spec list_for_project(binary()) :: [PhoenixKitProjects.Schemas.Whiteboard.t()]

Boards for a project, in position/creation order.

rename(board, name, opts \\ [])

Renames a board.

target_type()

@spec target_type() :: String.t()

The target_type this module's boards anchor annotations to.

viewer_board(board)

@spec viewer_board(PhoenixKitProjects.Schemas.Whiteboard.t()) :: map() | nil

The :board assign core's MediaCanvasViewer takes for a file-less board: the target pair and the canvas extent. nil for a board that still has a file — render that one through the file path.

viewer_file(file_uuid)

@spec viewer_file(binary()) :: map() | nil

The curated file map MediaCanvasViewer expects (same shape core's MediaViewer.curate_file/1 builds — that one is private). Nil when the file is gone or Storage errors: the LV renders a fallback card instead of a canvas.