DNS record management, CSS grid, Regex, location.query

This commit is contained in:
Face
2025-08-20 14:37:57 +03:00
parent 99f17dc42c
commit e8508bfe33
22 changed files with 1351 additions and 123 deletions

View File

@@ -0,0 +1,84 @@
class_name GridUtils
extends RefCounted
static func apply_grid_container_properties(node: GridContainer, styles: Dictionary) -> void:
if styles.has("grid-template-columns"):
var cols = styles["grid-template-columns"]
if cols is int:
node.columns = cols
elif cols is String:
var parts = cols.split(" ")
node.columns = parts.size()
if styles.has("gap"):
var gap_value = parse_grid_value(styles["gap"])
if gap_value is int or gap_value is float:
node.add_theme_constant_override("h_separation", int(gap_value))
node.add_theme_constant_override("v_separation", int(gap_value))
if styles.has("column-gap"):
var gap_value = parse_grid_value(styles["column-gap"])
if gap_value is int or gap_value is float:
node.add_theme_constant_override("h_separation", int(gap_value))
if styles.has("row-gap"):
var gap_value = parse_grid_value(styles["row-gap"])
if gap_value is int or gap_value is float:
node.add_theme_constant_override("v_separation", int(gap_value))
static func apply_grid_item_properties(node: Control, styles: Dictionary) -> void:
var grid_properties: Dictionary = node.get_meta("grid_properties", {})
var changed = false
if styles.has("grid-column"):
grid_properties["grid-column"] = styles["grid-column"]
changed = true
if styles["grid-column"].begins_with("span "):
var span_count = styles["grid-column"].substr(5).to_int()
if span_count > 1:
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
node.set_meta("grid_column_span", span_count)
elif styles["grid-column"] == "1 / -1":
# Full span
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
node.set_meta("grid_column_span", -1)
if styles.has("grid-row"):
grid_properties["grid-row"] = styles["grid-row"]
changed = true
if changed:
node.set_meta("grid_properties", grid_properties)
static func parse_grid_value(val):
if val is float or val is int:
return val
if val is String:
var s_val = val.strip_edges()
if s_val.is_valid_float():
return s_val.to_float()
if s_val.ends_with("px"):
return s_val.trim_suffix("px").to_float()
if s_val == "auto":
return "auto"
return null
static func get_grid_item_span(span_property: String) -> Dictionary:
var result = {"start": -1, "end": -1, "span": 1}
if span_property.begins_with("span "):
var span_count = span_property.substr(5).to_int()
result["span"] = max(1, span_count)
elif span_property == "1 / -1":
# Full span
result["span"] = -1
else:
var parts = span_property.split(" / ")
if parts.size() == 2:
result["start"] = parts[0].to_int()
result["end"] = parts[1].to_int()
return result

View File

@@ -0,0 +1 @@
uid://4p5lrhmdwgrj

View File

