Skip to content

Commit 3ef1a4b

Browse files
committed
only pins
1 parent 080fc4f commit 3ef1a4b

8 files changed

Lines changed: 209 additions & 10 deletions

File tree

scripts/apps/drawing-sheet.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -389,8 +389,8 @@ export class CustomDrawingSheet extends DrawingConfig {
389389
const noteType = this.document.flags[MODULE_ID]?.type;
390390
const updates = {};
391391

392-
// Only save text for non-handout notes
393-
if (noteType !== "handout") {
392+
// Only save text for notes that have text fields
393+
if (noteType !== "handout" && noteType !== "media" && noteType !== "pin") {
394394
updates[`flags.${MODULE_ID}.text`] = data.text || "";
395395
}
396396

@@ -415,8 +415,8 @@ export class CustomDrawingSheet extends DrawingConfig {
415415
updates[`flags.${MODULE_ID}.identityName`] = data.identityName;
416416
}
417417

418-
// Save font and fontSize to note flags (skip for handouts)
419-
if (noteType !== "handout") {
418+
// Save font and fontSize to note flags (skip for handouts, media, and pins)
419+
if (noteType !== "handout" && noteType !== "media" && noteType !== "pin") {
420420
if (data.font !== undefined) {
421421
updates[`flags.${MODULE_ID}.font`] = data.font;
422422
}

scripts/apps/hud.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export class InvestigationBoardHUD extends HandlebarsApplicationMixin(BasePlacea
5353
noteType: noteData?.type || "sticky",
5454
text: noteData?.text || "",
5555
hasConnections: (noteData?.connections?.length || 0) > 0,
56-
canEdit: drawing.document.testUserPermission(game.user, "OWNER")
56+
canEdit: drawing.document.testUserPermission(game.user, "OWNER"),
57+
isPin: noteData?.type === "pin"
5758
};
5859
}
5960

scripts/canvas/connection-manager.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,25 @@ export function updatePins() {
245245
canvas.drawings.placeables.forEach(drawing => {
246246
const noteData = drawing.document.flags[MODULE_ID];
247247
if (noteData && drawing.pinSprite) {
248+
// Special handling for "Only Pin" notes: Keep sprite on drawing, do not move to global container
249+
if (noteData.type === "pin") {
250+
if (drawing.pinSprite.parent !== drawing) {
251+
drawing.addChild(drawing.pinSprite);
252+
}
253+
drawing.pinSprite.eventMode = 'static';
254+
drawing.pinSprite.cursor = 'pointer';
255+
drawing.pinSprite.removeAllListeners();
256+
// Determine width/height for centering (should be small, e.g. 50)
257+
const width = drawing.document.shape.width || 50;
258+
const height = drawing.document.shape.height || 50;
259+
drawing.pinSprite.width = width;
260+
drawing.pinSprite.height = height;
261+
drawing.pinSprite.position.set(0, 0); // Local to drawing
262+
263+
drawing.pinSprite.on('click', (event) => onPinClick(event, drawing));
264+
return; // Skip the rest for this drawing
265+
}
266+
248267
drawing.zIndex = 0;
249268

250269
if (drawing.pinSprite.parent === drawing) {

scripts/canvas/custom-drawing.js

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ export class CustomDrawing extends Drawing {
173173
*/
174174
_onClickLeft2(event) {
175175
const noteData = this.document.flags?.[MODULE_ID];
176+
if (noteData?.type === "pin") return;
176177
if (noteData?.type) {
177178
// Open the detail view previewer
178179
new NotePreviewer(this.document).render(true);
@@ -230,6 +231,102 @@ export class CustomDrawing extends Drawing {
230231
menu.style.left = `${x}px`;
231232
menu.style.zIndex = '10000';
232233

234+
if (noteData.type === "pin") {
235+
const editOption = document.createElement('div');
236+
editOption.innerHTML = '<i class="fas fa-edit"></i> Edit';
237+
editOption.classList.add('ib-context-menu-item');
238+
editOption.onclick = (e) => {
239+
e.stopPropagation();
240+
this.document.sheet.render(true);
241+
menu.remove();
242+
};
243+
244+
if (noteData.linkedObject) {
245+
const linkMatch = noteData.linkedObject.match(/\[([^\]]+)\](?:\{([^\}]+)\})?/);
246+
if (linkMatch) {
247+
const uuid = linkMatch[1];
248+
const name = linkMatch[2] || "Linked Object";
249+
const linkOption = document.createElement('div');
250+
linkOption.innerHTML = `<i class="fas fa-link"></i> Open: ${name}`;
251+
linkOption.classList.add('ib-context-menu-item');
252+
linkOption.onclick = async (e) => {
253+
e.stopPropagation();
254+
menu.remove();
255+
try {
256+
const doc = await fromUuid(uuid);
257+
if (doc) {
258+
if (doc.testUserPermission(game.user, "LIMITED")) {
259+
doc.sheet.render(true);
260+
} else {
261+
ui.notifications.warn(`You do not have permission to view ${doc.name}.`);
262+
}
263+
} else {
264+
ui.notifications.warn(`Could not find linked document.`);
265+
}
266+
} catch (err) {
267+
console.error("Investigation Board: Error opening linked document from menu", err);
268+
}
269+
};
270+
menu.appendChild(linkOption);
271+
}
272+
}
273+
274+
const removeConnectionsOption = document.createElement('div');
275+
removeConnectionsOption.innerHTML = '<i class="fas fa-cut"></i> Remove Connections';
276+
removeConnectionsOption.classList.add('ib-context-menu-item');
277+
removeConnectionsOption.onclick = async (e) => {
278+
e.stopPropagation();
279+
menu.remove();
280+
const confirm = await foundry.applications.api.DialogV2.confirm({
281+
window: { title: "Remove All Connections" },
282+
content: `<p>Are you sure you want to remove ALL yarn connections connected to this note?</p>`,
283+
rejectClose: false,
284+
modal: true
285+
});
286+
if (confirm) {
287+
const noteId = this.document.id;
288+
await collaborativeUpdate(noteId, { [`flags.${MODULE_ID}.connections`]: [] });
289+
const otherNotesWithConnections = canvas.drawings.placeables.filter(d => {
290+
if (d.document.id === noteId) return false;
291+
const conns = d.document.flags[MODULE_ID]?.connections;
292+
return conns && conns.some(c => c.targetId === noteId);
293+
});
294+
for (let otherNote of otherNotesWithConnections) {
295+
const currentConns = otherNote.document.flags[MODULE_ID].connections;
296+
const updatedConns = currentConns.filter(c => c.targetId !== noteId);
297+
await collaborativeUpdate(otherNote.document.id, { [`flags.${MODULE_ID}.connections`]: updatedConns });
298+
}
299+
drawAllConnectionLines();
300+
ui.notifications.info("All related connections removed.");
301+
}
302+
};
303+
304+
const deleteOption = document.createElement('div');
305+
deleteOption.innerHTML = '<i class="fas fa-trash"></i> Delete';
306+
deleteOption.classList.add('ib-context-menu-item');
307+
deleteOption.onclick = async (e) => {
308+
e.stopPropagation();
309+
menu.remove();
310+
const confirm = await foundry.applications.api.DialogV2.confirm({
311+
window: { title: "Delete Pin" },
312+
content: `<p>Are you sure you want to delete this pin?</p>`,
313+
rejectClose: false,
314+
modal: true
315+
});
316+
if (confirm) {
317+
await collaborativeDelete(this.document.id);
318+
}
319+
};
320+
321+
menu.appendChild(editOption);
322+
menu.appendChild(removeConnectionsOption);
323+
menu.appendChild(deleteOption);
324+
document.body.appendChild(menu);
325+
const closeMenu = (e) => { if (!menu.contains(e.target)) { menu.remove(); document.removeEventListener('mousedown', closeMenu); window.removeEventListener('wheel', closeMenu); } };
326+
setTimeout(() => { document.addEventListener('mousedown', closeMenu); window.addEventListener('wheel', closeMenu); }, 100);
327+
return;
328+
}
329+
233330
const editOption = document.createElement('div');
234331
editOption.innerHTML = '<i class="fas fa-edit"></i> Edit';
235332
editOption.classList.add('ib-context-menu-item');
@@ -585,6 +682,65 @@ export class CustomDrawing extends Drawing {
585682
return; // Early exit for media notes
586683
}
587684

685+
// PIN ONLY LAYOUT
686+
if (noteData.type === "pin") {
687+
const width = this.document.shape.width || 50;
688+
const height = this.document.shape.height || 50;
689+
690+
// No background for pin-only
691+
if (this.bgSprite) {
692+
this.removeChild(this.bgSprite);
693+
this.bgSprite.destroy();
694+
this.bgSprite = null;
695+
}
696+
if (this.bgShadow) {
697+
this.removeChild(this.bgShadow);
698+
this.bgShadow.destroy();
699+
this.bgShadow = null;
700+
}
701+
if (this.photoImageSprite) {
702+
this.removeChild(this.photoImageSprite);
703+
this.photoImageSprite.destroy();
704+
this.photoImageSprite = null;
705+
}
706+
707+
// --- Pin Sprite ---
708+
const pinSetting = game.settings.get(MODULE_ID, "pinColor");
709+
if (!this.pinSprite || !this.pinSprite.parent) {
710+
if (this.pinSprite) this.pinSprite.destroy();
711+
this.pinSprite = new PIXI.Sprite();
712+
this.addChild(this.pinSprite);
713+
}
714+
715+
let pinColor = noteData.pinColor;
716+
if (!pinColor) {
717+
pinColor = (pinSetting === "random")
718+
? PIN_COLORS[Math.floor(Math.random() * PIN_COLORS.length)]
719+
: (pinSetting === "none" ? "redPin.webp" : `${pinSetting}Pin.webp`);
720+
await collaborativeUpdate(this.document.id, { [`flags.${MODULE_ID}.pinColor`]: pinColor });
721+
}
722+
723+
const pinImage = `modules/investigation-board/assets/${pinColor}`;
724+
try {
725+
const texture = await PIXI.Assets.load(pinImage);
726+
if (texture && this.pinSprite && this.pinSprite.parent) {
727+
this.pinSprite.texture = texture;
728+
this.pinSprite.width = width;
729+
this.pinSprite.height = height;
730+
this.pinSprite.position.set(0, 0);
731+
}
732+
} catch (err) {
733+
console.error(`Failed to load pin texture: ${pinImage}`, err);
734+
}
735+
736+
// Hide text
737+
if (this.noteText) this.noteText.visible = false;
738+
if (this.identityNameText) this.identityNameText.visible = false;
739+
if (this.futuristicText) this.futuristicText.visible = false;
740+
741+
return;
742+
}
743+
588744
// HANDOUT NOTE LAYOUT (Image-only, transparent background)
589745
if (isHandout) {
590746
const drawingWidth = this.document.shape.width || 400;
@@ -1139,6 +1295,16 @@ export class CustomDrawing extends Drawing {
11391295
y: this.document.y + 23
11401296
};
11411297
}
1298+
1299+
// Pin-only notes
1300+
if (noteData.type === "pin") {
1301+
const width = this.document.shape.width || 50;
1302+
const height = this.document.shape.height || 50;
1303+
return {
1304+
x: this.document.x + width / 2,
1305+
y: this.document.y + height / 2
1306+
};
1307+
}
11421308

11431309
const isPhoto = noteData.type === "photo";
11441310
const isIndex = noteData.type === "index";

scripts/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@ Hooks.on("getSceneControlButtons", (controls) => {
190190
onChange: () => createNote("media"),
191191
button: true
192192
};
193+
194+
controls.drawings.tools.createPinOnly = {
195+
name: "createPinOnly",
196+
title: "Create Pin Only",
197+
icon: "fas fa-thumbtack",
198+
onChange: () => createNote("pin"),
199+
button: true
200+
};
193201
}
194202
});
195203

scripts/utils/creation-utils.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,27 @@ export async function createNote(noteType) {
4040
const handoutW = game.settings.get(MODULE_ID, "handoutNoteWidth") || 400;
4141
const handoutH = game.settings.get(MODULE_ID, "handoutNoteHeight") || 400;
4242
const mediaW = 400;
43+
const pinW = 50;
4344

4445
const width = noteType === "photo" ? photoW
4546
: noteType === "index" ? indexW
4647
: noteType === "handout" ? handoutW
4748
: noteType === "media" ? mediaW
49+
: noteType === "pin" ? pinW
4850
: stickyW;
4951
const height = noteType === "photo" ? Math.round(photoW / (225 / 290))
5052
: noteType === "index" ? Math.round(indexW / (600 / 400))
5153
: noteType === "handout" ? handoutH
5254
: noteType === "media" ? Math.round(mediaW * 0.74)
55+
: noteType === "pin" ? pinW
5356
: stickyW;
5457

5558
const viewCenter = canvas.stage.pivot;
5659
const x = viewCenter.x - width / 2;
5760
const y = viewCenter.y - height / 2;
5861

5962
// Get default text from settings (fallback if missing)
60-
const defaultText = noteType === "handout"
63+
const defaultText = (noteType === "handout" || noteType === "pin")
6164
? ""
6265
: (game.settings.get(MODULE_ID, `${noteType}NoteDefaultText`) || "Notes");
6366

@@ -90,8 +93,8 @@ export async function createNote(noteType) {
9093
x,
9194
y,
9295
shape: { width, height },
93-
fillColor: noteType === "handout" || noteType === "media" ? "#000000" : "#ffffff",
94-
fillAlpha: noteType === "handout" || noteType === "media" ? 0 : 1,
96+
fillColor: noteType === "handout" || noteType === "media" || noteType === "pin" ? "#000000" : "#ffffff",
97+
fillAlpha: noteType === "handout" || noteType === "media" || noteType === "pin" ? 0 : 1,
9598
strokeColor: "#000000",
9699
strokeWidth: 0,
97100
strokeAlpha: 0,
@@ -108,7 +111,7 @@ export async function createNote(noteType) {
108111
}
109112
},
110113
ownership: { default: 3 },
111-
});
114+
}, { skipAutoOpen: noteType === "pin" });
112115

113116
// If in Investigation Board mode, ensure the new drawing is interactive
114117
if (InvestigationBoardState.isActive && created && created[0]) {

templates/drawing-sheet.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<form class="ib-note-config-form">
2-
{{#unless (or (eq noteType "handout") (eq noteType "media"))}}
2+
{{#unless (or (eq noteType "handout") (eq noteType "media") (eq noteType "pin"))}}
33
<div class="form-group">
44
<label for="text">Note Text:</label>
55
<textarea name="text" rows="4" placeholder="Enter note text...">{{text}}</textarea>

templates/hud.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
{{#if canEdit}}
77
<div class="hud-body">
8+
{{#unless isPin}}
89
<textarea name="quickText" rows="3" placeholder="Quick edit...">{{text}}</textarea>
10+
{{/unless}}
911
</div>
1012

1113
<div class="hud-controls">

0 commit comments

Comments
 (0)