Skip to content

Commit a2ddfd7

Browse files
committed
Refactor Core.js to utilize MessageBus for event handling, enhancing modularity and readability. Removed legacy message handling code and added new bindings for various events including 'tidy', 'preferences', 'displayImpression', 'activate-license', and 'remove-license'. This update improves the overall structure and maintainability of the code.
1 parent 955c283 commit a2ddfd7

3 files changed

Lines changed: 239 additions & 82 deletions

File tree

src/Core.js

Lines changed: 87 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import Tracking from 'src/utils/Tracking'
22
import Storage from 'src/utils/Storage'
3+
34
import FigPen from 'src/utils/FigPen'
5+
import MessageBus from 'src/utils/MessageBus'
6+
47
import { shouldShowCountdown, getCountdownSeconds, setCachedLicenseStatus } from 'src/payments/gate'
58
import {
69
getNodesGroupedbyPosition,
@@ -80,12 +83,11 @@ function ensureDirectCommandGate(commandName, executeCommand, preferences, UUID,
8083
})
8184
}, 50)
8285

83-
// Handle countdown completion
84-
FP.onUIMessage((msg) => {
85-
if (msg.type === 'direct-countdown-complete') {
86-
executeCommand()
87-
FP.closePlugin()
88-
}
86+
87+
// Bind direct countdown completion handler
88+
MessageBus.bind('direct-countdown-complete', (msg) => {
89+
executeCommand()
90+
FP.closePlugin()
8991
})
9092
} else {
9193
// Execute immediately if licensed
@@ -184,7 +186,86 @@ Storage.getMultiple([
184186
// Make sure initial selection is sent to the UI
185187
FP.notifyUI({ type: 'selection', selection: FP.currentSelection() })
186188
})
189+
190+
// Set up selection change listener using FigPen
191+
FP.onSelectionChange((selection) => {
192+
FP.notifyUI({ type: 'selection', selection: selection })
193+
})
194+
195+
// Bind all message handlers using MessageBus
196+
MessageBus.bind('tidy', (msg) => {
197+
var RENAMING_ENABLED = msg.options.renaming
198+
var REORDER_ENABLED = msg.options.reorder
199+
var TIDY_ENABLED = msg.options.tidy
200+
var PAGER_ENABLED = msg.options.pager
201+
202+
if (TIDY_ENABLED) cmdTidy(preferences.spacing.x, preferences.spacing.y, preferences.wrap_instances, preferences.layout_paradigm || 'rows')
203+
if (RENAMING_ENABLED) cmdRename(preferences.rename_strategy, preferences.start_name, preferences.layout_paradigm || 'rows')
204+
if (REORDER_ENABLED) cmdReorder(preferences.layout_paradigm || 'rows')
205+
if (PAGER_ENABLED) cmdPager(preferences.pager_variable, preferences.layout_paradigm || 'rows')
206+
FP.showNotification('Super Tidy')
207+
setTimeout(() => FP.closePlugin(), 100)
208+
})
209+
210+
MessageBus.bind('preferences', (msg) => {
211+
preferences = msg.preferences
212+
Storage.set(Storage.getKey('PREFERENCES'), preferences).then((success) => {
213+
if (success) {
214+
FP.showNotification('Preferences saved')
215+
} else {
216+
FP.showNotification('Failed to save preferences. Please try again.')
217+
}
218+
})
219+
})
220+
221+
MessageBus.bind('displayImpression', (msg) => {
222+
FP.resizeUI(320, 540+124)
223+
Storage.setMultiple({
224+
[Storage.getKey('AD_LAST_SHOWN_DATE')]: Date.now(),
225+
[Storage.getKey('AD_LAST_SHOWN_IMPRESSION')]: parseInt(AD_LAST_SHOWN_IMPRESSION)+1
226+
})
227+
})
228+
229+
MessageBus.bind('resetImpression', (msg) => {
230+
Storage.set(Storage.getKey('AD_LAST_SHOWN_IMPRESSION'), 0)
231+
})
232+
233+
MessageBus.bind('activate-license', (msg) => {
234+
// Store license data from UI
235+
const licenseData = {
236+
licensed: true,
237+
productId: msg.productId || 'gumroad',
238+
licenseKeyHash: msg.licenseKey ? hashLicenseKey(msg.licenseKey) : null,
239+
licenseKey: msg.licenseKey, // Store actual key for display
240+
deviceId: UUID,
241+
activatedAt: Date.now(),
242+
purchase: msg.purchase || {},
243+
uses: msg.uses || 1 // Include usage count from activation
244+
}
187245

246+
Storage.set(Storage.getKey('LICENSE_V1'), licenseData)
247+
.then((success) => {
248+
if (success) {
249+
setCachedLicenseStatus(licenseData) // Update cache
250+
FP.showNotification('You now have Super Tidy Pro')
251+
} else {
252+
FP.showNotification('Failed to save license. Please try again.')
253+
}
254+
})
255+
})
256+
257+
MessageBus.bind('remove-license', (msg) => {
258+
// Remove stored license
259+
Storage.remove(Storage.getKey('LICENSE_V1'))
260+
.then((success) => {
261+
if (success) {
262+
setCachedLicenseStatus(null) // Update cache
263+
FP.showNotification('License unlinked from this device')
264+
} else {
265+
FP.showNotification('Failed to unlink license. Please try again.')
266+
}
267+
})
268+
})
188269
// Command triggered by user
189270
if (cmd == 'rename') {
190271
// RUNS WITH COUNTDOWN GATE
@@ -237,81 +318,5 @@ Storage.getMultiple([
237318
AD_LAST_SHOWN_IMPRESSION: AD_LAST_SHOWN_IMPRESSION
238319
})
239320
FP.notifyUI({ type: 'selection', selection: FP.currentSelection() })
240-
241-
FP.onSelectionChange((selection) => {
242-
FP.notifyUI({ type: 'selection', selection: selection })
243-
})
244-
245-
FP.onUIMessage((msg) => {
246-
if (msg.type === 'tidy') {
247-
var RENAMING_ENABLED = msg.options.renaming
248-
var REORDER_ENABLED = msg.options.reorder
249-
var TIDY_ENABLED = msg.options.tidy
250-
var PAGER_ENABLED = msg.options.pager
251-
252-
if (TIDY_ENABLED) cmdTidy(preferences.spacing.x, preferences.spacing.y, preferences.wrap_instances, preferences.layout_paradigm || 'rows')
253-
if (RENAMING_ENABLED) cmdRename(preferences.rename_strategy, preferences.start_name, preferences.layout_paradigm || 'rows')
254-
if (REORDER_ENABLED) cmdReorder(preferences.layout_paradigm || 'rows')
255-
if (PAGER_ENABLED) cmdPager(preferences.pager_variable, preferences.layout_paradigm || 'rows')
256-
FP.showNotification('Super Tidy')
257-
setTimeout(() => FP.closePlugin(), 100)
258-
} else
259-
if (msg.type === 'preferences') {
260-
preferences = msg.preferences
261-
Storage.set(Storage.getKey('PREFERENCES'), preferences).then((success) => {
262-
if (success) {
263-
FP.showNotification('Preferences saved')
264-
} else {
265-
FP.showNotification('Failed to save preferences. Please try again.')
266-
}
267-
})
268-
} else
269-
if (msg.type === 'displayImpression') {
270-
FP.resizeUI(320, 540+124)
271-
Storage.setMultiple({
272-
[Storage.getKey('AD_LAST_SHOWN_DATE')]: Date.now(),
273-
[Storage.getKey('AD_LAST_SHOWN_IMPRESSION')]: parseInt(AD_LAST_SHOWN_IMPRESSION)+1
274-
})
275-
}
276-
277-
if (msg.type === 'resetImpression') {
278-
Storage.set(Storage.getKey('AD_LAST_SHOWN_IMPRESSION'), 0)
279-
} else
280-
if (msg.type === 'activate-license') {
281-
// Store license data from UI
282-
const licenseData = {
283-
licensed: true,
284-
productId: msg.productId || 'gumroad',
285-
licenseKeyHash: msg.licenseKey ? hashLicenseKey(msg.licenseKey) : null,
286-
licenseKey: msg.licenseKey, // Store actual key for display
287-
deviceId: UUID,
288-
activatedAt: Date.now(),
289-
purchase: msg.purchase || {},
290-
uses: msg.uses || 1 // Include usage count from activation
291-
}
292-
293-
Storage.set(Storage.getKey('LICENSE_V1'), licenseData)
294-
.then((success) => {
295-
if (success) {
296-
setCachedLicenseStatus(licenseData) // Update cache
297-
FP.showNotification('You now have Super Tidy Pro')
298-
} else {
299-
FP.showNotification('Failed to save license. Please try again.')
300-
}
301-
})
302-
} else
303-
if (msg.type === 'remove-license') {
304-
// Remove stored license
305-
Storage.remove(Storage.getKey('LICENSE_V1'))
306-
.then((success) => {
307-
if (success) {
308-
setCachedLicenseStatus(null) // Update cache
309-
FP.showNotification('License unlinked from this device')
310-
} else {
311-
FP.showNotification('Failed to unlink license. Please try again.')
312-
}
313-
})
314-
}
315-
})
316321
}
317322
})