@@ -341,32 +341,72 @@ static func _find_input_control_with_file_info(node: Node) -> Node:
return null
static func _get_select_value(element: HTMLParser.HTMLElement, dom_node: Node) -> String:
if dom_node is OptionButton:
var option_button = dom_node as OptionButton
var selected_index = option_button.selected
if selected_index >= 0 and selected_index < option_button.get_item_count():
var metadata = option_button.get_item_metadata(selected_index)
if metadata:
return str(metadata)
else:
return option_button.get_item_text(selected_index)
return ""
static func _set_select_value(element: HTMLParser.HTMLElement, dom_node: Node, value: Variant) -> void:
if dom_node is OptionButton:
var option_button = dom_node as OptionButton
var target_value = str(value)
# Find the correct index to select
var selected_index = -1
# Find option with matching value
for i in range(option_button.get_item_count()):
var metadata = option_button.get_item_metadata(i)
var option_value = str(metadata) if metadata else option_button.get_item_text(i)
if option_value == target_value:
selected_index = i
break
# If no matching value found, try to find by text
if selected_index == -1:
for i in range(option_button.get_item_count()):
if option_button.get_item_text(i) == target_value:
selected_index = i
break
# Use call_deferred to set the property on main thread
if selected_index != -1:
option_button.call_deferred("set", "selected", selected_index)
static func _set_input_value(element: HTMLParser.HTMLElement, dom_node: Node, value: Variant) -> void:
var input_type = element.get_attribute("type").to_lower()
match input_type:
"checkbox", "radio":
if dom_node is CheckBox:
dom_node.button_pressed = bool(value)
dom_node.call_deferred("set", "button_pressed", bool(value))
"color":
if dom_node is ColorPickerButton:
var color_value = str(value)
if color_value.begins_with("#"):
dom_node.color = Color.from_string(color_value, Color.WHITE)
var color = Color.from_string(color_value, Color.WHITE)
dom_node.call_deferred("set", "color", color)
"range":
if dom_node is HSlider:
dom_node.value = float(value)
dom_node.call_deferred("set", "value", float(value))
"number":
if dom_node is SpinBox:
dom_node.value = float(value)
dom_node.call_deferred("set", "value", float(value))
"date":
if dom_node is DateButton and dom_node.has_method("set_date_from_string"):
dom_node.set_date_from_string(str(value))
dom_node.call_deferred("set_date_from_string", str(value))
_: # text, password, email, etc.
if dom_node is LineEdit:
dom_node.text = str(value)
dom_node.call_deferred("set", "text", str(value))
elif dom_node is TextEdit:
dom_node.text = str(value)
dom_node.call_deferred("set", "text", str(value))
# Helper functions
static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement:
@@ -931,7 +971,7 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
match key:
"value":
if lua_api and tag_name == "input":
if lua_api and (tag_name == "input" or tag_name == "select"):
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
@@ -940,8 +980,13 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
if element and dom_node:
var input_value = _get_input_value(element, dom_node)
vm.lua_pushstring(str(input_value))
var value_result: String
if tag_name == "input":
value_result = str(_get_input_value(element, dom_node))
elif tag_name == "select":
value_result = _get_select_value(element, dom_node)
vm.lua_pushstring(value_result)
return 1
# Fallback to empty string
@@ -1225,7 +1270,7 @@ static func _element_newindex_wrapper(vm: LuauVM) -> int:
match key:
"value":
if tag_name == "input":
if tag_name == "input" or tag_name == "select":
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
@@ -1234,10 +1279,12 @@ static func _element_newindex_wrapper(vm: LuauVM) -> int:
var dom_node = lua_api.dom_parser.parse_result.dom_nodes.get(element_id, null)
if element and dom_node:
# Update the HTML element's value attribute
element.set_attribute("value", str(value))
_set_input_value(element, dom_node, value)
if tag_name == "input":
element.set_attribute("value", str(value))
_set_input_value(element, dom_node, value)
elif tag_name == "select":
element.set_attribute("value", str(value))
_set_select_value(element, dom_node, value)
return 0
"text":
var text: String = str(value) # Convert value to string

View File

@@ -25,7 +25,7 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
elif signal_node is Control:
var wrapper = func(event: InputEvent):
LuaAudioUtils.mark_user_event()
subscription.lua_api._on_gui_input_click(subscription, event)
subscription.lua_api._on_gui_input_click(event, subscription)
signal_node.gui_input.connect(wrapper)
subscription.connected_signal = "gui_input"
subscription.connected_node = signal_node

View File

