Skip to content

Commit b96875a

Browse files
committed
- Added a new system to dynamically require source from multiple locations (including web)
- Added the Collection Element and moved parts of the List logic to collection - Added a State Management System - Added a better system to change the position/size of elements - Removed the state plugin
1 parent 565209d commit b96875a

19 files changed

Lines changed: 1698 additions & 520 deletions

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ Accordion.lua
1515
Stepper.lua
1616
Drawer.lua
1717
Breadcrumb.lua
18-
Dialog.lua
18+
Dialog.lua
19+
DockLayout.lua
20+
ContextMenu.lua

src/elementManager.lua

Lines changed: 288 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ local ElementManager = {}
1717
ElementManager._elements = {}
1818
ElementManager._plugins = {}
1919
ElementManager._APIs = {}
20+
ElementManager._config = {
21+
autoLoadMissing = false,
22+
allowRemoteLoading = false,
23+
allowDiskLoading = true,
24+
remoteSources = {},
25+
diskMounts = {},
26+
useGlobalCache = false,
27+
globalCacheName = "_BASALT_ELEMENT_CACHE"
28+
}
29+
2030
local elementsDirectory = fs.combine(dir, "elements")
2131
local pluginsDirectory = fs.combine(dir, "plugins")
2232

@@ -29,7 +39,9 @@ if fs.exists(elementsDirectory) then
2939
ElementManager._elements[name] = {
3040
class = nil,
3141
plugins = {},
32-
loaded = false
42+
loaded = false,
43+
source = "local",
44+
path = nil
3345
}
3446
end
3547
end
@@ -66,7 +78,9 @@ if(minified)then
6678
ElementManager._elements[name:gsub(".lua", "")] = {
6779
class = nil,
6880
plugins = {},
69-
loaded = false
81+
loaded = false,
82+
source = "local",
83+
path = nil
7084
}
7185
end
7286
if(minified_pluginDirectory==nil)then
@@ -90,20 +104,225 @@ if(minified)then
90104
end
91105
end
92106