src/utils/FigPen.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ export default class FigPen {
5050
}
5151
}
5252

53+
clearUIListeners() {
54+
if (this.designTool === FIGMA) {
55+
figma.ui.onmessage = null
56+
} else if (this.designTool === PENPOT) {
57+
penpot.ui.onMessage = null
58+
penpot.ui.onMessage(() => { });
59+
}
60+
}
61+
5362
onSelectionChange(callback) {
5463
if (this.designTool === FIGMA) {
5564
figma.on('selectionchange', () => { callback(this.currentSelection()) })

src/utils/MessageBus.js

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* MessageBus utility for managing multiple figma.ui.onmessage listeners
3+
* Prevents listener overwriting by maintaining a registry of callbacks
4+
*/
5+
6+
import FigPen from 'src/utils/FigPen'
7+
import CONFIG from 'src/Config'
8+
9+
let singleton = null
10+
11+
class MessageBus {
12+
constructor() {
13+
this.listeners = new Map()
14+
this.isAttached = false
15+
this.FP = new FigPen(CONFIG)
16+
17+
if (!singleton) singleton = this
18+
return singleton
19+
}
20+
21+
/**
22+
* Binds a callback to a specific message name
23+
* If a listener already exists for this message name, it will be replaced
24+
* @param {string} messageName - The message name to listen for (e.g., 'activate-license')
25+
* @param {Function} callback - The callback function to execute
26+
*/
27+
bind(messageName, callback) {
28+
if (!messageName || typeof callback !== 'function') {
29+
throw new Error('MessageBus.bind() requires messageName and callback function')
30+
}
31+
32+
// Set the listener (this will overwrite any existing listener for this message name)
33+
this.listeners.set(messageName, callback)
34+
35+
// Attach the main message handler if not already attached
36+
this.attachMainHandler()
37+
38+
console.log(`[MessageBus] Bound listener for '${messageName}'`)
39+
}
40+
41+
/**
42+
* Unbinds the listener for a specific message name
43+
* @param {string} messageName - The message name to unbind
44+
* @returns {boolean} True if listener was found and removed
45+
*/
46+
unbind(messageName) {
47+
if (!this.listeners.has(messageName)) {
48+
console.warn(`[MessageBus] No listener found for: ${messageName}`)
49+
return false
50+
}
51+
52+
this.listeners.delete(messageName)
53+
console.log(`[MessageBus] Unbound listener for '${messageName}'`)
54+
55+
// Detach main handler if no listeners remain
56+
if (this.listeners.size === 0) {
57+
this.detachMainHandler()
58+
}
59+
60+
return true
61+
}
62+
63+
/**
64+
* Unbinds all listeners and detaches the main handler
65+
* @returns {number} Total number of listeners removed
66+
*/
67+
unbindAll() {
68+
const totalCount = this.listeners.size
69+
70+
this.listeners.clear()
71+
this.detachMainHandler()
72+
73+
console.log(`[MessageBus] Unbound all ${totalCount} listeners`)
74+
return totalCount
75+
}
76+
77+
/**
78+
* Gets information about current listeners (for debugging)
79+
* @returns {Array} Array of message names with listeners
80+
*/
81+
getListenerInfo() {
82+
return Array.from(this.listeners.keys())
83+
}
84+
85+
/**
86+
* Attaches the main figma.ui.onmessage handler
87+
* @private
88+
*/
89+
attachMainHandler() {
90+
if (this.isAttached) return
91+
92+
this.FP.onUIMessage(msg => {
93+
this.handleMessage(msg)
94+
})
95+
96+
this.isAttached = true
97+
console.log('[MessageBus] Main message handler attached')
98+
}
99+
100+
/**
101+
* Detaches the main figma.ui.onmessage handler
102+
* @private
103+
*/
104+
detachMainHandler() {
105+
if (!this.isAttached) return
106+
107+
this.FP.clearUIListeners()
108+
this.isAttached = false
109+
console.log('[MessageBus] Main message handler detached')
110+
}
111+
112+
/**
113+
* Handles incoming messages and distributes to registered listeners
114+
* @param {Object} msg - The message object from figma.ui
115+
* @private
116+
*/
117+
handleMessage(msg) {
118+
if (!msg || !msg.type) {
119+
console.warn('[MessageBus] Received message without type:', msg)
120+
return
121+
}
122+
123+
const messageName = msg.type
124+
const callback = this.listeners.get(messageName)
125+
126+
if (!callback) {
127+
console.log(`[MessageBus] No listener for message name: ${messageName}`)
128+
return
129+
}
130+
131+
console.log(`[MessageBus] Executing listener for '${messageName}'`)
132+
133+
// Execute the listener for this message name
134+
try {
135+
callback(msg)
136+
} catch (error) {
137+
console.error(`[MessageBus] Error in listener for '${messageName}':`, error)
138+
}
139+
}
140+
}
141+
142+
// Export singleton instance
143+
export default new MessageBus()

0 commit comments

Comments
 (0)