@@ -0,0 +1,142 @@
class_name LuaRegexUtils
extends RefCounted
static func regex_new_handler(vm: LuauVM) -> int:
var pattern: String = vm.luaL_checkstring(1)
var regex = RegEx.new()
var result = regex.compile(pattern)
if result != OK:
vm.luaL_error("Invalid regex pattern: " + pattern)
return 0
vm.lua_newtable()
vm.lua_pushobject(regex)
vm.lua_setfield(-2, "_regex")
vm.lua_pushcallable(regex_match_handler, "regex:match")
vm.lua_setfield(-2, "match")
vm.lua_pushcallable(regex_test_handler, "regex:test")
vm.lua_setfield(-2, "test")
return 1
static func regex_match_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var subject: String = vm.luaL_checkstring(2)
vm.lua_getfield(1, "_regex")
var regex: RegEx = vm.lua_toobject(-1) as RegEx
vm.lua_pop(1)
if not regex:
vm.luaL_error("Invalid regex object")
return 0
var result = regex.search(subject)
if not result:
vm.lua_pushnil()
return 1
vm.lua_newtable()
vm.lua_pushstring(result.get_string())
vm.lua_rawseti(-2, 1)
for i in range(1, result.get_group_count()):
var group = result.get_string(i)
vm.lua_pushstring(group)
vm.lua_rawseti(-2, i + 1)
return 1
static func regex_test_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)
var subject: String = vm.luaL_checkstring(2)
vm.lua_getfield(1, "_regex")
var regex: RegEx = vm.lua_toobject(-1) as RegEx
vm.lua_pop(1)
if not regex:
vm.luaL_error("Invalid regex object")
return 0
var result = regex.search(subject)
vm.lua_pushboolean(result != null)
return 1
static func string_replace_handler(vm: LuauVM) -> int:
var subject: String = vm.luaL_checkstring(1)
if vm.lua_istable(2):
vm.lua_getfield(2, "_regex")
var regex: RegEx = vm.lua_toobject(-1) as RegEx
vm.lua_pop(1)
if not regex:
vm.luaL_error("Invalid regex object")
return 0
var replacement: String = vm.luaL_checkstring(3)
var result = regex.sub(subject, replacement, false)
vm.lua_pushstring(result)
else:
var search: String = vm.luaL_checkstring(2)
var replacement: String = vm.luaL_checkstring(3)
var pos = subject.find(search)
if pos >= 0:
var result = subject.substr(0, pos) + replacement + subject.substr(pos + search.length())
vm.lua_pushstring(result)
else:
vm.lua_pushstring(subject)
return 1
static func string_replace_all_handler(vm: LuauVM) -> int:
var subject: String = vm.luaL_checkstring(1)
if vm.lua_istable(2):
vm.lua_getfield(2, "_regex")
var regex: RegEx = vm.lua_toobject(-1) as RegEx
vm.lua_pop(1)
if not regex:
vm.luaL_error("Invalid regex object")
return 0
var replacement: String = vm.luaL_checkstring(3)
var result = regex.sub(subject, replacement, true)
vm.lua_pushstring(result)
else:
var search: String = vm.luaL_checkstring(2)
var replacement: String = vm.luaL_checkstring(3)
var result = subject.replace(search, replacement)
vm.lua_pushstring(result)
return 1
static func setup_regex_api(vm: LuauVM) -> void:
vm.lua_newtable()
vm.lua_pushcallable(regex_new_handler, "Regex.new")
vm.lua_setfield(-2, "new")
vm.lua_setglobal("Regex")
vm.lua_getglobal("string")
if vm.lua_isnil(-1):
vm.lua_pop(1)
vm.lua_newtable()
vm.lua_setglobal("string")
vm.lua_getglobal("string")
vm.lua_pushcallable(string_replace_handler, "string.replace")
vm.lua_setfield(-2, "replace")
vm.lua_pushcallable(string_replace_all_handler, "string.replaceAll")
vm.lua_setfield(-2, "replaceAll")
vm.lua_pop(1)

View File

@@ -0,0 +1 @@
uid://c23cbsiegb0rq

View File

@@ -315,6 +315,15 @@ func _setup_threaded_gurt_api():
lua_vm.lua_pushstring(current_href)
lua_vm.lua_setfield(-2, "href")
lua_vm.lua_newtable()
lua_vm.lua_pushcallable(lua_api._gurt_location_query_get_handler, "gurt.location.query.get")
lua_vm.lua_setfield(-2, "get")
lua_vm.lua_pushcallable(lua_api._gurt_location_query_has_handler, "gurt.location.query.has")
lua_vm.lua_setfield(-2, "has")
lua_vm.lua_pushcallable(lua_api._gurt_location_query_getAll_handler, "gurt.location.query.getAll")
lua_vm.lua_setfield(-2, "getAll")
lua_vm.lua_setfield(-2, "query")
lua_vm.lua_setfield(-2, "location")
var body_element = dom_parser.find_first("body")
@@ -345,6 +354,7 @@ func _setup_additional_lua_apis():
LuaWebSocketUtils.setup_websocket_api(lua_vm)
LuaAudioUtils.setup_audio_api(lua_vm)
LuaCrumbsUtils.setup_crumbs_api(lua_vm)
LuaRegexUtils.setup_regex_api(lua_vm)
func _table_tostring_handler(vm: LuauVM) -> int:
vm.luaL_checktype(1, vm.LUA_TTABLE)