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 addmetadata: 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 addmetadata: 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 ofmetadata: 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
});