Documentation
Built-in
Gallery

Gallery

Gallery is a built-in service specifically designed for visual novel game galleries (image collections). It allows you to add, remove, and check for items. Gallery data can be persisted via localStorage (standalone) or automatically included in game save (via serialize/deserialize).

Gallery is suitable for managing image collection systems in games, such as character portraits, CG images, background images, etc.

import {Gallery} from "narraleaf-react";

Basic Usage

Creating a Gallery

const gallery = new Gallery<{timestamp: number}>();
 
scene.action([
  gallery.add("item", () => ({
    timestamp: Date.now(),
  }))
]);

Registering the Service

To use Gallery, you need to register it in the story:

story.registerService("gallery", gallery);

After registering, you can access the Gallery using the game context:

const liveGame = useLiveGame();
 
const gallery = liveGame.story?.getService<Gallery<{timestamp: number}>>("gallery");
 
if (gallery) {
  console.log("All items in the gallery:", gallery.$getAll());
}

Complete Example

const gallery = new Gallery<{timestamp: number, description: string}>();
 
scene.action([
  gallery.add("item1", {
    timestamp: Date.now(),
    description: "First item"
  }),
  
  gallery.add("item2", (ctx) => ({
    timestamp: Date.now(),
    description: "Dynamically created item"
  })),
  
  Condition.If(gallery.has("item1"), [
    "Item 1 is unlocked!",
  ]),
  
  gallery.remove("item1"),
  
  gallery.clear()
]);

Persisting with localStorage

You can persist Gallery data to localStorage (opens in a new tab) independently of the game save. Call serialize() to get the data, then deserialize() when loading:

const GALLERY_STORAGE_KEY = "my-game-gallery";
 
// Save gallery to localStorage (e.g. when opening gallery page or on interval)
function saveGalleryToStorage() {
  const gallery = liveGame.story?.getService<Gallery<CgMetadata>>("gallery");
  if (gallery) {
    const data = gallery.serialize();
    localStorage.setItem(GALLERY_STORAGE_KEY, JSON.stringify(data));
  }
}
 
// Load gallery from localStorage (e.g. on game init, before newGame)
function loadGalleryFromStorage() {
  const gallery = liveGame.story?.getService<Gallery<CgMetadata>>("gallery");
  if (gallery) {
    const raw = localStorage.getItem(GALLERY_STORAGE_KEY);
    if (raw) {
      try {
        gallery.deserialize(JSON.parse(raw));
      } catch {}
    }
  }
}

This keeps gallery unlocks across sessions even if the player never uses the full save/load system.

serialize / deserialize with Game Save

Gallery implements serialize() and deserialize(). When you use LiveGame.serialize to save the game, the framework automatically includes Gallery's serialized data in SavedGame.game.services. When you call LiveGame.deserialize to load a saved game, the framework restores all registered services, including Gallery.

So if you use a save system that persists liveGame.serialize() to localStorage, Gallery unlock state is automatically saved and restored with the game—no extra code needed. The serialize/deserialize methods are what enable this integration.

Chainable Methods

These methods need to be used within scene.action and return operation objects that can be chained together.

add

Add an item to the gallery

scene.action([
  gallery.add("item", {
    timestamp: Date.now(),
    description: "Item description"
  })
]);
 
// or using a function
scene.action([
  gallery.add("item", (ctx) => {
    return {
      timestamp: Date.now(),
      description: "Dynamic item"
    };
  })
]);
  • name: string - The name of the item to add
  • metadata: Metadata | ((ctx: ScriptCtx) => Metadata) - The metadata of the item, can be an object or a function that returns metadata

remove

Remove an item from the gallery

scene.action([
  gallery
    .remove("item")
    .remove("item2"),
]);
  • name: string - The name of the item to remove

clear

Clear the gallery

scene.action([
  gallery.clear()
]);

Public Methods

These methods prefixed with $ execute immediately and don't need to be used within scene.action. They are specifically designed for directly manipulating gallery data during game runtime.

has

Check if an item exists in the gallery

Condition.If(gallery.has("item"), [
    "Item is unlocked!",
    // ...
])
  • name: string - The name of the item to check
  • Returns Lambda<boolean> - Returns true if the item exists, false otherwise

$add

Add an item to the gallery immediately

gallery.$add("item", {
  timestamp: Date.now(),
  description: "Immediately added item"
});
  • name: string - The name of the item to add
  • metadata: Metadata - The metadata of the item

$remove

Remove an item from the gallery immediately

gallery.$remove("item");
  • name: string - The name of the item to remove

$clear

Clear the gallery immediately

gallery.$clear();

After calling this method, the gallery will be empty immediately.

$get

Get the metadata of an item

const metadata = gallery.$get("item");
console.log(metadata?.timestamp);
  • name: string - The name of the item to get the metadata of
  • Returns Metadata | undefined - The metadata of the item

$set

Set the metadata of an item

gallery.$set("item", {
  timestamp: Date.now(),
  description: "Updated description"
});
  • name: string - The name of the item to set the metadata of
  • metadata: Metadata - The metadata to set

$getAll

Get all items in the gallery

const allItems = gallery.$getAll();
console.log("All items:", allItems);
  • Returns Record<string, Metadata> - All items in the gallery

$has

Check if an item exists in the gallery

if (gallery.$has("item")) {
  console.log("Item exists");
}
  • name: string - The name of the item to check
  • Returns boolean - Returns true if the item exists, false otherwise

Type Parameters

Metadata

Gallery accepts a generic parameter Metadata to define the type of item metadata:

// Define metadata type
type ItemMetadata = {
  timestamp: number;
  description: string;
  unlocked: boolean;
};
 
// Create Gallery with type constraints
const gallery = new Gallery<ItemMetadata>();
 
// Now all methods will have type checking
gallery.add("item", {
  timestamp: Date.now(),
  description: "Description",
  unlocked: true
});