diff --git a/core/modules/info/dimensions.js b/core/modules/info/dimensions.js new file mode 100644 index 0000000000..48458dd0f0 --- /dev/null +++ b/core/modules/info/dimensions.js @@ -0,0 +1,86 @@ +/*\ +title: $:/core/modules/info/windowdimensions.js +type: application/javascript +module-type: info +\*/ + +exports.getInfoTiddlerFields = function(updateInfoTiddlersCallback) { + if(!$tw.browser) { + return []; + } + + class WindowDimensionsTracker { + constructor(updateCallback) { + this.updateCallback = updateCallback; + this.resizeHandlers = new Map(); + this.dimensionsInfo = [ + ["outer/width", win => win.outerWidth], + ["outer/height", win => win.outerHeight], + ["inner/width", win => win.innerWidth], + ["inner/height", win => win.innerHeight], + ["client/width", win => win.document.documentElement.clientWidth], + ["client/height", win => win.document.documentElement.clientHeight] + ]; + } + + buildTiddlers(win,windowId) { + const prefix = `$:/info/browser/window/${windowId}/`; + return this.dimensionsInfo.map(([suffix, getter]) => ({ + title: prefix + suffix, + text: String(getter(win)) + })); + } + + clearTiddlers(windowId) { + const prefix = `$:/info/browser/window/${windowId}/`, + deletions = this.dimensionsInfo.map(([suffix]) => prefix + suffix); + this.updateCallback([], deletions); + } + + getUpdateHandler(win,windowId) { + let scheduled = false; + return () => { + if(!scheduled) { + scheduled = true; + requestAnimationFrame(() => { + this.updateCallback(this.buildTiddlers(win,windowId), []); + scheduled = false; + }); + } + }; + } + + trackWindow(win,windowId) { + const handler = this.getUpdateHandler(win, windowId); + handler(); // initial update + win.addEventListener("resize",handler,{passive:true}); + this.resizeHandlers.set(windowId,{win, handler}); + } + + untrackWindow(windowId) { + const entry = this.resizeHandlers.get(windowId); + if(entry) { + entry.win.removeEventListener("resize", entry.handler); + this.resizeHandlers.delete(windowId); + } + this.clearTiddlers(windowId); + } + } + + const tracker = new WindowDimensionsTracker(updateInfoTiddlersCallback); + + // Track main window + tracker.trackWindow(window,"system/main"); + + // Hook into event bus for user windows + if($tw.eventBus) { + $tw.eventBus.on("window:opened", ({window: win, windowID}) => { + tracker.trackWindow(win, "user/" + windowID); + }); + $tw.eventBus.on("window:closed", ({windowID}) => { + tracker.untrackWindow("user/" + windowID); + }); + } + + return []; +}; diff --git a/core/modules/startup/eventbus.js b/core/modules/startup/eventbus.js new file mode 100644 index 0000000000..0ad69fb27f --- /dev/null +++ b/core/modules/startup/eventbus.js @@ -0,0 +1,46 @@ +/*\ +title: $:/core/modules/startup/eventbus.js +type: application/javascript +module-type: startup + +Event bus for cross module communication +\*/ + +exports.name = "eventbus"; +exports.platforms = ["browser"]; +exports.before = ["windows"]; +exports.synchronous = true; + +$tw.eventBus = { + listenersMap: new Map(), + + on(event,handler) { + if(!this.listenersMap.has(event)) { + this.listenersMap.set(event,new Set()); + } + const listeners = this.listenersMap.get(event); + listeners.add(handler); + }, + + off(event,handler) { + const listeners = this.listenersMap.get(event); + if(listeners) { + listeners.delete(handler); + } + }, + + once(event,handler) { + const wrapper = (...args) => { + handler(...args); + this.off(event, wrapper); + }; + this.on(event, wrapper); + }, + + emit(event,data) { + const listeners = this.listenersMap.get(event); + if(listeners) { + listeners.forEach(fn => fn(data)); + } + } +}; diff --git a/core/modules/startup/info.js b/core/modules/startup/info.js index a5767ed341..dc65557c7d 100644 --- a/core/modules/startup/info.js +++ b/core/modules/startup/info.js @@ -19,11 +19,17 @@ var TITLE_INFO_PLUGIN = "$:/temp/info-plugin"; exports.startup = function() { // Function to bake the info plugin with new tiddlers - var updateInfoPlugin = function(tiddlerFieldsArray) { + // additions: array of tiddler field objects + // removals: array of titles to remove + var updateInfoPlugin = function(additions = [], removals = []) { // Get the existing tiddlers var json = $tw.wiki.getTiddlerData(TITLE_INFO_PLUGIN,{tiddlers: {}}); - // Add the new ones - $tw.utils.each(tiddlerFieldsArray,function(fields) { + $tw.utils.each(removals,function(title) { + if(json.tiddlers[title]) { + delete json.tiddlers[title]; + } + }); + $tw.utils.each(additions,function(fields) { if(fields && fields.title) { json.tiddlers[fields.title] = fields; } @@ -47,7 +53,7 @@ exports.startup = function() { } }); updateInfoPlugin(tiddlerFieldsArray); - var changes = $tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]); + $tw.wiki.readPluginInfo([TITLE_INFO_PLUGIN]); $tw.wiki.registerPluginTiddlers("info",[TITLE_INFO_PLUGIN]); $tw.wiki.unpackPluginTiddlers(); }; diff --git a/core/modules/startup/windows.js b/core/modules/startup/windows.js index 8506a9866a..39a2f59d62 100644 --- a/core/modules/startup/windows.js +++ b/core/modules/startup/windows.js @@ -56,9 +56,11 @@ exports.startup = function() { srcDocument.write(""); srcDocument.close(); srcDocument.title = windowTitle; + $tw.eventBus.emit("window:opened",{windowID, window: srcWindow}); srcWindow.addEventListener("beforeunload",function(event) { delete $tw.windows[windowID]; $tw.wiki.removeEventListener("change",refreshHandler); + $tw.eventBus.emit("window:closed",{windowID}); },false); // Set up the styles var styleWidgetNode = $tw.wiki.makeTranscludeWidget("$:/core/ui/PageStylesheet",{ diff --git a/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid b/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid index 040dd7bd23..e22ff238a1 100644 --- a/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid +++ b/editions/tw5.com/tiddlers/mechanisms/InfoMechanism.tid @@ -1,5 +1,5 @@ created: 20140720164948099 -modified: 20201124185829706 +modified: 20250819181815032 tags: Mechanisms title: InfoMechanism type: text/vnd.tiddlywiki @@ -19,6 +19,7 @@ System tiddlers in the namespace `$:/info/` are used to expose information about |[[$:/info/browser/language]] |<<.from-version "5.1.20">> Language as reported by browser (note that some browsers report two character codes such as `en` while others report full codes such as `en-GB`) | |[[$:/info/browser/screen/width]] |Screen width in pixels | |[[$:/info/browser/screen/height]] |Screen height in pixels | +|`$:/info/browser/window/*` |<<.from-version "5.4.0">> Tiddlers reporting window dimensions, updated when the windows are resized | |[[$:/info/node]] |Running under [[Node.js]]? ("yes" or "no") | |[[$:/info/url/full]] |<<.from-version "5.1.14">> Full URL of wiki (eg, ''<>'') | |[[$:/info/url/host]] |<<.from-version "5.1.14">> Host portion of URL of wiki (eg, ''<>'') | @@ -29,3 +30,27 @@ System tiddlers in the namespace `$:/info/` are used to expose information about |[[$:/info/url/protocol]] |<<.from-version "5.1.14">> Protocol portion of URL of wiki (eg, ''<>'') | |[[$:/info/url/search]] |<<.from-version "5.1.14">> Search portion of URL of wiki (eg, ''<>'') | |[[$:/info/darkmode]] |<<.from-version "5.1.23">> Is dark mode enabled? ("yes" or "no") | + +! Main Window Dimension Tiddlers + +<<.from-version "5.4.0">> These tiddlers reports the dimensions of the main ~TiddlyWiki window and are updated automatically whenever the main window is resized. + +|!Title |!Description | +|[[$:/info/browser/window/system/main/outer/width]] |Full browser window including chrome, tabs, toolbars | +|[[$:/info/browser/window/system/main/outer/height]] |Full browser window including chrome, tabs, toolbars | +|[[$:/info/browser/window/system/main/inner/width]] |Viewport width including scrollbars | +|[[$:/info/browser/window/system/main/inner/height]] |Viewport height including scrollbars | +|[[$:/info/browser/window/system/main/client/width]] |Content width excluding scrollbars | +|[[$:/info/browser/window/system/main/client/height]] |Content height excluding scrollbars | + +! User-Created Window Dimension Tiddlers + +<<.from-version "5.4.0">> These tiddler reports the dimensions of additional windows opened via [[tm-open-window|WidgetMessage: tm-open-window]]. The windowID used when opening the window is used to identify the corresponding info tiddlers. These tiddlers are updated automatically whenever the main window is resized. + +|!Title |!Description | +|`$:/info/browser/window/user//outer/width` | Full browser window including chrome, tabs, toolbars | +|`$:/info/browser/window/user//outer/height` | Full browser window including chrome, tabs, toolbars | +|`$:/info/browser/window/user//inner/width` |Viewport width including scrollbars | +|`$:/info/browser/window/user//inner/height` |Viewport height including scrollbars | +|`$:/info/browser/window/user//client/width` |Content width excluding scrollbars | +|`$:/info/browser/window/user//client/height` |Content height excluding scrollbars |