user domains API, .value on input, fix flex sizing

This commit is contained in:
Face
2025-08-19 18:27:25 +03:00
parent dacda095d5
commit 99f17dc42c
18 changed files with 705 additions and 356 deletions

View File

@@ -28,7 +28,6 @@ Issues:
3. Certain properties like `scale` and `rotate` don't apply to the `active` pseudo-class because they rely on mouse_enter and mouse_exit events 3. Certain properties like `scale` and `rotate` don't apply to the `active` pseudo-class because they rely on mouse_enter and mouse_exit events
4. `<div style="bg-[#3b82f6] w-[100px] h-[100px] flex hover:scale-110 transition hover:rotate-45">Box</div>` something like this has the "Box" text (presumably the PanelContainer) as the target of the hover, not the div itself (which has the w/h size) 4. `<div style="bg-[#3b82f6] w-[100px] h-[100px] flex hover:scale-110 transition hover:rotate-45">Box</div>` something like this has the "Box" text (presumably the PanelContainer) as the target of the hover, not the div itself (which has the w/h size)
5. font in button doesn't comply with CSS, its the projects default 5. font in button doesn't comply with CSS, its the projects default
6. Flex containers, ironically enough, make the page unresponsive. This happens because of our custom `AutoSizingFlexContainer.gd` script, which aims to set a Godot UI size to the flex containers based on their content. However, they don't get resized when the window is resized, leading to unresponsiveness. The fact that we're setting the `custom_minimum_size` is not the root cause, but rather the fact that the script doesn't update the size when the window is resized - or, more likely, I just don't understand how flexbox works.
Notes: Notes:
- **< input />** is sort-of inline in normal web. We render it as a block element (new-line). - **< input />** is sort-of inline in normal web. We render it as a block element (new-line).

View File