107+
local function saveToGlobalCache(name, element)
108+
if not ElementManager._config.useGlobalCache then return end
109+
110+
if not _G[ElementManager._config.globalCacheName] then
111+
_G[ElementManager._config.globalCacheName] = {}
112+
end
113+
114+
_G[ElementManager._config.globalCacheName][name] = element
115+
log.debug("Cached element in _G: "..name)
116+
end
117+
118+
local function loadFromGlobalCache(name)
119+
if not ElementManager._config.useGlobalCache then return nil end
120+
121+
if _G[ElementManager._config.globalCacheName] and
122+
_G[ElementManager._config.globalCacheName][name] then
123+
log.debug("Loaded element from _G cache: "..name)
124+
return _G[ElementManager._config.globalCacheName][name]
125+
end
126+
127+
return nil
128+
end
129+
130+
--- Configures the ElementManager
131+
--- @param config table Configuration options
132+
function ElementManager.configure(config)
133+
for k, v in pairs(config) do
134+
if ElementManager._config[k] ~= nil then
135+
ElementManager._config[k] = v
136+
end
137+
end
138+
end
139+
140+
--- Registers a disk mount point for loading elements
141+
--- @param mountPath string The path to the disk mount
142+
function ElementManager.registerDiskMount(mountPath)
143+
if not fs.exists(mountPath) then
144+
error("Disk mount path does not exist: "..mountPath)
145+
end
146+
table.insert(ElementManager._config.diskMounts, mountPath)
147+
log.info("Registered disk mount: "..mountPath)
148+
149+
local elementsPath = fs.combine(mountPath, "elements")
150+
if fs.exists(elementsPath) then
151+
for _, file in ipairs(fs.list(elementsPath)) do
152+
local name = file:match("(.+).lua")
153+
if name then
154+
if not ElementManager._elements[name] then
155+
log.debug("Found element on disk: "..name)
156+
ElementManager._elements[name] = {
157+
class = nil,
158+
plugins = {},
159+
loaded = false,
160+
source = "disk",
161+
path = fs.combine(elementsPath, file)
162+
}
163+
end
164+
end
165+
end
166+
end
167+
end
168+
169+
--- Registers a remote source for an element
170+
--- @param elementName string The name of the element
171+
--- @param url string The URL to load the element from
172+
function ElementManager.registerRemoteSource(elementName, url)
173+
if not ElementManager._config.allowRemoteLoading then
174+
error("Remote loading is disabled. Enable with ElementManager.configure({allowRemoteLoading = true})")
175+
end
176+
ElementManager._config.remoteSources[elementName] = url
177+
178+
if not ElementManager._elements[elementName] then
179+
ElementManager._elements[elementName] = {
180+
class = nil,
181+
plugins = {},
182+
loaded = false,
183+
source = "remote",
184+
path = url
185+
}
186+
else
187+
ElementManager._elements[elementName].source = "remote"
188+
ElementManager._elements[elementName].path = url
189+
end
190+
191+
log.info("Registered remote source for "..elementName..": "..url)
192+
end
193+
194+
local function loadFromRemote(url)
195+
if not http then
196+
error("HTTP API is not available. Enable it in your CC:Tweaked config.")
197+
end
198+
199+
log.info("Loading element from remote: "..url)
200+
201+
local response = http.get(url)
202+
if not response then
203+
error("Failed to download from: "..url)
204+
end
205+
206+
local content = response.readAll()
207+
response.close()
208+
209+
if not content or content == "" then
210+
error("Empty response from: "..url)
211+
end
212+
213+
local func, err = load(content, url, "t", _ENV)
214+
if not func then
215+
error("Failed to load element from "..url..": "..tostring(err))
216+
end
217+
218+
local element = func()
219+
return element
220+
end
221+
222+
local function loadFromDisk(path)
223+
if not fs.exists(path) then
224+
error("Element file does not exist: "..path)
225+
end
226+
227+
log.info("Loading element from disk: "..path)
228+
229+
local func, err = loadfile(path)
230+
if not func then
231+
error("Failed to load element from "..path..": "..tostring(err))
232+
end
233+
234+
local element = func()
235+
return element
236+
end
237+
238+
--- Tries to load an element from any available source
239+
--- @param name string The element name
240+
--- @return boolean success Whether the element was loaded
241+
function ElementManager.tryAutoLoad(name)
242+
-- Try disk mounts first
243+
if ElementManager._config.allowDiskLoading then
244+
for _, mountPath in ipairs(ElementManager._config.diskMounts) do
245+
local elementsPath = fs.combine(mountPath, "elements")
246+
local filePath = fs.combine(elementsPath, name..".lua")
247+
248+
if fs.exists(filePath) then
249+
ElementManager._elements[name] = {
250+
class = nil,
251+
plugins = {},
252+
loaded = false,
253+
source = "disk",
254+
path = filePath
255+
}
256+
ElementManager.loadElement(name)
257+
return true
258+
end
259+
end
260+
end
261+
262+
if ElementManager._config.allowRemoteLoading and ElementManager._config.remoteSources[name] then
263+
ElementManager.loadElement(name)
264+
return true
265+
end
266+
267+
return false
268+
end
269+
93270
--- Loads an element by name. This will load the element and apply any plugins to it.
94271
--- @param name string The name of the element to load
95272
--- @usage ElementManager.loadElement("Button")
96273
function ElementManager.loadElement(name)
274+
if not ElementManager._elements[name] then
275+
-- Try to auto-load if enabled
276+
if ElementManager._config.autoLoadMissing then
277+
local success = ElementManager.tryAutoLoad(name)
278+
if not success then
279+
error("Element '"..name.."' not found and could not be auto-loaded")
280+
end
281+
else
282+
error("Element '"..name.."' not found")
283+
end
284+
end
285+
97286
if not ElementManager._elements[name].loaded then
98-
package.path = main.."rom/?"
99-
local element = require(fs.combine("elements", name))
100-
package.path = defaultPath
287+
local source = ElementManager._elements[name].source or "local"
288+
local element
289+
local loadedFromCache = false
290+
291+
element = loadFromGlobalCache(name)
292+
if element then
293+
loadedFromCache = true
294+
log.info("Loaded element from _G cache: "..name)
295+
elseif source == "local" then
296+
package.path = main.."rom/?"
297+
element = require(fs.combine("elements", name))
298+
package.path = defaultPath
299+
elseif source == "disk" then
300+
if not ElementManager._config.allowDiskLoading then
301+
error("Disk loading is disabled for element: "..name)
302+
end
303+
element = loadFromDisk(ElementManager._elements[name].path)
304+
saveToGlobalCache(name, element)
305+
elseif source == "remote" then
306+
if not ElementManager._config.allowRemoteLoading then
307+
error("Remote loading is disabled for element: "..name)
308+
end
309+
element = loadFromRemote(ElementManager._elements[name].path)
310+
saveToGlobalCache(name, element)
311+
else
312+
error("Unknown source type: "..source)
313+
end
314+
101315
ElementManager._elements[name] = {
102316
class = element,
103317
plugins = element.plugins,
104-
loaded = true
318+
loaded = true,
319+
source = loadedFromCache and "cache" or source,
320+
path = ElementManager._elements[name].path
105321
}
106-
log.debug("Loaded element: "..name)
322+
323+
if not loadedFromCache then
324+
log.debug("Loaded element: "..name.." from "..source)
325+
end
107326

