Skip to content

Commit 9f086f5

Browse files
committed
Sprint A-1: Foundry Sync Configuration Panel
Add Config tab to the Foundry sync dashboard with comprehensive sync management controls for DMs: - Connection setup: inline API URL, key, campaign ID fields with "Test Connection" button and live feedback - Sync scope: per-type direction controls (Bidirectional/Pull/Push/Off) for journals, maps, calendar, characters, and shops - Permission mapping: visibility-to-ownership sync toggle, DM-only hidden toggle, default ownership level selector - Sync behavior: conflict resolution strategy (Chronicle/Foundry/Newest wins), auto-sync toggle - Exclusion rules: tag-based exclusions, name pattern exclusions, counts of type/entity exclusions from Entities tab New settings registered: syncDirections, syncPermissions, defaultOwnership, dmOnlyHidden, conflictResolution, autoSync, excludedTags, excludedNamePattern. SyncManager gains direction-aware helpers: getSyncDirection(), canPull(), canPush(), isAutoSync(), isExcludedByRules(). Modules can query these to respect the DM's sync configuration. https://claude.ai/code/session_01WJEjfBqjZaGatHiXXXDupo
1 parent 1939d07 commit 9f086f5

5 files changed

Lines changed: 850 additions & 2 deletions

File tree

foundry-module/scripts/settings.mjs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,76 @@ export function registerSettings() {
117117
type: String,
118118
default: '{"excludedTypes":[],"excludedEntities":[]}',
119119
});
120+
121+
// -----------------------------------------------------------------------
122+
// Sync Configuration settings (managed via Config tab in dashboard)
123+
// -----------------------------------------------------------------------
124+
125+
// Per-type sync direction: JSON map of sync type → direction.
126+
// Directions: "both" (bidirectional), "pull" (Chronicle→Foundry), "push" (Foundry→Chronicle), "off".
127+
game.settings.register(MODULE_ID, 'syncDirections', {
128+
scope: 'world',
129+
config: false,
130+
type: String,
131+
default: '{"journals":"both","maps":"both","calendar":"both","characters":"both","shops":"both"}',
132+
});
133+
134+
// Permission mapping: sync Chronicle visibility to Foundry ownership levels.
135+
game.settings.register(MODULE_ID, 'syncPermissions', {
136+
scope: 'world',
137+
config: false,
138+
type: Boolean,
139+
default: true,
140+
});
141+
142+
// Default Foundry ownership level for newly synced documents.
143+
// Values: 0 (NONE), 1 (LIMITED), 2 (OBSERVER), 3 (OWNER).
144+
game.settings.register(MODULE_ID, 'defaultOwnership', {
145+
scope: 'world',
146+
config: false,
147+
type: Number,
148+
default: 0,
149+
});
150+
151+
// Whether DM-only entities should be hidden in Foundry (ownership NONE).
152+
game.settings.register(MODULE_ID, 'dmOnlyHidden', {
153+
scope: 'world',
154+
config: false,
155+
type: Boolean,
156+
default: true,
157+
});
158+
159+
// Conflict resolution strategy: "chronicle", "foundry", or "newest".
160+
game.settings.register(MODULE_ID, 'conflictResolution', {
161+
scope: 'world',
162+
config: false,
163+
type: String,
164+
default: 'chronicle',
165+
});
166+
167+
// Auto-sync on change (true) vs manual-only (false).
168+
game.settings.register(MODULE_ID, 'autoSync', {
169+
scope: 'world',
170+
config: false,
171+
type: Boolean,
172+
default: true,
173+
});
174+
175+
// Tag-based exclusions: JSON array of tag names to exclude from sync.
176+
game.settings.register(MODULE_ID, 'excludedTags', {
177+
scope: 'world',
178+
config: false,
179+
type: String,
180+
default: '[]',
181+
});
182+
183+
// Name pattern exclusion: entities matching this substring are excluded.
184+
game.settings.register(MODULE_ID, 'excludedNamePattern', {
185+
scope: 'world',
186+
config: false,
187+
type: String,
188+
default: '',
189+
});
120190
}
121191

122192
/**
@@ -168,6 +238,46 @@ export function isConfigured() {
168238
return !!(url && key && campaign);
169239
}
170240

241+
/**
242+
* Get sync directions config (per sync type).
243+
* @returns {{ journals: string, maps: string, calendar: string, characters: string, shops: string }}
244+
*/
245+
export function getSyncDirections() {
246+
try {
247+
return JSON.parse(getSetting('syncDirections'));
248+
} catch {
249+
return { journals: 'both', maps: 'both', calendar: 'both', characters: 'both', shops: 'both' };
250+
}
251+
}
252+
253+
/**
254+
* Save sync directions config.
255+
* @param {object} directions
256+
*/
257+
export async function setSyncDirections(directions) {
258+
await setSetting('syncDirections', JSON.stringify(directions));
259+
}
260+
261+
/**
262+
* Get excluded tags list.
263+
* @returns {string[]}
264+
*/
265+
export function getExcludedTags() {
266+
try {
267+
return JSON.parse(getSetting('excludedTags'));
268+
} catch {
269+
return [];
270+
}
271+
}
272+
273+
/**
274+
* Save excluded tags list.
275+
* @param {string[]} tags
276+
*/
277+
export async function setExcludedTags(tags) {
278+
await setSetting('excludedTags', JSON.stringify(tags));
279+
}
280+
171281
/**
172282
* Mask the API key input in the module settings dialog.
173283
* Foundry doesn't have a native password input type for settings,

0 commit comments

Comments
 (0)