sync: foundry v0.2

This commit is contained in:
2025-10-09 20:55:30 +02:00
parent 95a71cb6e2
commit 0a4148abaa
23 changed files with 1285 additions and 143 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,15 @@
{
"id": "investigation-board",
"title": "Investigation Board",
"description": "A Foundry VTT module that lets everyone create...photo notes on the scene as collaborative investigation tools.",
"version": "1.3.2",
"authors": [{ "name": "Gus", "url": "", "email": "" }],
"compatibility": { "minimum": "12", "verified": "13" },
"esmodules": [
"scripts/settings.js",
"scripts/investigation-board.js"
],
"styles": ["styles/style.css"],
"packs": [],
"templates": ["templates/drawing-sheet.html"],
}
@@ -0,0 +1,471 @@
//investigation-boards.js
import { registerSettings } from "./settings.js";
const MODULE_ID = "investigation-board";
const BASE_FONT_SIZE = 15;
const PIN_COLORS = ["redPin.webp", "bluePin.webp", "yellowPin.webp", "greenPin.webp"];
function getBaseCharacterLimits() {
return game.settings.get(MODULE_ID, "baseCharacterLimits") || {
sticky: 60,
photo: 15,
index: 200,
};
}
function getDynamicCharacterLimits(noteType, currentFontSize) {
const baseLimits = getBaseCharacterLimits();
const scaleFactor = BASE_FONT_SIZE / currentFontSize;
const limits = baseLimits[noteType] || { sticky: 60, photo: 15, index: 200 };
return {
sticky: Math.round(limits.sticky * scaleFactor),
photo: Math.round(limits.photo * scaleFactor),
index: Math.round(limits.index * scaleFactor),
};
}
class CustomDrawingSheet extends DrawingConfig {
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["custom-drawing-sheet"],
template: "modules/investigation-board/templates/drawing-sheet.html",
width: 400,
height: "auto",
title: "Note Configuration",
});
}
getData(options) {
const data = super.getData(options);
data.noteType = this.object.flags[MODULE_ID]?.type || "sticky";
data.text = this.object.flags[MODULE_ID]?.text || "Default Text";
data.image = this.object.flags[MODULE_ID]?.image || "modules/investigation-board/assets/placeholder.webp";
// Pass along the extra identityName for futuristic photo notes
data.identityName = this.object.flags[MODULE_ID]?.identityName || "";
// Include the board mode from settings for conditional display in the template
data.boardMode = game.settings.get(MODULE_ID, "boardMode");
data.noteTypes = {
sticky: "Sticky Note",
photo: "Photo Note",
index: "Index Card",
};
return data;
}
async _updateObject(event, formData) {
const updates = {
[`flags.${MODULE_ID}.type`]: formData.noteType,
[`flags.${MODULE_ID}.text`]: formData.text,
[`flags.${MODULE_ID}.image`]: formData.image || "modules/investigation-board/assets/placeholder.webp",
};
if (formData.identityName !== undefined) {
updates[`flags.${MODULE_ID}.identityName`] = formData.identityName;
}
await this.object.update(updates);
const drawing = canvas.drawings.get(this.object.id);
if (drawing) drawing.refresh();
}
// Add activateListeners to hook up the file-picker button.
activateListeners(element) {
super.activateListeners(html);
html.find(".file-picker-button").click(ev => {
ev.preventDefault();
// Open Foundry's FilePicker for images; adjust the "current" directory if needed.
new FilePicker({
type: "image",
current: "modules/investigation-board/assets/",
callback: path => {
// Update the readonly input with the chosen image path.
const input = element.querySelector("input[name='image']"); if (input) input.value = path;
}
}).browse();
});
}
}
class CustomDrawing extends Drawing {
constructor(...args) {
super(...args);
this.bgSprite = null;
this.pinSprite = null;
this.noteText = null;
this.photoImageSprite = null;
this.identityNameText = null;
this.futuristicText = null;
}
// Ensure sprites are created when the drawing is first rendered.
async draw() {
await super.draw();
await this._updateSprites();
return this;
}
// Ensure sprites update correctly on refresh.
async refresh() {
await super.refresh();
await this._updateSprites();
return this;
}
async _updateSprites() {
const noteData = this.document.flags[MODULE_ID];
if (!noteData) return;
const isPhoto = noteData.type === "photo";
const isIndex = noteData.type === "index";
const mode = game.settings.get(MODULE_ID, "boardMode");
// FUTURISTIC PHOTO NOTE LAYOUT
if (isPhoto && mode === "futuristic") {
const fullWidth = game.settings.get(MODULE_ID, "photoNoteWidth");
const margin = 10;
const photoImgWidth = fullWidth * 0.4;
const photoImgHeight = photoImgWidth * (4 / 3);
const textAreaX = margin + photoImgWidth + margin;
const fullHeight = photoImgHeight + margin * 2;
// --- Background Frame ---
if (!this.bgSprite) {
this.bgSprite = new PIXI.Sprite();
this.addChildAt(this.bgSprite, 0);
}
try {
// Always use the fixed photo frame image.
this.bgSprite.texture = PIXI.Texture.from("modules/investigation-board/assets/photoFrame.webp");
} catch (err) {
console.error("Failed to load photo frame texture", err);
this.bgSprite.texture = PIXI.Texture.EMPTY;
}
this.bgSprite.width = fullWidth;
this.bgSprite.height = fullHeight;
// --- Foreground (User-Assigned) Photo ---
if (!this.photoImageSprite) {
this.photoImageSprite = new PIXI.Sprite();
this.addChild(this.photoImageSprite);
}
try {
// Use a fallback if no image is assigned.
const imagePath = noteData.image || "modules/investigation-board/assets/placeholder.webp";
this.photoImageSprite.texture = PIXI.Texture.from(imagePath);
} catch (err) {
console.error(`Failed to load user photo: ${noteData.image}`, err);
this.photoImageSprite.texture = PIXI.Texture.EMPTY;
}
// Position the user photo inside the frame.
this.photoImageSprite.width = fullWidth * 0.9;
this.photoImageSprite.height = fullHeight * 0.9;
this.photoImageSprite.position.set(fullWidth * 0.05, fullHeight * 0.05);
// --- Identity Name and Additional Text (Futuristic) ---
const font = game.settings.get(MODULE_ID, "font");
const baseFontSize = game.settings.get(MODULE_ID, "baseFontSize");
const fontSize = (fullWidth / 200) * baseFontSize;
const textStyle = new PIXI.TextStyle({
fontFamily: font,
fontSize: fontSize,
fill: "#000000",
wordWrap: true,
wordWrapWidth: fullWidth - textAreaX - margin,
align: "left",
});
if (!this.identityNameText) {
this.identityNameText = new PIXI.Text("", textStyle);
this.addChild(this.identityNameText);
}
this.identityNameText.text = noteData.identityName || "Name";
this.identityNameText.style = textStyle;
this.identityNameText.position.set(textAreaX, margin);
if (!this.futuristicText) {
this.futuristicText = new PIXI.Text("", textStyle);
this.addChild(this.futuristicText);
}
this.futuristicText.text = noteData.text || "";
this.futuristicText.style = textStyle;
this.futuristicText.position.set(textAreaX, margin + this.identityNameText.height + 5);
// Remove default note text if present.
if (this.noteText) {
this.removeChild(this.noteText);
this.noteText.destroy();
this.noteText = null;
}
// --- Pin Handling (Futuristic) ---
const pinSetting = game.settings.get(MODULE_ID, "pinColor");
if (pinSetting === "none") {
if (this.pinSprite) {
this.removeChild(this.pinSprite);
this.pinSprite.destroy();
this.pinSprite = null;
}
} else {
if (!this.pinSprite) {
this.pinSprite = new PIXI.Sprite();
this.addChild(this.pinSprite);
}
let pinColor = noteData.pinColor;
if (!pinColor) {
pinColor = (pinSetting === "random")
? PIN_COLORS[Math.floor(Math.random() * PIN_COLORS.length)]
: `${pinSetting}Pin.webp`;
if (this.document.isOwner) {
await this.document.update({ [`flags.${MODULE_ID}.pinColor`]: pinColor });
}
}
const pinImage = `modules/investigation-board/assets/${pinColor}`;
try {
this.pinSprite.texture = PIXI.Texture.from(pinImage);
} catch (err) {
console.error(`Failed to load pin texture: ${pinImage}`, err);
this.pinSprite.texture = PIXI.Texture.EMPTY;
}
this.pinSprite.width = 40;
this.pinSprite.height = 40;
this.pinSprite.position.set(fullWidth / 2 - 20, 3);
}
return; // End early for futuristic photo notes.
}
// STANDARD LAYOUT (Modern photo notes, sticky, index, etc.)
const width = isPhoto
? game.settings.get(MODULE_ID, "photoNoteWidth")
: isIndex
? game.settings.get(MODULE_ID, "indexNoteWidth") || 600
: game.settings.get(MODULE_ID, "stickyNoteWidth");
const height = isPhoto
? Math.round(width / (225 / 290))
: isIndex
? Math.round(width / (600 / 400))
: width;
// Background Image based on board mode.
const getBackgroundImage = (noteType, mode) => {
if (mode === "futuristic") {
if (noteType === "photo") return "modules/investigation-board/assets/futuristic_photoFrame.webp";
if (noteType === "index") return "modules/investigation-board/assets/futuristic_note_index.webp";
return "modules/investigation-board/assets/futuristic_note_white.webp";
} else if (mode === "custom") {
if (noteType === "photo") return "modules/investigation-board/assets/custom_photoFrame.webp";
if (noteType === "index") return "modules/investigation-board/assets/custom_note_index.webp";
return "modules/investigation-board/assets/custom_note_white.webp";
}
// Default "modern" mode:
if (noteType === "photo") return "modules/investigation-board/assets/photoFrame.webp";
if (noteType === "index") return "modules/investigation-board/assets/note_index.webp";
return "modules/investigation-board/assets/note_white.webp";
};
const bgImage = getBackgroundImage(noteData.type, mode);
if (!this.bgSprite) {
this.bgSprite = new PIXI.Sprite();
this.addChild(this.bgSprite);
}
try {
this.bgSprite.texture = PIXI.Texture.from(bgImage);
} catch (err) {
console.error(`Failed to load background texture: ${bgImage}`, err);
this.bgSprite.texture = PIXI.Texture.EMPTY;
}
this.bgSprite.width = width;
this.bgSprite.height = height;
// --- Foreground (User-Assigned) Photo for Modern Mode ---
// (This is the code missing from your current version.)
if (isPhoto) {
const fgImage = noteData.image || "modules/investigation-board/assets/placeholder.webp";
if (!this.photoImageSprite) {
this.photoImageSprite = new PIXI.Sprite();
this.addChild(this.photoImageSprite);
}
try {
this.photoImageSprite.texture = PIXI.Texture.from(fgImage);
} catch (err) {
console.error(`Failed to load foreground texture: ${fgImage}`, err);
this.photoImageSprite.texture = PIXI.Texture.EMPTY;
}
// Use offsets similar to your old code.
const widthOffset = width * 0.13333;
const heightOffset = height * 0.30246;
this.photoImageSprite.width = width - widthOffset;
this.photoImageSprite.height = height - heightOffset;
this.photoImageSprite.position.set(widthOffset / 2, heightOffset / 2);
this.photoImageSprite.visible = true;
} else if (this.photoImageSprite) {
this.photoImageSprite.visible = false;
}
// --- Pin Handling (Standard) ---
{
const pinSetting = game.settings.get(MODULE_ID, "pinColor");
if (pinSetting === "none") {
if (this.pinSprite) {
this.removeChild(this.pinSprite);
this.pinSprite.destroy();
this.pinSprite = null;
}
} else {
if (!this.pinSprite) {
this.pinSprite = new PIXI.Sprite();
this.addChild(this.pinSprite);
}
let pinColor = noteData.pinColor;
if (!pinColor) {
pinColor = (pinSetting === "random")
? PIN_COLORS[Math.floor(Math.random() * PIN_COLORS.length)]
: `${pinSetting}Pin.webp`;
if (this.document.isOwner) {
await this.document.update({ [`flags.${MODULE_ID}.pinColor`]: pinColor });
}
}
const pinImage = `modules/investigation-board/assets/${pinColor}`;
try {
this.pinSprite.texture = PIXI.Texture.from(pinImage);
} catch (err) {
console.error(`Failed to load pin texture: ${pinImage}`, err);
this.pinSprite.texture = PIXI.Texture.EMPTY;
}
this.pinSprite.width = 40;
this.pinSprite.height = 40;
this.pinSprite.position.set(width / 2 - 20, 3);
}
}
// Default text layout for non-futuristic notes.
const font = game.settings.get(MODULE_ID, "font");
const baseFontSize = game.settings.get(MODULE_ID, "baseFontSize");
const fontSize = (width / 200) * baseFontSize;
const textStyle = new PIXI.TextStyle({
fontFamily: font,
fontSize: fontSize,
fill: "#000000",
wordWrap: true,
wordWrapWidth: width - 15,
align: "center",
});
const truncatedText = this._truncateText(noteData.text || "Default Text", font, noteData.type, fontSize);
if (!this.noteText) {
this.noteText = new PIXI.Text(truncatedText, textStyle);
this.noteText.anchor.set(0.5);
this.addChild(this.noteText);
} else {
this.noteText.style = textStyle;
this.noteText.text = truncatedText;
}
this.noteText.position.set(width / 2, isPhoto ? height - 25 : height / 2);
}
_truncateText(text, font, noteType, currentFontSize) {
const limits = getDynamicCharacterLimits(font, currentFontSize);
const charLimit = limits[noteType] || 100;
return text.length <= charLimit ? text : text.slice(0, charLimit).trim() + "...";
}
}
async function createNote(noteType) {
const scene = canvas.scene;
if (!scene) {
console.error("Cannot create note: No active scene.");
return;
}
// Retrieve width settings (or use defaults)
const stickyW = game.settings.get(MODULE_ID, "stickyNoteWidth") || 200;
const photoW = game.settings.get(MODULE_ID, "photoNoteWidth") || 225;
const indexW = game.settings.get(MODULE_ID, "indexNoteWidth") || 600;
const width = noteType === "photo" ? photoW
: noteType === "index" ? indexW
: stickyW;
const height = noteType === "photo" ? Math.round(photoW / (225 / 290))
: noteType === "index" ? Math.round(indexW / (600 / 400))
: stickyW;
const dims = canvas.dimensions;
const x = dims.width / 2;
const y = dims.height / 2;
// Get default text from settings (fallback if missing)
const defaultText = game.settings.get(MODULE_ID, `${noteType}NoteDefaultText`) || "Notes";
// Determine board mode and include identityName if note is a futuristic photo note
const boardMode = game.settings.get(MODULE_ID, "boardMode");
const extraFlags = {};
if (noteType === "photo" && boardMode === "futuristic") {
extraFlags.identityName = "";
}
await canvas.scene.createEmbeddedDocuments("Drawing", [
{
type: "r",
author: game.user.id,
x,
y,
shape: { width, height },
fillColor: "#ffffff",
fillAlpha: 1,
strokeColor: "transparent",
strokeAlpha: 0,
locked: false,
flags: {
[MODULE_ID]: {
type: noteType,
text: defaultText,
...extraFlags
},
},
"flags.core.sheetClass": "investigation-board.CustomDrawingSheet",
"ownership": { default: 3 },
},
]);
canvas.drawings.activate();
}
Hooks.on("getSceneControlButtons", (controls) => {
const journalControls = controls.find((c) => c.name === "notes");
if (!journalControls) return;
journalControls.tools.push(
{ name: "createStickyNote", title: "Create Sticky Note", icon: "fas fa-sticky-note", onClick: () => createNote("sticky"), button: true },
{ name: "createPhotoNote", title: "Create Photo Note", icon: "fa-solid fa-camera-polaroid", onClick: () => createNote("photo"), button: true },
{ name: "createIndexCard", title: "Create Index Card", icon: "fa-regular fa-subtitles", onClick: () => createNote("index"), button: true }
);
});
Hooks.once("init", () => {
registerSettings();
CONFIG.Drawing.objectClass = CustomDrawing;
DocumentSheetConfig.registerSheet(DrawingDocument, "investigation-board", CustomDrawingSheet, {
label: "Note Drawing Sheet",
types: ["base"],
makeDefault: false,
});
console.log("Investigation Board module initialized.");
});
export { CustomDrawing, CustomDrawingSheet };
@@ -0,0 +1,167 @@
export const MODULE_ID = "investigation-board";
export const registerSettings = function() {
const refreshAllDrawings = () => {
if (canvas.drawings) {
canvas.drawings.placeables.forEach(drawing => {
if (drawing.document.flags[MODULE_ID]) {
drawing.refresh();
}
});
}
};
// Update the pinColor setting to include a "No Pins" option.
game.settings.register(MODULE_ID, "pinColor", {
name: "Pin Color",
hint: "Choose the color of the pin for notes. Selecting 'Random' will randomly assign a pin color. Select 'No Pins' to disable pin display.",
scope: "world",
config: true,
type: String,
choices: {
random: "Random",
red: "Red",
blue: "Blue",
yellow: "Yellow",
green: "Green",
none: "No Pins"
},
default: "random",
onChange: () => {
if (canvas.drawings) {
canvas.drawings.placeables.forEach(drawing => drawing.refresh());
}
}
});
// Register a new setting for board mode selection.
game.settings.register(MODULE_ID, "boardMode", {
name: "Board Mode",
hint: "Select the board mode to change the styling of notes.",
scope: "world",
config: true,
type: String,
choices: {
modern: "Modern",
futuristic: "Futuristic",
custom: "Custom"
},
default: "modern",
onChange: () => refreshAllDrawings()
});
// Register existing settings
game.settings.register(MODULE_ID, "stickyNoteWidth", {
name: "Sticky Note Width",
hint: "The width (in pixels) for all newly created sticky notes (default: 200).",
scope: "world",
config: true,
type: Number,
default: 200,
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "photoNoteWidth", {
name: "Photo Note Width",
hint: "The width (in pixels) for all newly created photo notes (default: 225).",
scope: "world",
config: true,
type: Number,
default: 225,
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "indexNoteWidth", {
name: "Index Note Width",
hint: "The width (in pixels) for all newly created index cards (default: 600).",
scope: "world",
config: true,
type: Number,
default: 600,
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "baseFontSize", {
name: "Base Font Size",
hint: "The font size (in pixels) for text when the note width is at its default size (default: 16).",
scope: "world",
config: true,
type: Number,
default: 16,
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "font", {
name: "Font",
hint: "Choose the font to be used in notes.",
scope: "world",
config: true,
type: String,
choices: {
"Rock Salt": "Rock Salt",
"Courier New": "Courier New",
"Times New Roman": "Times New Roman",
"Signika": "Signika",
"Arial": "Arial"
},
default: "Rock Salt",
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "characterNameKey", {
name: "Character Name Key",
hint: "Specify the key path to retrieve the name (e.g., 'prototypeToken.name' or 'system.alias' for Blades in the Dark). If empty, defaults to 'name'.",
scope: "world",
config: true,
default: "prototypeToken.name",
type: String,
});
game.settings.register(MODULE_ID, "stickyNoteDefaultText", {
name: "Default Sticky Note Text",
hint: "The default text to use for new sticky notes.",
scope: "world",
config: true,
type: String,
default: "Clue",
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "photoNoteDefaultText", {
name: "Default Photo Note Text",
hint: "The default text to use for new photo notes.",
scope: "world",
config: true,
type: String,
default: "Suspect/Place",
onChange: () => refreshAllDrawings()
});
game.settings.register(MODULE_ID, "indexNoteDefaultText", {
name: "Default Index Note Text",
hint: "The default text to use for new index notes.",
scope: "world",
config: true,
type: String,
default: "Notes",
onChange: () => refreshAllDrawings()
});
// Register base font size and character limits
game.settings.register(MODULE_ID, "baseCharacterLimits", {
name: "Base Character Limits",
hint: "The base character limits for each font and note type. Edit this JSON to customize.",
scope: "world",
config: false, // Hidden from the settings UI
type: Object,
default: {
"Rock Salt": { sticky: 90, photo: 20, index: 210 },
"Courier New": { sticky: 250, photo: 30, index: 580 },
"Times New Roman": { sticky: 200, photo: 30, index: 800 },
"Signika": { sticky: 200, photo: 30, index: 650 },
"Arial": { sticky: 200, photo: 30, index: 650 }
}
});
};
@@ -0,0 +1,134 @@
@font-face {
font-family: 'Rock Salt';
src: url('../assets/fonts/rock_salt.ttf') format('ttf');
font-weight: normal;
font-style: normal;
}
.custom-drawing-text {
font-family: 'Rock Salt', Arial, sans-serif;
color: #000000; /* Black text */
}
.sticky-note {
background-image: url("modules/investigation-board/assets/note_white.webp");
width: 200px;
height: 200px;
}
.photo-note {
background-image: url("modules/investigation-board/assets/photoFrame.webp");
width: 250px;
height: 310px;
}
/* EDIT FORM */
/*
.note-config-wrapper {
position: relative;
width: 400px;
min-height: 310px;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
border: none;
}
.note-config-wrapper.sticky-mode {
background-image: url("../assets/note_white.webp");
background-repeat: no-repeat;
background-size: contain;
}
.note-config-wrapper.photo-mode {
background-image: url("../assets/photoFrame.webp");
background-repeat: no-repeat;
background-size: cover;
}
.note-config-wrapper .window-content {
background: none;
border: none;
}
.note-config-wrapper form {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
.note-type-group {
position: absolute;
top: 10px;
left: 10px;
width: 150px;
}
.note-text-group {
position: absolute;
left: 20px;
width: 360px;
}
.note-config-wrapper.sticky-mode .note-text-group {
top: 100px;
}
.note-config-wrapper.photo-mode .note-text-group {
top: 220px;
}
.note-image-group {
position: absolute;
top: 50px;
right: 20px;
width: 200px;
}
.note-save-group {
position: absolute;
bottom: 10px;
right: 10px;
}
.note-config-wrapper input,
.note-config-wrapper textarea,
.note-config-wrapper select {
background: rgba(255, 255, 255, 0.9);
border: 1px solid #ccc;
border-radius: 2px;
padding: 4px;
font-size: 14px;
font-family: Arial, sans-serif;
}
.note-config-wrapper label {
font-size: 12px;
font-weight: bold;
color: #333;
margin-bottom: 3px;
display: inline-block;
}
.file-picker-button {
margin-left: 5px;
background: #eee;
border: 1px solid #ccc;
padding: 2px 4px;
cursor: pointer;
}
.save-button {
background: #3f51b5;
color: #fff;
border: none;
padding: 5px 10px;
cursor: pointer;
font-size: 14px;
border-radius: 2px;
}
.save-button i {
margin-right: 5px;
} */
@@ -0,0 +1,37 @@
<form>
<div class="form-group">
<label for="text">Note Text:</label>
<textarea name="text" rows="4">{{text}}</textarea>
</div>
{{!-- Only show the Image Path field for photo notes --}}
{{#if (eq noteType "photo")}}
<div class="form-group" style="display: block;">
<label for="image">Image Path:</label>
<div class="file-picker">
<input type="text" name="image" value="{{image}}" placeholder="Enter image path" readonly />
<button type="button" class="file-picker-button">
<i class="fas fa-folder-open"></i> Browse
</button>
</div>
</div>
{{else}}
<div class="form-group" style="display: none;"></div>
{{/if}}
{{!-- If boardMode is futuristic and noteType is photo, show the extra Identity Name field --}}
{{#if (eq noteType "photo")}}
{{#if (eq boardMode "futuristic")}}
<div class="form-group" style="display: block;">
<label for="identityName">Identity Name:</label>
<input type="text" name="identityName" value="{{identityName}}" placeholder="Enter identity name" />
</div>
{{/if}}
{{/if}}
<div class="form-group">
<button type="submit" class="save-button">
<i class="fas fa-save"></i> Save
</button>
</div>
</form>