Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion lua/wikis/commons/Widget.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,28 @@ local Table = Lua.import('Module:Table')
---@operator call(table): self
---@field context Widget[]
---@field props table<string, any>
---@field propSpec table<string, any>
local Widget = Class.new(function(self, props)
self.props = Table.deepMerge(Table.deepCopy(self.defaultProps), props)
---@return table<string, any>
local getDefaultProps = function()
if Table.isNotEmpty(self.defaultProps) then
return self.defaultProps
end
Comment on lines +26 to +28
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo add a comment that this handling is only legacy and deprecated

if Table.isNotEmpty(self.propSpec) then
return Table.mapValues(self.propSpec, function(prop)
return prop.default
end)
end
return {}
end

self.props = Table.deepMerge(Table.deepCopy(getDefaultProps()), props)

for propName, prop in pairs(self.propSpec) do
if prop.required and self.props[propName] == nil then
error('Missing required property: ' .. propName)
end
end

if not Array.isArray(self.props.children) then
self.props.children = {self.props.children}
Expand All @@ -29,7 +49,9 @@ local Widget = Class.new(function(self, props)
self.context = {} -- Populated by the parent
end)

---@deprecated
Widget.defaultProps = {}
Widget.propSpec = {}

---Asserts the existence of a value and copies it
---@param value string
Expand Down
20 changes: 15 additions & 5 deletions lua/wikis/commons/Widget/Basic/Slider.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,39 @@ local Div = HtmlWidgets.Div
---@field props SliderWidgetParameters

local Slider = Class.new(Widget)
Slider.propSpec = {
id = {type = 'integer', required = true},
min = {type = 'integer', default = 1},
max = {type = 'integer', default = 100},
step = {type = 'integer', default = 1},
defaultValue = {type = 'integer', default = 1},
class = {type = 'string'},
title = {type = 'function', default = function(v) return tostring(v) end},
childrenAtValue = {type = 'function', required = true},
}

---@return Widget
function Slider:render()
assert(self.props.id, 'Slider requires a unique id property')
-- We make the real slider in js
local min, max, step = self.props.min or 0, self.props.max or 100, self.props.step or 1
local min, max, step = self.props.min, self.props.max, self.props.step

local children = {}
for value = min, max, step do
table.insert(children, {
content = self.props.childrenAtValue(value) or '',
title = self.props.title and self.props.title(value) or value,
title = self.props.title(value),
value = value,
})
end

-- We make the real slider in js
return Div{
classes = { 'slider' },
attributes = {
['data-id'] = self.props.id,
['data-min'] = min,
['data-max'] = max,
['data-step'] = step,
['data-value'] = self.props.defaultValue or self.props.min or 0,
['data-value'] = self.props.defaultValue,
},
children = Array.map(children, function(child)
return HtmlWidgets.Div{
Expand Down
67 changes: 64 additions & 3 deletions lua/wikis/commons/Widget/Factory.lua
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,78 @@
local Lua = require('Module:Lua')

local Class = Lua.import('Module:Class')
local Json = Lua.import('Module:Json')
local Logic = Lua.import('Module:Logic')

local WidgetFactory = {}

---@param args {widget: string, children: ((Widget|Html|string|number)[])|Widget|Html|string|number, [any]:any}
---@param args {widget: string, children: any, [any]:any}
Comment on lines -14 to +16
Copy link
Collaborator

@ElectricalBoy ElectricalBoy Sep 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo make a type alias for the original children type and use that
so something like Renderable = Widget|Html|string|number and make children of type Renderable|Renderable[]

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice idea we could use it in some other places too :)

---@return Widget
function WidgetFactory.fromTemplate(args)
local widgetClass = args.widget
args.widget = nil
args.children = type(args.children) == 'table' and args.children or {args.children}
local WidgetClass = Lua.import('Module:Widget/' .. widgetClass)

local WidgetClass = Lua.requireIfExists('Module:Widget/' .. widgetClass)
assert(WidgetClass, 'Widget not found: ' .. widgetClass)
---@cast WidgetClass Widget

local propSpec = WidgetClass.propSpec
args.children = WidgetFactory._parseTable(args.children) or args.children

for propName, prop in pairs(propSpec) do
if prop.type == 'integer' then
args[propName] = WidgetFactory._parseInteger(args[propName])
Comment on lines +30 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about non integer numbers?
maybe

Suggested change
if prop.type == 'integer' then
args[propName] = WidgetFactory._parseInteger(args[propName])
if prop.type == 'integer' or prop.type == 'number' then
args[propName] = WidgetFactory._parseNumber(args[propName])

elseif prop.type == 'string' then
args[propName] = WidgetFactory._parseString(args[propName])
elseif prop.type == 'table' then
args[propName] = WidgetFactory._parseTable(args[propName])
elseif prop.type == 'boolean' then
args[propName] = WidgetFactory._parseBoolean(args[propName])
end
end

return WidgetClass(args)
end

---@param integer any
---@return integer|nil
function WidgetFactory._parseInteger(integer)
if type(integer) == 'number' then
return integer
end
return tonumber(integer)
end

---@param string any
---@return string|nil
function WidgetFactory._parseString(string)
if type(string) == 'string' then
return string
end
return tostring(string)
end

---@param table any
---@return table|nil
function WidgetFactory._parseTable(table)
if type(table) == 'table' then
return table
elseif type(table) == 'string' then
local parsedTable, parsingError = Json.parseStringified(table)
if not parsingError then
return parsedTable
end
end
return nil
end

---@param boolean any
---@return boolean|nil
function WidgetFactory._parseBoolean(boolean)
if type(boolean) == 'boolean' then
return boolean
end
return Logic.readBool(boolean)
end

return Class.export(WidgetFactory, {exports = {'fromTemplate'}})
Loading