108327
if(ElementManager._plugins[name]~=nil)then
109328
for _, plugin in pairs(ElementManager._plugins[name]) do
@@ -148,6 +367,17 @@ end
148367
--- @param name string The name of the element to get
149368
--- @return table Element The element class
150369
function ElementManager.getElement(name)
370+
if not ElementManager._elements[name] then
371+
if ElementManager._config.autoLoadMissing then
372+
local success = ElementManager.tryAutoLoad(name)
373+
if not success then
374+
error("Element '"..name.."' not found")
375+
end
376+
else
377+
error("Element '"..name.."' not found")
378+
end
379+
end
380+
151381
if not ElementManager._elements[name].loaded then
152382
ElementManager.loadElement(name)
153383
end
@@ -167,4 +397,55 @@ function ElementManager.getAPI(name)
167397
return ElementManager._APIs[name]
168398
end
169399

400+
--- Checks if an element exists (is registered)
401+
--- @param name string The element name
402+
--- @return boolean exists Whether the element exists
403+
function ElementManager.hasElement(name)
404+
return ElementManager._elements[name] ~= nil
405+
end
406+
407+
--- Checks if an element is loaded
408+
--- @param name string The element name
409+
--- @return boolean loaded Whether the element is loaded
410+
function ElementManager.isElementLoaded(name)
411+
return ElementManager._elements[name] and ElementManager._elements[name].loaded or false
412+
end
413+
414+
--- Clears the global cache (_G)
415+
--- @usage ElementManager.clearGlobalCache()
416+
function ElementManager.clearGlobalCache()
417+
if _G[ElementManager._config.globalCacheName] then
418+
_G[ElementManager._config.globalCacheName] = nil
419+
log.info("Cleared global element cache")
420+
end
421+
end
422+
423+
--- Gets cache statistics
424+
--- @return table stats Cache statistics with size and element names
425+
function ElementManager.getCacheStats()
426+
if not _G[ElementManager._config.globalCacheName] then
427+
return {size = 0, elements = {}}
428+
end
429+
430+
local elements = {}
431+
for name, _ in pairs(_G[ElementManager._config.globalCacheName]) do
432+
table.insert(elements, name)
433+
end
434+
435+
return {
436+
size = #elements,
437+
elements = elements
438+
}
439+
end
440+
441+
--- Preloads elements into the global cache
442+
--- @param elementNames table List of element names to preload
443+
function ElementManager.preloadElements(elementNames)
444+
for _, name in ipairs(elementNames) do
445+
if ElementManager._elements[name] and not ElementManager._elements[name].loaded then
446+
ElementManager.loadElement(name)
447+
end
448+
end
449+
end
450+
170451
return ElementManager

0 commit comments

Comments
 (0)