@@ -19,6 +19,7 @@ This is a Domain Management API built with Rust (Actix Web) and PostgreSQL. It p
- [GET /auth/me](#get-authme) - [GET /auth/me](#get-authme)
- [POST /auth/invite](#post-authinvite) - [POST /auth/invite](#post-authinvite)
- [POST /auth/redeem-invite](#post-authredeem-invite) - [POST /auth/redeem-invite](#post-authredeem-invite)
- [GET /auth/domains](#get-authdomains) 🔒
- [Domain Endpoints](#domain-endpoints) - [Domain Endpoints](#domain-endpoints)
- [GET /](#get-) - [GET /](#get-)
- [POST /domain](#post-domain) 🔒 - [POST /domain](#post-domain) 🔒
@@ -127,6 +128,50 @@ Redeem an invite code to get 3 additional domain registrations. Requires authent
} }
``` ```
### GET /auth/domains 🔒
Get all domains owned by the authenticated user, including their status. Requires `Authorization: Bearer <token>` header.
**Query Parameters:**
- `page` - Page number (default: 1)
- `limit` - Items per page (default: 100, max: 1000)
**Response:**
```json
{
"domains": [
{
"name": "myawesome",
"tld": "dev",
"ip": "192.168.1.100",
"status": "approved",
"denial_reason": null
},
{
"name": "pending",
"tld": "fr",
"ip": "10.0.0.1",
"status": "pending",
"denial_reason": null
},
{
"name": "rejected",
"tld": "mf",
"ip": "172.16.0.1",
"status": "denied",
"denial_reason": "Invalid IP address"
}
],
"page": 1,
"limit": 100
}
```
**Status Values:**
- `pending` - Domain is awaiting approval
- `approved` - Domain has been approved and is active
- `denied` - Domain was rejected (see `denial_reason` for details)
## Domain Endpoints ## Domain Endpoints
### GET / ### GET /
@@ -159,15 +204,6 @@ Submit a domain for approval. Requires authentication and consumes one registrat
} }
``` ```
**Response:**
```json
{
"message": "Domain registration submitted for approval",
"domain": "myawesome.dev",
"status": "pending"
}
```
**Error Responses:** **Error Responses:**
- `401 Unauthorized` - Missing or invalid JWT token - `401 Unauthorized` - Missing or invalid JWT token
- `400 Bad Request` - No registrations remaining, invalid domain, or offensive name - `400 Bad Request` - No registrations remaining, invalid domain, or offensive name

View File

@@ -26,15 +26,15 @@
} }
.primary-btn { .primary-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white w-32 h-12
} }
.success-btn { .success-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white w-32 h-12
} }
.danger-btn { .danger-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#b91c1c] text-white p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#b91c1c] text-white w-32 h-12
} }
.secondary-btn { .secondary-btn {
@@ -42,7 +42,7 @@
} }
.warning-btn { .warning-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white w-32 h-12
} }
.form-group { .form-group {
@@ -58,7 +58,7 @@
} }
.stats-card { .stats-card {
bg-[#1f1f1f] p-4 rounded-lg border border-[#dc2626] p-4 rounded-lg border border-[#dc2626]
} }
.domain-item { .domain-item {
@@ -98,48 +98,10 @@
<div style="stats-card mb-6"> <div style="stats-card mb-6">
<div style="flex justify-between items-center w-full"> <div style="flex justify-between items-center w-full">
<p id="user-info" style="text-white text-lg font-semibold">Loading...</p> <p id="user-info" style="text-white text-lg font-semibold">Loading...</p>
<button id="logout-btn" style="secondary-btn">Logout</button>
</div>
</div>
<div style="card mb-6">
<h2 style="mb-4">Register New Domain</h2>
<div style="form-group">
<p>Domain Name:</p>
<input id="domain-name" type="text" style="form-input" placeholder="myawesome" />
</div>
<div style="form-group">
<p>Select TLD:</p>
<div id="tld-selector" style="tld-selector">
<p id="tld-loading">Loading TLDs...</p>
</div>
</div>
<div style="form-group">
<p>IP Address:</p>
<input id="domain-ip" type="text" style="form-input" placeholder="192.168.1.100" />
</div>
<div id="domain-error" style="error-text hidden mb-2"></div>
<button id="submit-domain-btn" style="success-btn">Submit for Approval</button>
</div>
<div style="card mb-6">
<h2>Invite System</h2>
<p style="text-[#6b7280] mb-4">Create invite codes to share with friends, or redeem codes to get more domain
registrations.</p>
<p id="invite-code-display" style="invite-code-display mt-2">Placeholder</p>
<div style="flex flex-col gap-4 items-center justify-center mx-auto">
<h3>Create Invite</h3>
<button id="create-invite-btn" style="warning-btn">Generate Invite Code</button>
</div>
<div style="flex flex-col gap-4 mx-auto">
<h3>Redeem Invite</h3>
<div style="flex gap-2"> <div style="flex gap-2">
<input id="invite-code-input" type="text" style="form-input" placeholder="Enter invite code" /> <button id="new-btn" style="success-btn">New</button>
<button id="redeem-invite-btn" style="primary-btn">Redeem</button> <button id="logout-btn" style="secondary-btn">Logout</button>
</div> </div>
<div id="redeem-error" style="error-text hidden mt-2"></div>
</div> </div>
</div> </div>

View File

@@ -1,16 +1,12 @@
local user = nil local user = nil
local domains = {} local domains = {}
local tlds = {}
local authToken = nil local authToken = nil
local userInfo = gurt.select('#user-info') local userInfo = gurt.select('#user-info')
local domainsList = gurt.select('#domains-list') local domainsList = gurt.select('#domains-list')
local tldSelector = gurt.select('#tld-selector')
local loadingElement = gurt.select('#tld-loading') local loadingElement = gurt.select('#tld-loading')
local displayElement = gurt.select('#invite-code-display')
local options
displayElement:hide() local options
local function showError(elementId, message) local function showError(elementId, message)
local element = gurt.select('#' .. elementId) local element = gurt.select('#' .. elementId)
@@ -29,57 +25,17 @@ local function updateUserInfo()
userInfo.text = 'Welcome, ' .. user.username .. '!' userInfo.text = 'Welcome, ' .. user.username .. '!'
end end
local function renderTLDSelector()
loadingElement:remove()
tldSelector.text = ''
local i = 1
local total = #tlds
local intervalId
intervalId = gurt.setInterval(function()
if i > total then
gurt.clearInterval(intervalId)
return
end
local tld = tlds[i]
local option = gurt.create('button', {
text = '.' .. tld,
style = 'tld-option',
['data-tld'] = tld
})
tldSelector:append(option)
option:on('click', function()
-- Clear previous selection
if not options then
options = gurt.selectAll('.tld-option')
end
for j = 1, #options do
if options[j].classList:contains('tld-selected') then
options[j].classList:remove('tld-selected')
end
end
-- Select this option
option.classList:add('tld-selected')
end)
i = i + 1
end, 16)
end
local function renderDomains() local function renderDomains()
local loadingElement = gurt.select('#domains-loading') local loadingElement = gurt.select('#domains-loading')
loadingElement:remove() if loadingElement then
loadingElement:remove()
end
domainsList.text = '' domainsList.text = ''
if #domains == 0 then if #domains == 0 then
local emptyMessage = gurt.create('div', { local emptyMessage = gurt.create('div', {
text = 'No domains registered yet. Submit your first domain below!', text = 'No domains registered yet. Click "New" to register your first domain!',
style = 'text-center text-[#6b7280] py-8' style = 'text-center text-[#6b7280] py-8'
}) })
domainsList:append(emptyMessage) domainsList:append(emptyMessage)
@@ -91,7 +47,7 @@ local function renderDomains()
style = 'domain-item' style = 'domain-item'
}) })
local domainInfo = gurt.create('div', {}) local domainInfo = gurt.create('div', { style = 'w-full' })
local domainName = gurt.create('div', { local domainName = gurt.create('div', {
text = domain.name .. '.' .. domain.tld, text = domain.name .. '.' .. domain.tld,
@@ -103,8 +59,14 @@ local function renderDomains()
style = 'text-[#6b7280]' style = 'text-[#6b7280]'
}) })
local domainStatus = gurt.create('div', {
text = 'Status: ' .. (domain.status or 'Unknown'),
style = 'text-[#6b7280]'
})
domainInfo:append(domainName) domainInfo:append(domainName)
domainInfo:append(domainIP) domainInfo:append(domainIP)
domainInfo:append(domainStatus)
local actions = gurt.create('div', { local actions = gurt.create('div', {
style = 'flex gap-2' style = 'flex gap-2'
@@ -143,7 +105,7 @@ end
local function loadDomains() local function loadDomains()
print('Loading domains...') print('Loading domains...')
local response = fetch('gurt://localhost:8877/domains?page=1&size=100', { local response = fetch('gurt://localhost:8877/auth/domains?page=1&size=100', {
headers = { headers = {
Authorization = 'Bearer ' .. authToken Authorization = 'Bearer ' .. authToken
} }
@@ -159,19 +121,6 @@ local function loadDomains()
end end
end end
local function loadTLDs()
print('Loading available TLDs...')
local response = fetch('gurt://localhost:8877/tlds')
if response:ok() then
tlds = response:json()
print('Loaded ' .. #tlds .. ' TLDs')
renderTLDSelector()
else
print('Failed to load TLDs: ' .. response:text())
end
end
local function checkAuth() local function checkAuth()
authToken = gurt.crumbs.get("auth_token") authToken = gurt.crumbs.get("auth_token")
@@ -188,7 +137,6 @@ local function checkAuth()
print('Authentication successful for user: ' .. user.username) print('Authentication successful for user: ' .. user.username)
updateUserInfo() updateUserInfo()
loadDomains() loadDomains()
loadTLDs()
else else
print('Token invalid, redirecting to login...') print('Token invalid, redirecting to login...')
gurt.crumbs.delete('auth_token') gurt.crumbs.delete('auth_token')
@@ -206,126 +154,13 @@ local function logout()
gurt.location.goto("../") gurt.location.goto("../")
end end
local function submitDomain(name, tld, ip) local function goToRegister()
hideError('domain-error') gurt.location.goto("/register.html")
print('Submitting domain: ' .. name .. '.' .. tld)
local response = fetch('gurt://localhost:8877/domain', {
method = 'POST',
headers = {
['Content-Type'] = 'application/json',
Authorization = 'Bearer ' .. authToken
},
body = JSON.stringify({ name = name, tld = tld, ip = ip })
})
if response:ok() then
local data = response:json()
print('Domain submitted successfully: ' .. data.domain)
-- Update user registrations remaining
user.registrations_remaining = user.registrations_remaining - 1
updateUserInfo()
-- Clear form
gurt.select('#domain-name').text = ''
gurt.select('#domain-ip').text = ''
-- Refresh domains list
loadDomains()
else
local error = response:text()
showError('domain-error', 'Domain submission failed: ' .. error)
print('Domain submission failed: ' .. error)
end
end
local function createInvite()
print('Creating invite code...')
local response = fetch('gurt://localhost:8877/auth/invite', {
method = 'POST',
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
local data = response:json()
local inviteCode = data.invite_code
displayElement.text = 'Invite code: ' .. inviteCode .. ' (copied to clipboard)'
displayElement:show()
Clipboard.write(inviteCode)
print('Invite code created and copied to clipboard: ' .. inviteCode)
else
print('Failed to create invite: ' .. response:text())
end
end
local function redeemInvite(code)
hideError('redeem-error')
print('Redeeming invite code: ' .. code)
local response = fetch('gurt://localhost:8877/auth/redeem-invite', {
method = 'POST',
headers = {
['Content-Type'] = 'application/json',
Authorization = 'Bearer ' .. authToken
},
body = JSON.stringify({ invite_code = code })
})
if response:ok() then
local data = response:json()
print('Invite redeemed: +' .. data.registrations_added .. ' registrations')
-- Update user info
user.registrations_remaining = user.registrations_remaining + data.registrations_added
updateUserInfo()
-- Clear form
gurt.select('#invite-code-input').text = ''
else
local error = response:text()
showError('redeem-error', 'Failed to redeem invite: ' .. error)
print('Failed to redeem invite: ' .. error)
end
end end
-- Event handlers -- Event handlers
gurt.select('#logout-btn'):on('click', logout) gurt.select('#logout-btn'):on('click', logout)
gurt.select('#new-btn'):on('click', goToRegister)
gurt.select('#submit-domain-btn'):on('click', function()
local name = gurt.select('#domain-name').text
local ip = gurt.select('#domain-ip').text
local selectedTLD = gurt.select('.tld-selected')
if not name or name == '' then
showError('domain-error', 'Domain name is required')
return
end
if not ip or ip == '' then
showError('domain-error', 'IP address is required')
return
end
if not selectedTLD then
showError('domain-error', 'Please select a TLD')
return
end
local tld = selectedTLD:getAttribute('data-tld')
submitDomain(name, tld, ip)
end)
gurt.select('#create-invite-btn'):on('click', createInvite)
gurt.select('#redeem-invite-btn'):on('click', function()
local code = gurt.select('#invite-code-input').text
if code and code ~= '' then
redeemInvite(code)
end
end)
-- Initialize -- Initialize
print('Dashboard initialized') print('Dashboard initialized')

View File

@@ -38,6 +38,7 @@
#log-output { text-white p-4 rounded-md mt-4 font-mono max-h-40 } #log-output { text-white p-4 rounded-md mt-4 font-mono max-h-40 }
</style> </style>
<script src="script.lua" />
</head> </head>
<body> <body>

136
dns/frontend/register.html Normal file
View File

@@ -0,0 +1,136 @@
<head>
<title>Register Domain</title>
<icon src="https://cdn-icons-png.flaticon.com/512/295/295128.png">
<meta name="theme-color" content="#0891b2">
<meta name="description" content="Register a new domain">
<style>
body {
bg-[#171616] font-sans text-white
}
h1 {
text-[#ef4444] text-3xl font-bold text-center
}
h2 {
text-[#dc2626] text-xl font-semibold
}
h3 {
text-[#fca5a5] text-lg font-medium
}
.container {
bg-[#262626] p-6 rounded-lg shadow-lg max-w-6xl
}
.primary-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white
}
.success-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white
}
.secondary-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#525252] text-white w-32 h-12
}
.warning-btn {
p-3 rounded-lg font-medium cursor-pointer transition-colors bg-[#dc2626] text-white
}
.form-group {
flex flex-col gap-2 mb-4 w-full
}
.form-input {
w-full p-3 border border-gray-600 rounded-md bg-[#374151] text-white active:border-red-500
}
.card {
bg-[#262626] p-6 rounded-lg shadow border border-gray-700
}
.stats-card {
bg-[#1f1f1f] p-4 rounded-lg border border-[#dc2626]
}
.error-text {
text-[#fca5a5] text-sm
}
.tld-selector {
flex flex-wrap gap-2
}
.tld-option {
px-3 py-1 rounded border border-gray-600 cursor-pointer bg-[#374151] text-white w-12 h-12
}
.tld-selected {
bg-[#dc2626] text-white
}
.invite-code-display {
bg-[#374151] p-3 rounded font-mono text-center mb-2 text-white
}
</style>
<script src="register.lua" />
</head>
<body>
<div style="container mt-6">
<div style="stats-card mb-6">
<div style="flex justify-between items-center w-full">
<p id="user-info" style="text-white text-lg font-semibold">Loading...</p>
<div style="flex gap-2">
<button id="dashboard-btn" style="secondary-btn">Dashboard</button>
<button id="logout-btn" style="secondary-btn">Logout</button>
</div>
</div>
</div>
<div style="card mb-6">
<h2 id="remaining" style="mb-4">Register New Domain</h2>
<div style="form-group">
<p>Domain Name:</p>
<input id="domain-name" type="text" style="form-input" placeholder="myawesome" />
</div>
<div style="form-group">
<p>Select TLD:</p>
<div id="tld-selector" style="tld-selector">
<p id="tld-loading">Loading TLDs...</p>
</div>
</div>
<div style="form-group">
<p>IP Address:</p>
<input id="domain-ip" type="text" style="form-input" placeholder="192.168.1.100" />
</div>
<div id="domain-error" style="error-text hidden mb-2"></div>
<button id="submit-domain-btn" style="success-btn">Submit for Approval</button>
</div>
<div style="card mb-6">
<h2>Invite System</h2>
<p style="text-[#6b7280] mb-4">Create invite codes to share with friends, or redeem codes to get more domain registrations.</p>
<p id="invite-code-display" style="invite-code-display mt-2">Placeholder</p>
<div style="flex flex-col gap-4 items-center justify-center mx-auto">
<h3>Create Invite</h3>
<button id="create-invite-btn" style="warning-btn">Generate Invite Code</button>
</div>
<div style="flex flex-col gap-4 mx-auto">
<h3>Redeem Invite</h3>
<div style="flex gap-2">
<input id="invite-code-input" type="text" style="form-input" placeholder="Enter invite code" />
<button id="redeem-invite-btn" style="primary-btn">Redeem</button>
</div>
<div id="redeem-error" style="error-text hidden mt-2"></div>
</div>
</div>
</div>
</body>

257
dns/frontend/register.lua Normal file
View File

@@ -0,0 +1,257 @@
local user = nil
local tlds = {}
local authToken = nil
local userInfo = gurt.select('#user-info')
local tldSelector = gurt.select('#tld-selector')
local loadingElement = gurt.select('#tld-loading')
local displayElement = gurt.select('#invite-code-display')
local remainingElement = gurt.select('#remaining')
local options
displayElement:hide()
local function showError(elementId, message)
local element = gurt.select('#' .. elementId)
element.text = message
element.classList:remove('hidden')
end
local function hideError(elementId)
local element = gurt.select('#' .. elementId)
element.classList:add('hidden')
end
local function updateUserInfo()
userInfo.text = 'Welcome, ' .. user.username .. '!'
remainingElement.text = 'Register New Domain (' .. user.registrations_remaining .. ' remaining)'
end
local function renderTLDSelector()
loadingElement:remove()
tldSelector.text = ''
local i = 1
local total = #tlds
local intervalId
intervalId = gurt.setInterval(function()
if i > total then
gurt.clearInterval(intervalId)
return
end
local tld = tlds[i]
local option = gurt.create('button', {
text = '.' .. tld,
style = 'tld-option',
['data-tld'] = tld
})
tldSelector:append(option)
option:on('click', function()
-- Clear previous selection
if not options then
options = gurt.selectAll('.tld-option')
end
for j = 1, #options do
if options[j].classList:contains('tld-selected') then
options[j].classList:remove('tld-selected')
end
end
-- Select this option
option.classList:add('tld-selected')
end)
i = i + 1
end, 16)
end
local function loadTLDs()
print('Loading available TLDs...')
local response = fetch('gurt://localhost:8877/tlds')
if response:ok() then
tlds = response:json()
print('Loaded ' .. #tlds .. ' TLDs')
renderTLDSelector()
else
print('Failed to load TLDs: ' .. response:text())
end
end
local function checkAuth()
authToken = gurt.crumbs.get("auth_token")
if authToken then
print('Found auth token, checking validity...')
local response = fetch('gurt://localhost:8877/auth/me', {
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
user = response:json()
print('Authentication successful for user: ' .. user.username)
updateUserInfo()
loadTLDs()
else
print('Token invalid, redirecting to login...')
gurt.crumbs.delete('auth_token')
gurt.location.goto('../')
end
else
print('No auth token found, redirecting to login...')
gurt.location.goto('../')
end
end
local function logout()
gurt.crumbs.delete('auth_token')
print('Logged out successfully')
gurt.location.goto("../")
end
local function goToDashboard()
gurt.location.goto("/dashboard.html")
end
local function submitDomain(name, tld, ip)
hideError('domain-error')
print('Submitting domain: ' .. name .. '.' .. tld)
local response = fetch('gurt://localhost:8877/domain', {
method = 'POST',
headers = {
['Content-Type'] = 'application/json',
Authorization = 'Bearer ' .. authToken
},
body = JSON.stringify({ name = name, tld = tld, ip = ip })
})
if response:ok() then
print('Domain submitted successfully.')
-- Update user registrations remaining
user.registrations_remaining = user.registrations_remaining - 1
updateUserInfo()
-- Clear form
gurt.select('#domain-name').text = ''
gurt.select('#domain-ip').text = ''
-- Redirect to dashboard
gurt.location.goto('/dashboard.html')
else
local error = response:text()
showError('domain-error', 'Domain submission failed: ' .. error)
print('Domain submission failed: ' .. error)
end
end
local function createInvite()
print('Creating invite code...')
local response = fetch('gurt://localhost:8877/auth/invite', {
method = 'POST',
headers = {
Authorization = 'Bearer ' .. authToken
}
})
if response:ok() then
local data = response:json()
local inviteCode = data.invite_code
displayElement.text = 'Invite code: ' .. inviteCode .. ' (copied to clipboard)'
displayElement:show()
Clipboard.write(inviteCode)
print('Invite code created and copied to clipboard: ' .. inviteCode)
else
print('Failed to create invite: ' .. response:text())
end
end
local function redeemInvite(code)
hideError('redeem-error')
print('Redeeming invite code: ' .. code)
local response = fetch('gurt://localhost:8877/auth/redeem-invite', {
method = 'POST',
headers = {
['Content-Type'] = 'application/json',
Authorization = 'Bearer ' .. authToken
},
body = JSON.stringify({ invite_code = code })
})
if response:ok() then
local data = response:json()
print('Invite redeemed: +' .. data.registrations_added .. ' registrations')
-- Update user info
user.registrations_remaining = user.registrations_remaining + data.registrations_added
updateUserInfo()
-- Clear form
gurt.select('#invite-code-input').text = ''
else
local error = response:text()
showError('redeem-error', 'Failed to redeem invite: ' .. error)
print('Failed to redeem invite: ' .. error)
end
end
-- Event handlers
gurt.select('#logout-btn'):on('click', logout)
gurt.select('#dashboard-btn'):on('click', goToDashboard)
gurt.select('#submit-domain-btn'):on('click', function()
local name = gurt.select('#domain-name').value
local ip = gurt.select('#domain-ip').value
local selectedTLD = gurt.select('.tld-selected')
print('Submit domain button clicked')
print('Input name:', name)
print('Input IP:', ip)
print('Selected TLD element:', selectedTLD)
if not name or name == '' then
print('Validation failed: Domain name is required')
showError('domain-error', 'Domain name is required')
return
end
if not ip or ip == '' then
print('Validation failed: IP address is required')
showError('domain-error', 'IP address is required')
return
end
if not selectedTLD then
print('Validation failed: No TLD selected')
showError('domain-error', 'Please select a TLD')
return
end
local tld = selectedTLD:getAttribute('data-tld')
print('Submitting domain with name:', name, 'tld:', tld, 'ip:', ip)
submitDomain(name, tld, ip)
end)
gurt.select('#create-invite-btn'):on('click', createInvite)
gurt.select('#redeem-invite-btn'):on('click', function()
local code = gurt.select('#invite-code-input').text
if code and code ~= '' then
redeemInvite(code)
end
end)
-- Initialize
print('Register page initialized')
checkAuth()

View File

@@ -93,6 +93,7 @@ enum HandlerType {
CreateDomain, CreateDomain,
UpdateDomain, UpdateDomain,
DeleteDomain, DeleteDomain,
GetUserDomains,
} }
impl GurtHandler for AppHandler { impl GurtHandler for AppHandler {
@@ -128,6 +129,7 @@ impl GurtHandler for AppHandler {
HandlerType::RedeemInvite => handle_authenticated!(ctx, app_state, auth_routes::redeem_invite), HandlerType::RedeemInvite => handle_authenticated!(ctx, app_state, auth_routes::redeem_invite),
HandlerType::CreateDomainInvite => handle_authenticated!(ctx, app_state, auth_routes::create_domain_invite), HandlerType::CreateDomainInvite => handle_authenticated!(ctx, app_state, auth_routes::create_domain_invite),
HandlerType::RedeemDomainInvite => handle_authenticated!(ctx, app_state, auth_routes::redeem_domain_invite), HandlerType::RedeemDomainInvite => handle_authenticated!(ctx, app_state, auth_routes::redeem_domain_invite),
HandlerType::GetUserDomains => handle_authenticated!(ctx, app_state, routes::get_user_domains),
HandlerType::CreateDomain => { HandlerType::CreateDomain => {
// Check rate limit first // Check rate limit first
if let Some(ref rate_limit_state) = rate_limit_state { if let Some(ref rate_limit_state) = rate_limit_state {
@@ -205,6 +207,7 @@ pub async fn start(cli: crate::Cli) -> std::io::Result<()> {
.route(Route::post("/auth/redeem-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::RedeemInvite }) .route(Route::post("/auth/redeem-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::RedeemInvite })
.route(Route::post("/auth/create-domain-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::CreateDomainInvite }) .route(Route::post("/auth/create-domain-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::CreateDomainInvite })
.route(Route::post("/auth/redeem-domain-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::RedeemDomainInvite }) .route(Route::post("/auth/redeem-domain-invite"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::RedeemDomainInvite })
.route(Route::get("/auth/domains"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::GetUserDomains })
.route(Route::post("/domain"), AppHandler { app_state: app_state.clone(), rate_limit_state: Some(rate_limit_state), handler_type: HandlerType::CreateDomain }) .route(Route::post("/domain"), AppHandler { app_state: app_state.clone(), rate_limit_state: Some(rate_limit_state), handler_type: HandlerType::CreateDomain })
.route(Route::put("/domain/*"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::UpdateDomain }) .route(Route::put("/domain/*"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::UpdateDomain })
.route(Route::delete("/domain/*"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::DeleteDomain }); .route(Route::delete("/domain/*"), AppHandler { app_state: app_state.clone(), rate_limit_state: None, handler_type: HandlerType::DeleteDomain });

View File

@@ -101,3 +101,19 @@ pub(crate) struct DomainList {
pub(crate) domain: String, pub(crate) domain: String,
pub(crate) taken: bool, pub(crate) taken: bool,
} }
#[derive(Debug, Serialize)]
pub(crate) struct UserDomain {
pub(crate) name: String,
pub(crate) tld: String,
pub(crate) ip: String,
pub(crate) status: String,
pub(crate) denial_reason: Option<String>,
}
#[derive(Serialize)]
pub(crate) struct UserDomainResponse {
pub(crate) domains: Vec<UserDomain>,
pub(crate) page: u32,
pub(crate) limit: u32,
}

View File

@@ -39,7 +39,7 @@ pub(crate) async fn create_logic(domain: Domain, user_id: i32, app: &AppState) -
} }
let existing_count: i64 = sqlx::query_scalar( let existing_count: i64 = sqlx::query_scalar(
"SELECT COUNT(*) FROM domains WHERE name = ? AND tld = ?" "SELECT COUNT(*) FROM domains WHERE name = $1 AND tld = $2"
) )
.bind(&domain.name) .bind(&domain.name)
.bind(&domain.tld) .bind(&domain.tld)
@@ -306,6 +306,56 @@ pub(crate) async fn delete_domain(ctx: &ServerContext, app_state: AppState, clai
Ok(GurtResponse::ok().with_string_body("Domain deleted successfully")) Ok(GurtResponse::ok().with_string_body("Domain deleted successfully"))
} }
pub(crate) async fn get_user_domains(ctx: &ServerContext, app_state: AppState, claims: Claims) -> Result<GurtResponse> {
// Parse pagination from query parameters
let path = ctx.path();
let query_params = if let Some(query_start) = path.find('?') {
let query_string = &path[query_start + 1..];
parse_query_string(query_string)
} else {
HashMap::new()
};
let page = query_params.get("page")
.and_then(|p| p.parse::<u32>().ok())
.unwrap_or(1)
.max(1);
let page_size = query_params.get("limit")
.and_then(|l| l.parse::<u32>().ok())
.unwrap_or(100)
.clamp(1, 1000);
let offset = (page - 1) * page_size;
let domains: Vec<Domain> = sqlx::query_as::<_, Domain>(
"SELECT id, name, tld, ip, user_id, status, denial_reason, created_at FROM domains WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3"
)
.bind(claims.user_id)
.bind(page_size as i64)
.bind(offset as i64)
.fetch_all(&app_state.db)
.await
.map_err(|_| GurtError::invalid_message("Database error"))?;
let response_domains: Vec<UserDomain> = domains.into_iter().map(|domain| {
UserDomain {
name: domain.name,
tld: domain.tld,
ip: domain.ip,
status: domain.status.unwrap_or_else(|| "pending".to_string()),
denial_reason: domain.denial_reason,
}
}).collect();
let response = UserDomainResponse {
domains: response_domains,
page,
limit: page_size,
};
Ok(GurtResponse::ok().with_json_body(&response)?)
}
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
struct Error { struct Error {

View File

@@ -66,77 +66,55 @@ SpinBox/styles/down_background_pressed = SubResource("StyleBoxEmpty_xxc4c")
SpinBox/styles/up_background_hovered = SubResource("StyleBoxEmpty_unki5") SpinBox/styles/up_background_hovered = SubResource("StyleBoxEmpty_unki5")
SpinBox/styles/up_background_pressed = SubResource("StyleBoxEmpty_3wmat") SpinBox/styles/up_background_pressed = SubResource("StyleBoxEmpty_3wmat")
[node name="input" type="Control"] [node name="VBoxContainer" type="VBoxContainer"]
layout_mode = 3 offset_right = 40.0
anchors_preset = 15 offset_bottom = 40.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -1791.0
offset_bottom = -996.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 0
script = ExtResource("1_input") script = ExtResource("1_input")
[node name="LineEdit" type="LineEdit" parent="."] [node name="LineEdit" type="LineEdit" parent="."]
visible = false visible = false
layout_mode = 1 layout_mode = 2
offset_right = 200.0
offset_bottom = 35.0
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
placeholder_text = "Enter text..." placeholder_text = "Enter text..."
caret_blink = true caret_blink = true
[node name="CheckBox" type="CheckBox" parent="."] [node name="CheckBox" type="CheckBox" parent="."]
visible = false visible = false
layout_mode = 0 layout_mode = 2
offset_right = 31.0
offset_bottom = 31.0
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
flat = true flat = true
[node name="RadioButton" type="CheckBox" parent="."] [node name="RadioButton" type="CheckBox" parent="."]
visible = false visible = false
layout_mode = 0 layout_mode = 2
offset_right = 31.0
offset_bottom = 31.0
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
theme_override_constants/icon_max_width = 24 theme_override_constants/icon_max_width = 24
button_group = SubResource("ButtonGroup_06us3") button_group = SubResource("ButtonGroup_06us3")
flat = true flat = true
[node name="ColorPickerButton" type="ColorPickerButton" parent="."] [node name="ColorPickerButton" type="ColorPickerButton" parent="."]
layout_mode = 0 layout_mode = 2
offset_right = 83.0
offset_bottom = 35.0
toggle_mode = false toggle_mode = false
[node name="DateButton" parent="." instance=ExtResource("3_a88g6")] [node name="DateButton" parent="." instance=ExtResource("3_a88g6")]
visible = false visible = false
layout_mode = 0 layout_mode = 2
[node name="HSlider" type="HSlider" parent="."] [node name="HSlider" type="HSlider" parent="."]
visible = false visible = false
layout_mode = 0 layout_mode = 2
offset_right = 200.0
offset_bottom = 35.0
theme = ExtResource("2_theme") theme = ExtResource("2_theme")
value = 50.0 value = 50.0
[node name="SpinBox" type="SpinBox" parent="."] [node name="SpinBox" type="SpinBox" parent="."]
visible = false visible = false
layout_mode = 0 layout_mode = 2
offset_right = 94.5625
offset_bottom = 35.0
theme = SubResource("Theme_poopw") theme = SubResource("Theme_poopw")
min_value = -99999.0 min_value = -99999.0
max_value = 99999.0 max_value = 99999.0
[node name="FileContainer" type="HBoxContainer" parent="."] [node name="FileContainer" type="HBoxContainer" parent="."]
layout_mode = 0 layout_mode = 2
offset_right = 300.0
offset_bottom = 35.0
[node name="FileButton" type="Button" parent="FileContainer"] [node name="FileButton" type="Button" parent="FileContainer"]
custom_minimum_size = Vector2(100, 0) custom_minimum_size = Vector2(100, 0)
@@ -159,5 +137,3 @@ ok_button_text = "Open"
file_mode = 0 file_mode = 0
access = 2 access = 2
use_native_dialog = true use_native_dialog = true
[connection signal="popup_closed" from="ColorPickerButton" to="." method="_on_color_picker_popup_closed"]

View File

@@ -88,7 +88,7 @@ func _resort() -> void:
if not auto_size_width: if not auto_size_width:
available_width = calculate_available_dimension(true) available_width = calculate_available_dimension(true)
elif flex_wrap == FlexContainer.FlexWrap.Wrap: elif flex_wrap != FlexContainer.FlexWrap.NoWrap:
available_width = get_parent_or_fallback_size(true) available_width = get_parent_or_fallback_size(true)
if not auto_size_height: if not auto_size_height:

View File

@@ -170,7 +170,7 @@ func get_element_styles_with_inheritance(element: HTMLElement, event: String = "
styles[property] = inline_parsed[property] styles[property] = inline_parsed[property]
# Inherit certain properties from parent elements # Inherit certain properties from parent elements
var inheritable_properties = ["width", "height", "font-size", "color", "font-family", "cursor", "font-bold", "font-italic", "underline"] var inheritable_properties = ["font-size", "color", "font-family", "cursor", "font-bold", "font-italic", "underline"]
var parent_element = element.parent var parent_element = element.parent
while parent_element: while parent_element:
var parent_styles = get_element_styles_internal(parent_element, event) var parent_styles = get_element_styles_internal(parent_element, event)

View File

@@ -663,10 +663,7 @@ func _handle_text_setting(operation: Dictionary):
var text_node = get_dom_node(dom_node, "text") var text_node = get_dom_node(dom_node, "text")
if text_node: if text_node:
if text_node is RichTextLabel: if text_node is RichTextLabel:
var formatted_text = element.get_bbcode_formatted_text(dom_parser) StyleManager.apply_styles_to_label(text_node, dom_parser.get_element_styles_with_inheritance(element, "", []), element, dom_parser, text)
formatted_text = "[font_size=24]%s[/font_size]" % formatted_text
text_node.text = formatted_text
text_node.call_deferred("_auto_resize_to_content") text_node.call_deferred("_auto_resize_to_content")
elif text_node.has_method("set_text"): elif text_node.has_method("set_text"):
text_node.set_text(text) text_node.set_text(text)
@@ -677,10 +674,7 @@ func _handle_text_setting(operation: Dictionary):
else: else:
var rich_text_label = _find_rich_text_label_recursive(dom_node) var rich_text_label = _find_rich_text_label_recursive(dom_node)
if rich_text_label: if rich_text_label:
var formatted_text = element.get_bbcode_formatted_text(dom_parser) StyleManager.apply_styles_to_label(rich_text_label, dom_parser.get_element_styles_with_inheritance(element, "", []), element, dom_parser, text)
formatted_text = "[font_size=24]%s[/font_size]" % formatted_text
rich_text_label.text = formatted_text
rich_text_label.call_deferred("_auto_resize_to_content") rich_text_label.call_deferred("_auto_resize_to_content")
func _find_rich_text_label_recursive(node: Node) -> RichTextLabel: func _find_rich_text_label_recursive(node: Node) -> RichTextLabel:

View File

@@ -60,55 +60,24 @@ static func apply_element_styles(node: Control, element: HTMLParser.HTMLElement,
if styles.has("height"): if styles.has("height"):
height = parse_size(styles["height"]) height = parse_size(styles["height"])
# Skip width/height inheritance for buttons when inheriting from auto-sized containers
var skip_sizing = SizingUtils.should_skip_sizing(node, element, parser) var skip_sizing = SizingUtils.should_skip_sizing(node, element, parser)
if (width != null or height != null) and not skip_sizing: if (width != null or height != null) and not skip_sizing:
# FlexContainers handle percentage sizing differently than regular controls if width != null:
if node is FlexContainer: if width is String and width == "100%":
if width != null:
if SizingUtils.is_percentage(width):
# For FlexContainers with percentage width, use proportion sizing
var percentage_value = float(width.replace("%", "")) / 100.0
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
node.size_flags_stretch_ratio = percentage_value
else:
node.custom_minimum_size.x = width
node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
node.set_meta("size_flags_horizontal_set", true)
if height != null:
if SizingUtils.is_percentage(height):
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
node.set_meta("size_flags_vertical_set", true)
node.set_meta("size_flags_set_by_style_manager", true)
elif node is VBoxContainer or node is HBoxContainer or node is Container:
# Hcontainer nodes (like ul, ol)
SizingUtils.apply_container_dimension_sizing(node, width, height, styles)
elif node is HTMLP:
# Only apply sizing if element has explicit size, otherwise preserve natural sizing
var element_styles = parser.get_element_styles_internal(element, "")
if element_styles.has("width") or element_styles.has("height"):
var orig_h_flag = node.size_flags_horizontal
var orig_v_flag = node.size_flags_vertical
SizingUtils.apply_regular_control_sizing(node, width, height, styles)
if not element_styles.has("width"):
node.size_flags_horizontal = orig_h_flag
if not element_styles.has("height"):
node.size_flags_vertical = orig_v_flag
else:
if element.tag_name == "img" and SizingUtils.is_percentage(width) and SizingUtils.is_percentage(height):
node.size_flags_horizontal = Control.SIZE_EXPAND_FILL node.size_flags_horizontal = Control.SIZE_EXPAND_FILL
node.size_flags_vertical = Control.SIZE_EXPAND_FILL node.custom_minimum_size.x = 0
# Clear any hardcoded sizing
node.custom_minimum_size = Vector2.ZERO
else: else:
# regular controls node.custom_minimum_size.x = width
SizingUtils.apply_regular_control_sizing(node, width, height, styles) node.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
if height != null:
if height is String and height == "100%":
node.size_flags_vertical = Control.SIZE_EXPAND_FILL
node.custom_minimum_size.y = 0
else:
node.custom_minimum_size.y = height
node.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
apply_element_centering(node, styles) apply_element_centering(node, styles)
@@ -539,7 +508,7 @@ static func apply_body_styles(body: HTMLParser.HTMLElement, parser: HTMLParser,
original_parent.move_child(margin_container, container_index) original_parent.move_child(margin_container, container_index)
margin_container.add_child(website_container) margin_container.add_child(website_container)
var padding_val = parse_size(styles["padding"]) var padding_val = parse_size(styles["padding"] if styles.has("padding") else 0)
margin_container.add_theme_constant_override("margin_left", padding_val) margin_container.add_theme_constant_override("margin_left", padding_val)
margin_container.add_theme_constant_override("margin_right", padding_val) margin_container.add_theme_constant_override("margin_right", padding_val)

View File

@@ -61,7 +61,7 @@ static func apply_flex_container_properties(node, styles: Dictionary) -> void:
if styles.has("width"): if styles.has("width"):
var width_val = styles["width"] var width_val = styles["width"]
if width_val == "full": if width_val == "full" or width_val == "100%":
# For flex containers, w-full should expand to fill parent # For flex containers, w-full should expand to fill parent
node.set_meta("should_fill_horizontal", true) node.set_meta("should_fill_horizontal", true)
elif typeof(width_val) == TYPE_STRING and width_val.ends_with("%"): elif typeof(width_val) == TYPE_STRING and width_val.ends_with("%"):
@@ -79,6 +79,7 @@ static func apply_flex_container_properties(node, styles: Dictionary) -> void:
node.set_meta("custom_css_height", SizingUtils.parse_size_value(height_val)) node.set_meta("custom_css_height", SizingUtils.parse_size_value(height_val))
if styles.has("background-color"): if styles.has("background-color"):
node.set_meta("custom_css_background_color", styles["background-color"]) node.set_meta("custom_css_background_color", styles["background-color"])
node.update_layout() node.update_layout()
static func apply_flex_item_properties(node: Control, styles: Dictionary) -> void: static func apply_flex_item_properties(node: Control, styles: Dictionary) -> void:

View File

@@ -290,6 +290,84 @@ static func render_new_element(element: HTMLParser.HTMLElement, parent_node: Nod
main_scene.safe_add_child(container_node, element_node) main_scene.safe_add_child(container_node, element_node)
static func _get_input_value(element: HTMLParser.HTMLElement, dom_node: Node) -> Variant:
var input_type = element.get_attribute("type").to_lower()
match input_type:
"checkbox", "radio":
if dom_node is CheckBox:
return dom_node.button_pressed
return false
"color":
if dom_node is ColorPickerButton:
return "#" + dom_node.color.to_html(false)
return "#ffffff"
"range":
if dom_node is HSlider:
return dom_node.value
return 0.0
"number":
if dom_node is SpinBox:
return dom_node.value
return 0.0
"file":
# For file inputs, need to find the input control that has get_file_info method
var input_control = _find_input_control_with_file_info(dom_node)
if input_control:
var file_info = input_control.get_file_info()
return file_info.get("fileName", "")
return ""
"date":
if dom_node is DateButton:
if dom_node.has_method("get_date_text"):
return dom_node.get_date_text()
return ""
_: # text, password, email, etc.
if dom_node is LineEdit:
return dom_node.text
elif dom_node is TextEdit:
return dom_node.text
return ""
static func _find_input_control_with_file_info(node: Node) -> Node:
if node and node.has_method("get_file_info"):
return node
var current = node
while current:
current = current.get_parent()
if current and current.has_method("get_file_info"):
return current
return null
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)
"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)
"range":
if dom_node is HSlider:
dom_node.value = float(value)
"number":
if dom_node is SpinBox:
dom_node.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))
_: # text, password, email, etc.
if dom_node is LineEdit:
dom_node.text = str(value)
elif dom_node is TextEdit:
dom_node.text = str(value)
# Helper functions # Helper functions
static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement: static func find_element_by_id(element_id: String, dom_parser: HTMLParser) -> HTMLParser.HTMLElement:
if element_id == "body": if element_id == "body":
@@ -852,6 +930,23 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
return LuaAudioUtils.handle_dom_audio_index(vm, element_id, key) return LuaAudioUtils.handle_dom_audio_index(vm, element_id, key)
match key: match key:
"value":
if lua_api and tag_name == "input":
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
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))
return 1
# Fallback to empty string
vm.lua_pushstring("")
return 1
"text": "text":
if lua_api: if lua_api:
# Get element ID and find the element # Get element ID and find the element
@@ -929,12 +1024,10 @@ static func _element_index_wrapper(vm: LuauVM) -> int:
return 1 return 1
static func _add_classlist_support(vm: LuauVM, lua_api: LuaAPI) -> void: static func _add_classlist_support(vm: LuauVM, lua_api: LuaAPI) -> void:
# Create classList table with threaded methods
vm.lua_newtable() vm.lua_newtable()
# Store the element_id in the classList table vm.lua_getfield(-2, "_element_id")
vm.lua_getfield(-2, "_element_id") # Get element_id from parent element vm.lua_setfield(-2, "_element_id")
vm.lua_setfield(-2, "_element_id") # Store it in classList table
# Add classList methods # Add classList methods
vm.lua_pushcallable(LuaDOMUtils._classlist_add_wrapper, "classList.add") vm.lua_pushcallable(LuaDOMUtils._classlist_add_wrapper, "classList.add")
@@ -961,12 +1054,11 @@ static func _add_classlist_support(vm: LuauVM, lua_api: LuaAPI) -> void:
vm.lua_setfield(-2, "classList") vm.lua_setfield(-2, "classList")
static func _classlist_add_wrapper(vm: LuauVM) -> int: static func _classlist_add_wrapper(vm: LuauVM) -> int:
# Get lua_api from VM metadata
var lua_api = vm.get_meta("lua_api") as LuaAPI var lua_api = vm.get_meta("lua_api") as LuaAPI
if not lua_api: if not lua_api:
return 0 return 0
vm.luaL_checktype(1, vm.LUA_TTABLE) # classList table vm.luaL_checktype(1, vm.LUA_TTABLE)
var cls: String = vm.luaL_checkstring(2) var cls: String = vm.luaL_checkstring(2)
# Get element_id from classList table # Get element_id from classList table
@@ -1132,6 +1224,21 @@ static func _element_newindex_wrapper(vm: LuauVM) -> int:
return LuaAudioUtils.handle_dom_audio_newindex(vm, element_id, key, value) return LuaAudioUtils.handle_dom_audio_newindex(vm, element_id, key, value)
match key: match key:
"value":
if tag_name == "input":
vm.lua_getfield(1, "_element_id")
var element_id: String = vm.lua_tostring(-1)
vm.lua_pop(1)
var element = lua_api.dom_parser.find_by_id(element_id) if element_id != "body" else lua_api.dom_parser.find_first("body")
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)
return 0
"text": "text":
var text: String = str(value) # Convert value to string var text: String = str(value) # Convert value to string

View File

@@ -53,18 +53,6 @@ func _ready():
ProjectSettings.set_setting("display/window/size/min_width", MIN_SIZE.x) ProjectSettings.set_setting("display/window/size/min_width", MIN_SIZE.x)
ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y) ProjectSettings.set_setting("display/window/size/min_height", MIN_SIZE.y)
DisplayServer.window_set_min_size(MIN_SIZE) DisplayServer.window_set_min_size(MIN_SIZE)
get_viewport().size_changed.connect(_on_viewport_size_changed)
func _on_viewport_size_changed():
recalculate_percentage_elements(website_container)
func recalculate_percentage_elements(node: Node):
if node is Control and node.has_meta("needs_percentage_recalc"):
SizingUtils.apply_container_percentage_sizing(node)
for child in node.get_children():
recalculate_percentage_elements(child)
var current_domain = "" # Store current domain for display var current_domain = "" # Store current domain for display
@@ -267,9 +255,6 @@ static func safe_add_child(parent: Node, child: Node) -> void:
child.get_parent().remove_child(child) child.get_parent().remove_child(child)
parent.add_child(child) parent.add_child(child)
if child.has_meta("container_percentage_width") or child.has_meta("container_percentage_height"):
SizingUtils.apply_container_percentage_sizing(child)
child.set_meta("needs_percentage_recalc", true)
func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool: func contains_hyperlink(element: HTMLParser.HTMLElement) -> bool:
if element.tag_name == "a": if element.tag_name == "a":
@@ -290,6 +275,7 @@ func is_text_only_element(element: HTMLParser.HTMLElement) -> bool:
func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control: func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) -> Control:
var styles = parser.get_element_styles_with_inheritance(element, "", []) var styles = parser.get_element_styles_with_inheritance(element, "", [])
var hover_styles = parser.get_element_styles_with_inheritance(element, "hover", [])
var is_flex_container = styles.has("display") and ("flex" in styles["display"]) var is_flex_container = styles.has("display") and ("flex" in styles["display"])
var final_node: Control var final_node: Control
@@ -308,10 +294,22 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
if is_flex_container: if is_flex_container:
# The element's primary identity IS a flex container. # The element's primary identity IS a flex container.
# We create it directly. if element.tag_name == "div":
final_node = AUTO_SIZING_FLEX_CONTAINER.new() if BackgroundUtils.needs_background_wrapper(styles) or hover_styles.size() > 0:
final_node.name = "Flex_" + element.tag_name final_node = BackgroundUtils.create_panel_container_with_background(styles, hover_styles)
container_for_children = final_node var flex_container = AUTO_SIZING_FLEX_CONTAINER.new()
flex_container.name = "Flex_" + element.tag_name
var vbox = final_node.get_child(0) as VBoxContainer
vbox.add_child(flex_container)
container_for_children = flex_container
else:
final_node = AUTO_SIZING_FLEX_CONTAINER.new()
final_node.name = "Flex_" + element.tag_name
container_for_children = final_node
else:
final_node = AUTO_SIZING_FLEX_CONTAINER.new()
final_node.name = "Flex_" + element.tag_name
container_for_children = final_node
# For FLEX ul/ol elements, we need to create the li children directly in the flex container # For FLEX ul/ol elements, we need to create the li children directly in the flex container
if element.tag_name == "ul" or element.tag_name == "ol": if element.tag_name == "ul" or element.tag_name == "ol":
@@ -353,11 +351,20 @@ func create_element_node(element: HTMLParser.HTMLElement, parser: HTMLParser) ->
# Apply flex CONTAINER properties if it's a flex container # Apply flex CONTAINER properties if it's a flex container
if is_flex_container: if is_flex_container:
var flex_container_node = final_node var flex_container_node = final_node
# If the node was wrapped in a MarginContainer, get the inner FlexContainer
if final_node is MarginContainer and final_node.get_child_count() > 0: if final_node is FlexContainer:
# Direct FlexContainer
flex_container_node = final_node
elif final_node is MarginContainer and final_node.get_child_count() > 0:
var first_child = final_node.get_child(0) var first_child = final_node.get_child(0)
if first_child is FlexContainer: if first_child is FlexContainer:
flex_container_node = first_child flex_container_node = first_child
elif final_node is PanelContainer and final_node.get_child_count() > 0:
var vbox = final_node.get_child(0)
if vbox is VBoxContainer and vbox.get_child_count() > 0:
var potential_flex = vbox.get_child(0)
if potential_flex is FlexContainer:
flex_container_node = potential_flex
if flex_container_node is FlexContainer: if flex_container_node is FlexContainer:
FlexUtils.apply_flex_container_properties(flex_container_node, styles) FlexUtils.apply_flex_container_properties(flex_container_node, styles)
@@ -476,7 +483,7 @@ func create_element_node_internal(element: HTMLParser.HTMLElement, parser: HTMLP
var hover_styles = parser.get_element_styles_with_inheritance(element, "hover", []) var hover_styles = parser.get_element_styles_with_inheritance(element, "hover", [])
var is_flex_container = styles.has("display") and ("flex" in styles["display"]) var is_flex_container = styles.has("display") and ("flex" in styles["display"])
# For flex divs, don't create div scene - the AutoSizingFlexContainer handles it # For flex divs, let the general flex container logic handle them
if is_flex_container: if is_flex_container:
return null return null