external resource loading, window.location, tab icon fix
This commit is contained in:
4
dns/Cargo.lock
generated
4
dns/Cargo.lock
generated
@@ -1560,9 +1560,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "macros-rs"
|
name = "macros-rs"
|
||||||
version = "1.2.1"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb8136cf4bdbfd10cdf683dab195f53892cb3b6433397ef48b211556b9c49dcb"
|
checksum = "9cfca1250b52a785fbe49de29612471f59592b6b659159dcfcb976af08c803b4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|||||||
10
dns/frontend/dashboard.html
Normal file
10
dns/frontend/dashboard.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<head>
|
||||||
|
<title>Login</title>
|
||||||
|
<icon src="https://cdn-icons-png.flaticon.com/512/295/295128.png">
|
||||||
|
<meta name="theme-color" content="#1b1b1b">
|
||||||
|
<meta name="description" content="Login to your account">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>todo</h1>
|
||||||
|
</body>
|
||||||
@@ -38,56 +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>
|
<script src="script.lua" />
|
||||||
local submitBtn = gurt.select('#submit')
|
|
||||||
local username_input = gurt.select('#username')
|
|
||||||
local password_input = gurt.select('#password')
|
|
||||||
local log_output = gurt.select('#log-output')
|
|
||||||
|
|
||||||
function addLog(message)
|
|
||||||
gurt.log(message)
|
|
||||||
log_output.text = log_output.text .. message .. '\\n'
|
|
||||||
end
|
|
||||||
|
|
||||||
submitBtn:on('submit', function(event)
|
|
||||||
local username = event.data.username
|
|
||||||
local password = event.data.password
|
|
||||||
|
|
||||||
local request_body = JSON.stringify({
|
|
||||||
username = username,
|
|
||||||
password = password
|
|
||||||
})
|
|
||||||
print(request_body)
|
|
||||||
local url = 'http://localhost:8080/auth/login'
|
|
||||||
local headers = {
|
|
||||||
['Content-Type'] = 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
addLog('Attempting to log in with username: ' .. username)
|
|
||||||
log_output.text = ''
|
|
||||||
|
|
||||||
local response = fetch(url, {
|
|
||||||
method = 'POST',
|
|
||||||
headers = headers,
|
|
||||||
body = request_body
|
|
||||||
})
|
|
||||||
|
|
||||||
addLog('Response Status: ' .. response.status .. ' ' .. response.statusText)
|
|
||||||
|
|
||||||
if response:ok() then
|
|
||||||
addLog('Login successful!')
|
|
||||||
local jsonData = response:json()
|
|
||||||
if jsonData then
|
|
||||||
addLog('Logged in as user: ' .. jsonData.user.username)
|
|
||||||
addLog('Token: ' .. jsonData.token:sub(1, 20) .. '...')
|
|
||||||
end
|
|
||||||
else
|
|
||||||
addLog('Request failed with status: ' .. response.status)
|
|
||||||
local error_data = response:text()
|
|
||||||
addLog('Error response: ' .. error_data)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
52
dns/frontend/script.lua
Normal file
52
dns/frontend/script.lua
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
local submitBtn = gurt.select('#submit')
|
||||||
|
local username_input = gurt.select('#username')
|
||||||
|
local password_input = gurt.select('#password')
|
||||||
|
local log_output = gurt.select('#log-output')
|
||||||
|
|
||||||
|
function addLog(message)
|
||||||
|
gurt.log(message)
|
||||||
|
log_output.text = log_output.text .. message .. '\\n'
|
||||||
|
end
|
||||||
|
|
||||||
|
print(gurt.location.href)
|
||||||
|
submitBtn:on('submit', function(event)
|
||||||
|
local username = event.data.username
|
||||||
|
local password = event.data.password
|
||||||
|
|
||||||
|
local request_body = JSON.stringify({
|
||||||
|
username = username,
|
||||||
|
password = password
|
||||||
|
})
|
||||||
|
print(request_body)
|
||||||
|
local url = 'http://localhost:8080/auth/login'
|
||||||
|
local headers = {
|
||||||
|
['Content-Type'] = 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
addLog('Attempting to log in with username: ' .. username)
|
||||||
|
log_output.text = ''
|
||||||
|
|
||||||
|
local response = fetch(url, {
|
||||||
|
method = 'POST',
|
||||||
|
headers = headers,
|
||||||
|
body = request_body
|
||||||
|
})
|
||||||
|
|
||||||
|
addLog('Response Status: ' .. response.status .. ' ' .. response.statusText)
|
||||||
|
|
||||||
|
if response:ok() then
|
||||||
|
addLog('Login successful!')
|
||||||
|
local jsonData = response:json()
|
||||||
|
if jsonData then
|
||||||
|
addLog('Logged in as user: ' .. jsonData.user.username)
|
||||||
|
addLog('Token: ' .. jsonData.token:sub(1, 20) .. '...')
|
||||||
|
|
||||||
|
-- TODO: store as cookie
|
||||||
|
gurt.location.goto("/dashboard.html")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
addLog('Request failed with status: ' .. response.status)
|
||||||
|
local error_data = response:text()
|
||||||
|
addLog('Error response: ' .. error_data)
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -1,21 +1,38 @@
|
|||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
username VARCHAR(50) UNIQUE NOT NULL,
|
username VARCHAR(50) UNIQUE NOT NULL,
|
||||||
email VARCHAR(255) UNIQUE NOT NULL,
|
|
||||||
password_hash VARCHAR(255) NOT NULL,
|
password_hash VARCHAR(255) NOT NULL,
|
||||||
registrations_remaining INTEGER DEFAULT 3,
|
registrations_remaining INTEGER DEFAULT 3,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
domain_invite_codes INTEGER DEFAULT 3,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS invite_codes (
|
CREATE TABLE IF NOT EXISTS invite_codes (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
code VARCHAR(32) UNIQUE NOT NULL,
|
code VARCHAR(32) UNIQUE NOT NULL,
|
||||||
created_by INTEGER REFERENCES users(id),
|
created_by INTEGER REFERENCES users(id),
|
||||||
used_by INTEGER REFERENCES users(id),
|
used_by INTEGER REFERENCES users(id),
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
used_at TIMESTAMP
|
used_at TIMESTAMPTZ
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invite_codes_code ON invite_codes(code);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS domain_invite_codes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
code VARCHAR(32) UNIQUE NOT NULL,
|
||||||
|
created_by INTEGER REFERENCES users(id),
|
||||||
|
used_by INTEGER REFERENCES users(id),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
used_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_code ON domain_invite_codes(code);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_created_by ON domain_invite_codes(created_by);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_used_by ON domain_invite_codes(used_by);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS domains (
|
CREATE TABLE IF NOT EXISTS domains (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
name VARCHAR(100) NOT NULL,
|
name VARCHAR(100) NOT NULL,
|
||||||
@@ -24,13 +41,24 @@ CREATE TABLE IF NOT EXISTS domains (
|
|||||||
user_id INTEGER REFERENCES users(id),
|
user_id INTEGER REFERENCES users(id),
|
||||||
status VARCHAR(20) DEFAULT 'pending',
|
status VARCHAR(20) DEFAULT 'pending',
|
||||||
denial_reason TEXT,
|
denial_reason TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE(name, tld)
|
UNIQUE(name, tld)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_domains_name_tld ON domains(name, tld);
|
CREATE INDEX IF NOT EXISTS idx_domains_name_tld ON domains(name, tld);
|
||||||
CREATE INDEX IF NOT EXISTS idx_domains_user_id ON domains(user_id);
|
CREATE INDEX IF NOT EXISTS idx_domains_user_id ON domains(user_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_domains_status ON domains(status);
|
CREATE INDEX IF NOT EXISTS idx_domains_status ON domains(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
CREATE TABLE IF NOT EXISTS dns_records (
|
||||||
CREATE INDEX IF NOT EXISTS idx_invite_codes_code ON invite_codes(code);
|
id SERIAL PRIMARY KEY,
|
||||||
|
domain_id INTEGER NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
|
||||||
|
record_type VARCHAR(10) NOT NULL CHECK (record_type IN ('A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SRV')),
|
||||||
|
name VARCHAR(255) NOT NULL DEFAULT '@', -- @ for root, or subdomain name
|
||||||
|
value VARCHAR(1000) NOT NULL,
|
||||||
|
ttl INTEGER DEFAULT 3600,
|
||||||
|
priority INTEGER, -- For MX records
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dns_records_domain_type ON dns_records(domain_id, record_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_dns_records_name ON dns_records(name);
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
ALTER TABLE users DROP COLUMN IF EXISTS email;
|
|
||||||
|
|
||||||
DROP INDEX IF EXISTS idx_users_email;
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
ALTER TABLE users ALTER COLUMN created_at TYPE TIMESTAMPTZ;
|
|
||||||
ALTER TABLE invite_codes ALTER COLUMN created_at TYPE TIMESTAMPTZ;
|
|
||||||
ALTER TABLE invite_codes ALTER COLUMN used_at TYPE TIMESTAMPTZ;
|
|
||||||
ALTER TABLE domains ALTER COLUMN created_at TYPE TIMESTAMPTZ;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
ALTER TABLE users ADD COLUMN domain_invite_codes INTEGER DEFAULT 3;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS domain_invite_codes (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
code VARCHAR(32) UNIQUE NOT NULL,
|
|
||||||
created_by INTEGER REFERENCES users(id),
|
|
||||||
used_by INTEGER REFERENCES users(id),
|
|
||||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
used_at TIMESTAMPTZ
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_code ON domain_invite_codes(code);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_created_by ON domain_invite_codes(created_by);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_domain_invite_codes_used_by ON domain_invite_codes(used_by);
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
CREATE TABLE dns_records (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
domain_id INTEGER NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
|
|
||||||
record_type VARCHAR(10) NOT NULL CHECK (record_type IN ('A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SRV')),
|
|
||||||
name VARCHAR(255) NOT NULL DEFAULT '@', -- @ for root, or subdomain name
|
|
||||||
value VARCHAR(1000) NOT NULL,
|
|
||||||
ttl INTEGER DEFAULT 3600,
|
|
||||||
priority INTEGER, -- For MX records
|
|
||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX idx_dns_records_domain_type ON dns_records(domain_id, record_type);
|
|
||||||
CREATE INDEX idx_dns_records_name ON dns_records(name);
|
|
||||||
|
|
||||||
INSERT INTO dns_records (domain_id, record_type, name, value, ttl)
|
|
||||||
SELECT id, 'A', '@', ip, 3600
|
|
||||||
FROM domains
|
|
||||||
WHERE status = 'approved';
|
|
||||||
|
|
||||||
INSERT INTO dns_records (domain_id, record_type, name, value, ttl, priority)
|
|
||||||
SELECT id, 'SRV', '_gurt._tcp', '0 5 4878 @', 3600, 0
|
|
||||||
FROM domains
|
|
||||||
WHERE status = 'approved';
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use serenity::async_trait;
|
use serenity::async_trait;
|
||||||
use serenity::all::*;
|
use serenity::all::*;
|
||||||
use serenity::prelude::*;
|
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
pub struct DiscordBot {
|
pub struct DiscordBot {
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ mod routes;
|
|||||||
|
|
||||||
use crate::{auth::jwt_middleware, config::Config, discord_bot};
|
use crate::{auth::jwt_middleware, config::Config, discord_bot};
|
||||||
use actix_governor::{Governor, GovernorConfigBuilder};
|
use actix_governor::{Governor, GovernorConfigBuilder};
|
||||||
use actix_web::{http::Method, web, web::Data, App, HttpRequest, HttpServer};
|
use actix_web::{http::Method, web, web::Data, App, HttpServer};
|
||||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
use anyhow::{anyhow, Error};
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use macros_rs::fmt::{crashln, string};
|
use macros_rs::fmt::{crashln, string};
|
||||||
use ratelimit::RealIpKeyExtractor;
|
use ratelimit::RealIpKeyExtractor;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use super::helpers::deserialize_lowercase;
|
use super::helpers::deserialize_lowercase;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, types::chrono::{DateTime, Utc}};
|
use sqlx::{FromRow, types::chrono::{DateTime, Utc}};
|
||||||
use chrono;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, FromRow)]
|
#[derive(Clone, Debug, Deserialize, Serialize, FromRow)]
|
||||||
pub struct Domain {
|
pub struct Domain {
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ pub(crate) async fn get_domain(path: web::Path<(String, String)>, app: Data<AppS
|
|||||||
tld: domain.tld,
|
tld: domain.tld,
|
||||||
name: domain.name,
|
name: domain.name,
|
||||||
ip: domain.ip,
|
ip: domain.ip,
|
||||||
|
records: None,
|
||||||
}),
|
}),
|
||||||
Ok(None) => HttpResponse::NotFound().finish(),
|
Ok(None) => HttpResponse::NotFound().finish(),
|
||||||
Err(_) => HttpResponse::InternalServerError().finish(),
|
Err(_) => HttpResponse::InternalServerError().finish(),
|
||||||
@@ -378,6 +379,7 @@ pub(crate) async fn get_domains(query: web::Query<PaginationParams>, app: Data<A
|
|||||||
tld: domain.tld,
|
tld: domain.tld,
|
||||||
name: domain.name,
|
name: domain.name,
|
||||||
ip: domain.ip,
|
ip: domain.ip,
|
||||||
|
records: None,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ class ParseResult:
|
|||||||
var css_parser: CSSParser = null
|
var css_parser: CSSParser = null
|
||||||
var inline_styles: Dictionary = {}
|
var inline_styles: Dictionary = {}
|
||||||
var dom_nodes: Dictionary = {}
|
var dom_nodes: Dictionary = {}
|
||||||
|
var external_css: Array[String] = []
|
||||||
|
var external_scripts: Array[String] = []
|
||||||
|
|
||||||
func _init():
|
func _init():
|
||||||
root = HTMLElement.new("document")
|
root = HTMLElement.new("document")
|
||||||
@@ -105,7 +107,9 @@ func handle_style_element(style_element: HTMLElement) -> void:
|
|||||||
# Check if it's an external stylesheet
|
# Check if it's an external stylesheet
|
||||||
var src = style_element.get_attribute("src")
|
var src = style_element.get_attribute("src")
|
||||||
if src.length() > 0:
|
if src.length() > 0:
|
||||||
# TODO: Handle external CSS loading when Network module is available
|
if not parse_result.external_css:
|
||||||
|
parse_result.external_css = []
|
||||||
|
parse_result.external_css.append(src)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Handle inline CSS - we'll get the text content when parsing is complete
|
# Handle inline CSS - we'll get the text content when parsing is complete
|
||||||
@@ -128,6 +132,24 @@ func process_styles() -> void:
|
|||||||
parse_result.css_parser.css_text = css_content
|
parse_result.css_parser.css_text = css_content
|
||||||
parse_result.css_parser.parse()
|
parse_result.css_parser.parse()
|
||||||
|
|
||||||
|
func process_external_styles(base_url: String = "") -> void:
|
||||||
|
if not parse_result.external_css or parse_result.external_css.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
if not parse_result.css_parser:
|
||||||
|
parse_result.css_parser = CSSParser.new()
|
||||||
|
parse_result.css_parser.init()
|
||||||
|
|
||||||
|
var combined_css = parse_result.css_parser.css_text if parse_result.css_parser.css_text else Constants.DEFAULT_CSS
|
||||||
|
|
||||||
|
for css_url in parse_result.external_css:
|
||||||
|
var css_content = await Network.fetch_external_resource(css_url, base_url)
|
||||||
|
if not css_content.is_empty():
|
||||||
|
combined_css += "\n" + css_content
|
||||||
|
|
||||||
|
parse_result.css_parser.css_text = combined_css
|
||||||
|
parse_result.css_parser.parse()
|
||||||
|
|
||||||
func get_element_styles_with_inheritance(element: HTMLElement, event: String = "", visited_elements: Array = []) -> Dictionary:
|
func get_element_styles_with_inheritance(element: HTMLElement, event: String = "", visited_elements: Array = []) -> Dictionary:
|
||||||
if !parse_result.css_parser:
|
if !parse_result.css_parser:
|
||||||
return {}
|
return {}
|
||||||
@@ -362,11 +384,23 @@ func process_scripts(lua_api: LuaAPI, lua_vm) -> void:
|
|||||||
var inline_code = script_element.text_content.strip_edges()
|
var inline_code = script_element.text_content.strip_edges()
|
||||||
|
|
||||||
if not src.is_empty():
|
if not src.is_empty():
|
||||||
# TODO: add support for external Lua script
|
if not parse_result.external_scripts:
|
||||||
pass
|
parse_result.external_scripts = []
|
||||||
|
parse_result.external_scripts.append(src)
|
||||||
elif not inline_code.is_empty():
|
elif not inline_code.is_empty():
|
||||||
lua_api.execute_lua_script(inline_code, lua_vm)
|
lua_api.execute_lua_script(inline_code, lua_vm)
|
||||||
|
|
||||||
|
func process_external_scripts(lua_api: LuaAPI, lua_vm, base_url: String = "") -> void:
|
||||||
|
if not lua_api or not parse_result.external_scripts or parse_result.external_scripts.is_empty():
|
||||||
|
return
|
||||||
|
|
||||||
|
lua_api.dom_parser = self
|
||||||
|
|
||||||
|
for script_url in parse_result.external_scripts:
|
||||||
|
var script_content = await Network.fetch_external_resource(script_url, base_url)
|
||||||
|
if not script_content.is_empty():
|
||||||
|
lua_api.execute_lua_script(script_content, lua_vm)
|
||||||
|
|
||||||
func get_all_stylesheets() -> Array[String]:
|
func get_all_stylesheets() -> Array[String]:
|
||||||
return get_attribute_values("style", "src")
|
return get_attribute_values("style", "src")
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,35 @@ func _gurt_clear_interval_handler(vm: LuauVM) -> int:
|
|||||||
_ensure_timeout_manager()
|
_ensure_timeout_manager()
|
||||||
return timeout_manager.clear_interval_handler(vm)
|
return timeout_manager.clear_interval_handler(vm)
|
||||||
|
|
||||||
|
# Location API handlers
|
||||||
|
func _gurt_location_reload_handler(vm: LuauVM) -> int:
|
||||||
|
call_deferred("_reload_current_page")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
func _gurt_location_goto_handler(vm: LuauVM) -> int:
|
||||||
|
var url: String = vm.luaL_checkstring(1)
|
||||||
|
call_deferred("_navigate_to_url", url)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
func _gurt_location_get_href_handler(vm: LuauVM) -> int:
|
||||||
|
var main_node = Engine.get_main_loop().current_scene
|
||||||
|
if main_node and main_node.has_method("get_current_url"):
|
||||||
|
var current_url = main_node.get_current_url()
|
||||||
|
vm.lua_pushstring(current_url)
|
||||||
|
else:
|
||||||
|
vm.lua_pushstring("")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
func _reload_current_page():
|
||||||
|
var main_node = Engine.get_main_loop().current_scene
|
||||||
|
if main_node and main_node.has_method("reload_current_page"):
|
||||||
|
main_node.reload_current_page()
|
||||||
|
|
||||||
|
func _navigate_to_url(url: String):
|
||||||
|
var main_node = Engine.get_main_loop().current_scene
|
||||||
|
if main_node and main_node.has_method("navigate_to_url"):
|
||||||
|
main_node.navigate_to_url(url)
|
||||||
|
|
||||||
# Event system handlers
|
# Event system handlers
|
||||||
func _element_on_event_handler(vm: LuauVM) -> int:
|
func _element_on_event_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
|
|||||||
@@ -65,3 +65,60 @@ func fetch_image(url: String) -> ImageTexture:
|
|||||||
var texture = ImageTexture.create_from_image(image)
|
var texture = ImageTexture.create_from_image(image)
|
||||||
|
|
||||||
return texture
|
return texture
|
||||||
|
|
||||||
|
func fetch_text(url: String) -> String:
|
||||||
|
var http_request = HTTPRequest.new()
|
||||||
|
add_child(http_request)
|
||||||
|
|
||||||
|
if url.is_empty():
|
||||||
|
http_request.queue_free()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
var request_headers = PackedStringArray()
|
||||||
|
request_headers.append("User-Agent: " + UserAgent.get_user_agent())
|
||||||
|
|
||||||
|
var error = http_request.request(url, request_headers)
|
||||||
|
if error != OK:
|
||||||
|
print("Error making HTTP request for text resource: ", url, " Error: ", error)
|
||||||
|
http_request.queue_free()
|
||||||
|
return ""
|
||||||
|
|
||||||
|
var response = await http_request.request_completed
|
||||||
|
|
||||||
|
var result = response[0] # HTTPClient.Result
|
||||||
|
var response_code = response[1] # int
|
||||||
|
var headers = response[2] # PackedStringArray
|
||||||
|
var body = response[3] # PackedByteArray
|
||||||
|
|
||||||
|
http_request.queue_free()
|
||||||
|
|
||||||
|
if result != HTTPRequest.RESULT_SUCCESS or response_code != 200:
|
||||||
|
print("Failed to fetch text resource. URL: ", url, " Result: ", result, " Response code: ", response_code)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
return body.get_string_from_utf8()
|
||||||
|
|
||||||
|
func fetch_external_resource(url: String, base_url: String = "") -> String:
|
||||||
|
var resolved_url = URLUtils.resolve_url(base_url, url)
|
||||||
|
|
||||||
|
if resolved_url.begins_with("http://") or resolved_url.begins_with("https://"):
|
||||||
|
return await fetch_text(resolved_url)
|
||||||
|
elif resolved_url.begins_with("gurt://"):
|
||||||
|
return await fetch_gurt_resource(resolved_url)
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func fetch_gurt_resource(url: String) -> String:
|
||||||
|
if not GurtProtocol.is_gurt_domain(url):
|
||||||
|
return ""
|
||||||
|
|
||||||
|
var result = await GurtProtocol.handle_gurt_domain(url)
|
||||||
|
|
||||||
|
if result.has("error"):
|
||||||
|
print("GURT resource error: ", result.error)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if result.has("html"):
|
||||||
|
return result.html.get_string_from_utf8()
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|||||||
@@ -48,31 +48,18 @@ func set_icon(new_icon: Texture) -> void:
|
|||||||
|
|
||||||
func update_icon_from_url(icon_url: String) -> void:
|
func update_icon_from_url(icon_url: String) -> void:
|
||||||
if icon_url.is_empty():
|
if icon_url.is_empty():
|
||||||
stop_loading()
|
const GLOBE_ICON = preload("res://Assets/Icons/globe.svg")
|
||||||
|
set_icon(GLOBE_ICON)
|
||||||
return
|
return
|
||||||
|
|
||||||
const LOADER_CIRCLE = preload("res://Assets/Icons/loader-circle.svg")
|
# Load the icon in the background
|
||||||
|
|
||||||
loading_tween = create_tween()
|
|
||||||
|
|
||||||
set_icon(LOADER_CIRCLE)
|
|
||||||
|
|
||||||
loading_tween.set_loops()
|
|
||||||
|
|
||||||
icon.pivot_offset = Vector2(11.5, 11.5)
|
|
||||||
loading_tween.tween_method(func(angle):
|
|
||||||
if !is_instance_valid(icon):
|
|
||||||
if loading_tween: loading_tween.kill()
|
|
||||||
return
|
|
||||||
icon.rotation = angle
|
|
||||||
, 0.0, TAU, 1.0)
|
|
||||||
|
|
||||||
var icon_resource = await Network.fetch_image(icon_url)
|
var icon_resource = await Network.fetch_image(icon_url)
|
||||||
|
|
||||||
# Only update if tab still exists
|
if is_instance_valid(self) and icon_resource:
|
||||||
if is_instance_valid(self):
|
|
||||||
set_icon(icon_resource)
|
set_icon(icon_resource)
|
||||||
stop_loading()
|
elif is_instance_valid(self):
|
||||||
|
const GLOBE_ICON = preload("res://Assets/Icons/globe.svg")
|
||||||
|
set_icon(GLOBE_ICON)
|
||||||
|
|
||||||
func _on_button_mouse_entered() -> void:
|
func _on_button_mouse_entered() -> void:
|
||||||
mouse_over_tab = true
|
mouse_over_tab = true
|
||||||
@@ -89,16 +76,18 @@ func start_loading() -> void:
|
|||||||
|
|
||||||
stop_loading()
|
stop_loading()
|
||||||
|
|
||||||
loading_tween = create_tween()
|
|
||||||
set_icon(LOADER_CIRCLE)
|
set_icon(LOADER_CIRCLE)
|
||||||
loading_tween.set_loops()
|
|
||||||
icon.pivot_offset = Vector2(11.5, 11.5)
|
icon.pivot_offset = Vector2(11.5, 11.5)
|
||||||
loading_tween.tween_method(func(angle):
|
|
||||||
if !is_instance_valid(icon):
|
loading_tween = create_tween()
|
||||||
if loading_tween: loading_tween.kill()
|
if loading_tween:
|
||||||
return
|
loading_tween.set_loops(0)
|
||||||
icon.rotation = angle
|
loading_tween.tween_method(func(angle):
|
||||||
, 0.0, TAU, 1.0)
|
if !is_instance_valid(icon):
|
||||||
|
if loading_tween: loading_tween.kill()
|
||||||
|
return
|
||||||
|
icon.rotation = angle
|
||||||
|
, 0.0, TAU, 1.0)
|
||||||
|
|
||||||
func stop_loading() -> void:
|
func stop_loading() -> void:
|
||||||
if loading_tween:
|
if loading_tween:
|
||||||
|
|||||||
@@ -113,12 +113,12 @@ func _lua_thread_worker():
|
|||||||
lua_vm.LUA_COROUTINE_LIB, lua_vm.LUA_MATH_LIB, lua_vm.LUA_UTF8_LIB,
|
lua_vm.LUA_COROUTINE_LIB, lua_vm.LUA_MATH_LIB, lua_vm.LUA_UTF8_LIB,
|
||||||
lua_vm.LUA_TABLE_LIB, lua_vm.LUA_STRING_LIB, lua_vm.LUA_VECTOR_LIB])
|
lua_vm.LUA_TABLE_LIB, lua_vm.LUA_STRING_LIB, lua_vm.LUA_VECTOR_LIB])
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_print_handler, "print")
|
lua_vm.lua_pushcallable(_print_handler, "print")
|
||||||
lua_vm.lua_setglobal("print")
|
lua_vm.lua_setglobal("print")
|
||||||
|
|
||||||
# Setup threaded Time.sleep function
|
# Setup threaded Time.sleep function
|
||||||
lua_vm.lua_newtable()
|
lua_vm.lua_newtable()
|
||||||
lua_vm.lua_pushcallable(_threaded_time_sleep_handler, "Time.sleep")
|
lua_vm.lua_pushcallable(_time_sleep_handler, "Time.sleep")
|
||||||
lua_vm.lua_setfield(-2, "sleep")
|
lua_vm.lua_setfield(-2, "sleep")
|
||||||
lua_vm.lua_setglobal("Time")
|
lua_vm.lua_setglobal("Time")
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ func _execute_timeout_in_thread(timeout_id: int):
|
|||||||
|
|
||||||
lua_vm.lua_pop(1) # Pop the table
|
lua_vm.lua_pop(1) # Pop the table
|
||||||
|
|
||||||
func _threaded_print_handler(vm: LuauVM) -> int:
|
func _print_handler(vm: LuauVM) -> int:
|
||||||
var message_parts: Array = []
|
var message_parts: Array = []
|
||||||
var num_args = vm.lua_gettop()
|
var num_args = vm.lua_gettop()
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ func _threaded_print_handler(vm: LuauVM) -> int:
|
|||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
func _threaded_time_sleep_handler(vm: LuauVM) -> int:
|
func _time_sleep_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checknumber(1)
|
vm.luaL_checknumber(1)
|
||||||
var seconds = vm.lua_tonumber(1)
|
var seconds = vm.lua_tonumber(1)
|
||||||
|
|
||||||
@@ -268,55 +268,65 @@ func _threaded_time_sleep_handler(vm: LuauVM) -> int:
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
func _setup_threaded_gurt_api():
|
func _setup_threaded_gurt_api():
|
||||||
lua_vm.lua_pushcallable(_threaded_print_handler, "print")
|
lua_vm.lua_pushcallable(_print_handler, "print")
|
||||||
lua_vm.lua_setglobal("print")
|
lua_vm.lua_setglobal("print")
|
||||||
|
|
||||||
LuaTimeUtils.setup_time_api(lua_vm)
|
LuaTimeUtils.setup_time_api(lua_vm)
|
||||||
|
|
||||||
lua_vm.lua_getglobal("Time")
|
lua_vm.lua_getglobal("Time")
|
||||||
if not lua_vm.lua_isnil(-1):
|
if not lua_vm.lua_isnil(-1):
|
||||||
lua_vm.lua_pushcallable(_threaded_time_sleep_handler, "Time.sleep")
|
lua_vm.lua_pushcallable(_time_sleep_handler, "Time.sleep")
|
||||||
lua_vm.lua_setfield(-2, "sleep")
|
lua_vm.lua_setfield(-2, "sleep")
|
||||||
lua_vm.lua_pop(1)
|
lua_vm.lua_pop(1)
|
||||||
|
|
||||||
lua_vm.lua_newtable()
|
lua_vm.lua_newtable()
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_print_handler, "gurt.log")
|
lua_vm.lua_pushcallable(_print_handler, "gurt.log")
|
||||||
lua_vm.lua_setfield(-2, "log")
|
lua_vm.lua_setfield(-2, "log")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_gurt_select_handler, "gurt.select")
|
lua_vm.lua_pushcallable(_gurt_select_handler, "gurt.select")
|
||||||
lua_vm.lua_setfield(-2, "select")
|
lua_vm.lua_setfield(-2, "select")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_gurt_select_all_handler, "gurt.selectAll")
|
lua_vm.lua_pushcallable(_gurt_select_all_handler, "gurt.selectAll")
|
||||||
lua_vm.lua_setfield(-2, "selectAll")
|
lua_vm.lua_setfield(-2, "selectAll")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_gurt_create_handler, "gurt.create")
|
lua_vm.lua_pushcallable(_gurt_create_handler, "gurt.create")
|
||||||
lua_vm.lua_setfield(-2, "create")
|
lua_vm.lua_setfield(-2, "create")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_set_timeout_handler, "gurt.setTimeout")
|
lua_vm.lua_pushcallable(_set_timeout_handler, "gurt.setTimeout")
|
||||||
lua_vm.lua_setfield(-2, "setTimeout")
|
lua_vm.lua_setfield(-2, "setTimeout")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_clear_timeout_handler, "gurt.clearTimeout")
|
lua_vm.lua_pushcallable(lua_api._gurt_clear_timeout_handler, "gurt.clearTimeout")
|
||||||
lua_vm.lua_setfield(-2, "clearTimeout")
|
lua_vm.lua_setfield(-2, "clearTimeout")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_set_interval_handler, "gurt.setInterval")
|
lua_vm.lua_pushcallable(_set_interval_handler, "gurt.setInterval")
|
||||||
lua_vm.lua_setfield(-2, "setInterval")
|
lua_vm.lua_setfield(-2, "setInterval")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_clear_interval_handler, "gurt.clearInterval")
|
lua_vm.lua_pushcallable(lua_api._gurt_clear_interval_handler, "gurt.clearInterval")
|
||||||
lua_vm.lua_setfield(-2, "clearInterval")
|
lua_vm.lua_setfield(-2, "clearInterval")
|
||||||
|
|
||||||
# Add body element access
|
lua_vm.lua_newtable()
|
||||||
|
lua_vm.lua_pushcallable(lua_api._gurt_location_reload_handler, "gurt.location.reload")
|
||||||
|
lua_vm.lua_setfield(-2, "reload")
|
||||||
|
lua_vm.lua_pushcallable(lua_api._gurt_location_goto_handler, "gurt.location.goto")
|
||||||
|
lua_vm.lua_setfield(-2, "goto")
|
||||||
|
|
||||||
|
var current_href = get_current_href()
|
||||||
|
lua_vm.lua_pushstring(current_href)
|
||||||
|
lua_vm.lua_setfield(-2, "href")
|
||||||
|
|
||||||
|
lua_vm.lua_setfield(-2, "location")
|
||||||
|
|
||||||
var body_element = dom_parser.find_first("body")
|
var body_element = dom_parser.find_first("body")
|
||||||
if body_element:
|
if body_element:
|
||||||
LuaDOMUtils.create_element_wrapper(lua_vm, body_element, lua_api)
|
LuaDOMUtils.create_element_wrapper(lua_vm, body_element, lua_api)
|
||||||
lua_vm.lua_pushcallable(_threaded_body_on_handler, "body.on")
|
lua_vm.lua_pushcallable(_body_on_handler, "body.on")
|
||||||
lua_vm.lua_setfield(-2, "on")
|
lua_vm.lua_setfield(-2, "on")
|
||||||
lua_vm.lua_setfield(-2, "body")
|
lua_vm.lua_setfield(-2, "body")
|
||||||
|
|
||||||
lua_vm.lua_setglobal("gurt")
|
lua_vm.lua_setglobal("gurt")
|
||||||
|
|
||||||
func _setup_additional_lua_apis():
|
func _setup_additional_lua_apis():
|
||||||
# Add table.tostring utility that's needed in callbacks
|
|
||||||
lua_vm.lua_getglobal("table")
|
lua_vm.lua_getglobal("table")
|
||||||
if lua_vm.lua_isnil(-1):
|
if lua_vm.lua_isnil(-1):
|
||||||
lua_vm.lua_pop(1)
|
lua_vm.lua_pop(1)
|
||||||
@@ -324,9 +334,9 @@ func _setup_additional_lua_apis():
|
|||||||
lua_vm.lua_setglobal("table")
|
lua_vm.lua_setglobal("table")
|
||||||
lua_vm.lua_getglobal("table")
|
lua_vm.lua_getglobal("table")
|
||||||
|
|
||||||
lua_vm.lua_pushcallable(_threaded_table_tostring_handler, "table.tostring")
|
lua_vm.lua_pushcallable(_table_tostring_handler, "table.tostring")
|
||||||
lua_vm.lua_setfield(-2, "tostring")
|
lua_vm.lua_setfield(-2, "tostring")
|
||||||
lua_vm.lua_pop(1) # Pop table from stack
|
lua_vm.lua_pop(1)
|
||||||
|
|
||||||
LuaSignalUtils.setup_signal_api(lua_vm)
|
LuaSignalUtils.setup_signal_api(lua_vm)
|
||||||
LuaClipboardUtils.setup_clipboard_api(lua_vm)
|
LuaClipboardUtils.setup_clipboard_api(lua_vm)
|
||||||
@@ -335,7 +345,7 @@ func _setup_additional_lua_apis():
|
|||||||
LuaWebSocketUtils.setup_websocket_api(lua_vm)
|
LuaWebSocketUtils.setup_websocket_api(lua_vm)
|
||||||
LuaAudioUtils.setup_audio_api(lua_vm)
|
LuaAudioUtils.setup_audio_api(lua_vm)
|
||||||
|
|
||||||
func _threaded_table_tostring_handler(vm: LuauVM) -> int:
|
func _table_tostring_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
var table_string = LuaPrintUtils.table_to_string(vm, 1)
|
var table_string = LuaPrintUtils.table_to_string(vm, 1)
|
||||||
vm.lua_pushstring(table_string)
|
vm.lua_pushstring(table_string)
|
||||||
@@ -350,14 +360,24 @@ func _emit_script_error(error: String):
|
|||||||
func _emit_print_output(message: String):
|
func _emit_print_output(message: String):
|
||||||
print_output.emit(message)
|
print_output.emit(message)
|
||||||
|
|
||||||
func _threaded_gurt_select_all_handler(vm: LuauVM) -> int:
|
func _gurt_select_all_handler(vm: LuauVM) -> int:
|
||||||
# For threaded mode, selectAll is complex as it requires DOM access
|
var selector: String = vm.luaL_checkstring(1)
|
||||||
# Return empty array for now, or implement via main thread operation
|
|
||||||
|
if not dom_parser or not dom_parser.parse_result:
|
||||||
|
vm.lua_newtable()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
var elements = SelectorUtils.find_all_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
|
|
||||||
vm.lua_newtable()
|
vm.lua_newtable()
|
||||||
|
for i in range(elements.size()):
|
||||||
|
vm.lua_pushinteger(i + 1)
|
||||||
|
LuaDOMUtils.create_element_wrapper(vm, elements[i], lua_api)
|
||||||
|
vm.lua_rawset(-3)
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
func _threaded_gurt_create_handler(vm: LuauVM) -> int:
|
func _gurt_create_handler(vm: LuauVM) -> int:
|
||||||
# Create new HTML element using existing system
|
|
||||||
var tag_name: String = vm.luaL_checkstring(1)
|
var tag_name: String = vm.luaL_checkstring(1)
|
||||||
var attributes = {}
|
var attributes = {}
|
||||||
|
|
||||||
@@ -365,36 +385,29 @@ func _threaded_gurt_create_handler(vm: LuauVM) -> int:
|
|||||||
vm.luaL_checktype(2, vm.LUA_TTABLE)
|
vm.luaL_checktype(2, vm.LUA_TTABLE)
|
||||||
attributes = vm.lua_todictionary(2)
|
attributes = vm.lua_todictionary(2)
|
||||||
|
|
||||||
# Create HTML element using existing HTMLParser
|
|
||||||
var new_element = HTMLParser.HTMLElement.new(tag_name)
|
var new_element = HTMLParser.HTMLElement.new(tag_name)
|
||||||
|
|
||||||
# Apply attributes and content
|
|
||||||
for attr_name in attributes:
|
for attr_name in attributes:
|
||||||
if attr_name == "text":
|
if attr_name == "text":
|
||||||
# Set text content directly on the HTML element
|
|
||||||
new_element.text_content = str(attributes[attr_name])
|
new_element.text_content = str(attributes[attr_name])
|
||||||
else:
|
else:
|
||||||
new_element.set_attribute(attr_name, str(attributes[attr_name]))
|
new_element.set_attribute(attr_name, str(attributes[attr_name]))
|
||||||
|
|
||||||
# Assign a unique ID
|
|
||||||
var element_id = lua_api.get_or_assign_element_id(new_element)
|
var element_id = lua_api.get_or_assign_element_id(new_element)
|
||||||
new_element.set_attribute("id", element_id)
|
new_element.set_attribute("id", element_id)
|
||||||
|
|
||||||
# Add to parser's element collection
|
|
||||||
dom_parser.parse_result.all_elements.append(new_element)
|
dom_parser.parse_result.all_elements.append(new_element)
|
||||||
|
|
||||||
LuaDOMUtils.create_element_wrapper(vm, new_element, lua_api)
|
LuaDOMUtils.create_element_wrapper(vm, new_element, lua_api)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
func _threaded_set_timeout_handler(vm: LuauVM) -> int:
|
func _set_timeout_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
||||||
var delay_ms: int = vm.luaL_checkint(2)
|
var delay_ms: int = vm.luaL_checkint(2)
|
||||||
|
|
||||||
# Generate a unique timeout ID
|
|
||||||
var timeout_id = lua_api.timeout_manager.next_timeout_id
|
var timeout_id = lua_api.timeout_manager.next_timeout_id
|
||||||
lua_api.timeout_manager.next_timeout_id += 1
|
lua_api.timeout_manager.next_timeout_id += 1
|
||||||
|
|
||||||
# Store the callback in THIS threaded VM's registry
|
|
||||||
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
||||||
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
||||||
if vm.lua_isnil(-1):
|
if vm.lua_isnil(-1):
|
||||||
@@ -405,29 +418,25 @@ func _threaded_set_timeout_handler(vm: LuauVM) -> int:
|
|||||||
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
||||||
|
|
||||||
vm.lua_pushinteger(timeout_id)
|
vm.lua_pushinteger(timeout_id)
|
||||||
vm.lua_pushvalue(1) # Copy the callback function
|
vm.lua_pushvalue(1)
|
||||||
vm.lua_rawset(-3)
|
vm.lua_rawset(-3)
|
||||||
vm.lua_pop(1)
|
vm.lua_pop(1)
|
||||||
|
|
||||||
# Create timeout info and send timer creation command to main thread
|
|
||||||
call_deferred("_create_threaded_timeout", timeout_id, delay_ms)
|
call_deferred("_create_threaded_timeout", timeout_id, delay_ms)
|
||||||
|
|
||||||
vm.lua_pushinteger(timeout_id)
|
vm.lua_pushinteger(timeout_id)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
func _threaded_clear_timeout_handler(vm: LuauVM) -> int:
|
func _clear_timeout_handler(vm: LuauVM) -> int:
|
||||||
# Delegate to Lua API timeout system
|
|
||||||
return lua_api._gurt_clear_timeout_handler(vm)
|
return lua_api._gurt_clear_timeout_handler(vm)
|
||||||
|
|
||||||
func _threaded_set_interval_handler(vm: LuauVM) -> int:
|
func _set_interval_handler(vm: LuauVM) -> int:
|
||||||
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
vm.luaL_checktype(1, vm.LUA_TFUNCTION)
|
||||||
var delay_ms: int = vm.luaL_checkint(2)
|
var delay_ms: int = vm.luaL_checkint(2)
|
||||||
|
|
||||||
# Generate a unique interval ID
|
|
||||||
var interval_id = lua_api.timeout_manager.next_timeout_id
|
var interval_id = lua_api.timeout_manager.next_timeout_id
|
||||||
lua_api.timeout_manager.next_timeout_id += 1
|
lua_api.timeout_manager.next_timeout_id += 1
|
||||||
|
|
||||||
# Store the callback in THIS threaded VM's registry (same as timeout)
|
|
||||||
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
vm.lua_pushstring("GURT_THREADED_TIMEOUTS")
|
||||||
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
||||||
if vm.lua_isnil(-1):
|
if vm.lua_isnil(-1):
|
||||||
@@ -438,48 +447,40 @@ func _threaded_set_interval_handler(vm: LuauVM) -> int:
|
|||||||
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
vm.lua_rawset(vm.LUA_REGISTRYINDEX)
|
||||||
|
|
||||||
vm.lua_pushinteger(interval_id)
|
vm.lua_pushinteger(interval_id)
|
||||||
vm.lua_pushvalue(1) # Copy the callback function
|
vm.lua_pushvalue(1)
|
||||||
vm.lua_rawset(-3)
|
vm.lua_rawset(-3)
|
||||||
vm.lua_pop(1)
|
vm.lua_pop(1)
|
||||||
|
|
||||||
# Create interval info and send timer creation command to main thread
|
|
||||||
call_deferred("_create_threaded_interval", interval_id, delay_ms)
|
call_deferred("_create_threaded_interval", interval_id, delay_ms)
|
||||||
|
|
||||||
vm.lua_pushinteger(interval_id)
|
vm.lua_pushinteger(interval_id)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
func _threaded_clear_interval_handler(vm: LuauVM) -> int:
|
func get_current_href() -> String:
|
||||||
# Delegate to Lua API timeout system (clearInterval works same as clearTimeout)
|
var main_node = Engine.get_main_loop().current_scene
|
||||||
return lua_api._gurt_clear_interval_handler(vm)
|
|
||||||
|
return main_node.current_domain
|
||||||
|
|
||||||
func _threaded_gurt_select_handler(vm: LuauVM) -> int:
|
func _gurt_select_handler(vm: LuauVM) -> int:
|
||||||
var selector: String = vm.luaL_checkstring(1)
|
var selector: String = vm.luaL_checkstring(1)
|
||||||
|
|
||||||
if not dom_parser or not dom_parser.parse_result:
|
if not dom_parser or not dom_parser.parse_result:
|
||||||
vm.lua_pushnil()
|
vm.lua_pushnil()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# Find the element using the existing SelectorUtils
|
|
||||||
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
var element = SelectorUtils.find_first_matching(selector, dom_parser.parse_result.all_elements)
|
||||||
if element:
|
if element:
|
||||||
# Use DOM.gd element wrapper
|
|
||||||
LuaDOMUtils.create_element_wrapper(vm, element, lua_api)
|
LuaDOMUtils.create_element_wrapper(vm, element, lua_api)
|
||||||
return 1
|
return 1
|
||||||
else:
|
else:
|
||||||
# Return nil if element not found
|
|
||||||
vm.lua_pushnil()
|
vm.lua_pushnil()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
# All element handlers now use DOM.gd wrappers
|
func _body_on_handler(vm: LuauVM) -> int:
|
||||||
|
vm.luaL_checktype(1, vm.LUA_TTABLE)
|
||||||
func _threaded_body_on_handler(vm: LuauVM) -> int:
|
var event_name: String = vm.luaL_checkstring(2)
|
||||||
# Handle body event registration in threaded mode
|
vm.luaL_checktype(3, vm.LUA_TFUNCTION)
|
||||||
# Arguments: (self, event_name, callback) due to colon syntax
|
|
||||||
vm.luaL_checktype(1, vm.LUA_TTABLE) # self (body table)
|
|
||||||
var event_name: String = vm.luaL_checkstring(2) # event name
|
|
||||||
vm.luaL_checktype(3, vm.LUA_TFUNCTION) # callback function
|
|
||||||
|
|
||||||
# Store callback in registry
|
|
||||||
vm.lua_pushstring("THREADED_CALLBACKS")
|
vm.lua_pushstring("THREADED_CALLBACKS")
|
||||||
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
vm.lua_rawget(vm.LUA_REGISTRYINDEX)
|
||||||
if vm.lua_isnil(-1):
|
if vm.lua_isnil(-1):
|
||||||
@@ -492,16 +493,14 @@ func _threaded_body_on_handler(vm: LuauVM) -> int:
|
|||||||
var callback_ref = lua_api.next_callback_ref
|
var callback_ref = lua_api.next_callback_ref
|
||||||
lua_api.next_callback_ref += 1
|
lua_api.next_callback_ref += 1
|
||||||
|
|
||||||
# Get a proper subscription ID
|
|
||||||
var subscription_id = lua_api.next_subscription_id
|
var subscription_id = lua_api.next_subscription_id
|
||||||
lua_api.next_subscription_id += 1
|
lua_api.next_subscription_id += 1
|
||||||
|
|
||||||
vm.lua_pushinteger(callback_ref)
|
vm.lua_pushinteger(callback_ref)
|
||||||
vm.lua_pushvalue(3) # Copy the callback function (3rd argument)
|
vm.lua_pushvalue(3)
|
||||||
vm.lua_rawset(-3)
|
vm.lua_rawset(-3)
|
||||||
vm.lua_pop(1)
|
vm.lua_pop(1)
|
||||||
|
|
||||||
# Queue DOM operation for main thread (body events)
|
|
||||||
var operation = {
|
var operation = {
|
||||||
"type": "register_body_event",
|
"type": "register_body_event",
|
||||||
"event_name": event_name,
|
"event_name": event_name,
|
||||||
@@ -511,7 +510,6 @@ func _threaded_body_on_handler(vm: LuauVM) -> int:
|
|||||||
|
|
||||||
call_deferred("_emit_dom_operation_request", operation)
|
call_deferred("_emit_dom_operation_request", operation)
|
||||||
|
|
||||||
# Return subscription with unsubscribe method
|
|
||||||
vm.lua_newtable()
|
vm.lua_newtable()
|
||||||
vm.lua_pushinteger(subscription_id)
|
vm.lua_pushinteger(subscription_id)
|
||||||
vm.lua_setfield(-2, "_subscription_id")
|
vm.lua_setfield(-2, "_subscription_id")
|
||||||
@@ -524,15 +522,12 @@ func _emit_dom_operation_request(operation: Dictionary):
|
|||||||
dom_operation_request.emit(operation)
|
dom_operation_request.emit(operation)
|
||||||
|
|
||||||
func _create_threaded_timeout(timeout_id: int, delay_ms: int):
|
func _create_threaded_timeout(timeout_id: int, delay_ms: int):
|
||||||
# Ensure timeout manager exists
|
|
||||||
lua_api._ensure_timeout_manager()
|
lua_api._ensure_timeout_manager()
|
||||||
|
|
||||||
# Create timeout info for threaded execution
|
|
||||||
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(timeout_id, timeout_id, lua_vm, lua_api.timeout_manager, false, delay_ms)
|
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(timeout_id, timeout_id, lua_vm, lua_api.timeout_manager, false, delay_ms)
|
||||||
lua_api.timeout_manager.active_timeouts[timeout_id] = timeout_info
|
lua_api.timeout_manager.active_timeouts[timeout_id] = timeout_info
|
||||||
lua_api.timeout_manager.threaded_vm = self
|
lua_api.timeout_manager.threaded_vm = self
|
||||||
|
|
||||||
# Create and start timer on main thread
|
|
||||||
var timer = Timer.new()
|
var timer = Timer.new()
|
||||||
timer.wait_time = delay_ms / 1000.0
|
timer.wait_time = delay_ms / 1000.0
|
||||||
timer.one_shot = true
|
timer.one_shot = true
|
||||||
@@ -543,17 +538,15 @@ func _create_threaded_timeout(timeout_id: int, delay_ms: int):
|
|||||||
timer.start()
|
timer.start()
|
||||||
|
|
||||||
func _create_threaded_interval(interval_id: int, delay_ms: int):
|
func _create_threaded_interval(interval_id: int, delay_ms: int):
|
||||||
# Ensure timeout manager exists
|
|
||||||
lua_api._ensure_timeout_manager()
|
lua_api._ensure_timeout_manager()
|
||||||
|
|
||||||
# Create interval info for threaded execution
|
|
||||||
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(interval_id, interval_id, lua_vm, lua_api.timeout_manager, true, delay_ms)
|
var timeout_info = lua_api.timeout_manager.TimeoutInfo.new(interval_id, interval_id, lua_vm, lua_api.timeout_manager, true, delay_ms)
|
||||||
lua_api.timeout_manager.active_timeouts[interval_id] = timeout_info
|
lua_api.timeout_manager.active_timeouts[interval_id] = timeout_info
|
||||||
lua_api.timeout_manager.threaded_vm = self
|
lua_api.timeout_manager.threaded_vm = self
|
||||||
|
|
||||||
var timer = Timer.new()
|
var timer = Timer.new()
|
||||||
timer.wait_time = delay_ms / 1000.0
|
timer.wait_time = delay_ms / 1000.0
|
||||||
timer.one_shot = false # Repeating timer for intervals
|
timer.one_shot = false
|
||||||
timer.timeout.connect(lua_api.timeout_manager._on_timeout_triggered.bind(timeout_info))
|
timer.timeout.connect(lua_api.timeout_manager._on_timeout_triggered.bind(timeout_info))
|
||||||
|
|
||||||
timeout_info.timer = timer
|
timeout_info.timer = timer
|
||||||
|
|||||||
62
flumi/Scripts/Utils/URLUtils.gd
Normal file
62
flumi/Scripts/Utils/URLUtils.gd
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
class_name URLUtils
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
static func resolve_url(base_url: String, relative_url: String) -> String:
|
||||||
|
# If relative_url is already absolute, return it as-is
|
||||||
|
if relative_url.begins_with("http://") or relative_url.begins_with("https://") or relative_url.begins_with("gurt://"):
|
||||||
|
return relative_url
|
||||||
|
|
||||||
|
# If empty, treat as relative to current domain
|
||||||
|
if base_url.is_empty():
|
||||||
|
return relative_url
|
||||||
|
|
||||||
|
var clean_base = base_url.rstrip("/")
|
||||||
|
|
||||||
|
# Parse scheme and host
|
||||||
|
var scheme_end = clean_base.find("://")
|
||||||
|
if scheme_end == -1:
|
||||||
|
return relative_url
|
||||||
|
|
||||||
|
var scheme = clean_base.substr(0, scheme_end + 3)
|
||||||
|
var remainder = clean_base.substr(scheme_end + 3)
|
||||||
|
|
||||||
|
# Split remainder into host and path
|
||||||
|
var first_slash = remainder.find("/")
|
||||||
|
var host = ""
|
||||||
|
var current_path_parts = []
|
||||||
|
|
||||||
|
if first_slash == -1:
|
||||||
|
# No path in base URL, just host
|
||||||
|
host = remainder
|
||||||
|
else:
|
||||||
|
host = remainder.substr(0, first_slash)
|
||||||
|
var path = remainder.substr(first_slash + 1)
|
||||||
|
if not path.is_empty():
|
||||||
|
current_path_parts = path.split("/")
|
||||||
|
|
||||||
|
var final_path_parts = []
|
||||||
|
|
||||||
|
if relative_url.begins_with("/"):
|
||||||
|
# Absolute path from root
|
||||||
|
var href_path = relative_url.substr(1) if relative_url.length() > 1 else ""
|
||||||
|
if not href_path.is_empty():
|
||||||
|
final_path_parts = href_path.split("/")
|
||||||
|
else:
|
||||||
|
# Relative path
|
||||||
|
final_path_parts = current_path_parts.duplicate()
|
||||||
|
|
||||||
|
var href_parts = relative_url.split("/")
|
||||||
|
for part in href_parts:
|
||||||
|
if part == "..":
|
||||||
|
if final_path_parts.size() > 0:
|
||||||
|
final_path_parts.pop_back()
|
||||||
|
elif part == "." or part == "":
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
final_path_parts.append(part)
|
||||||
|
|
||||||
|
var result = scheme + host
|
||||||
|
if final_path_parts.size() > 0:
|
||||||
|
result += "/" + "/".join(final_path_parts)
|
||||||
|
|
||||||
|
return result
|
||||||
1
flumi/Scripts/Utils/URLUtils.gd.uid
Normal file
1
flumi/Scripts/Utils/URLUtils.gd.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://bpdeuavthgjxt
|
||||||
@@ -71,42 +71,7 @@ func recalculate_percentage_elements(node: Node):
|
|||||||
var current_domain = "" # Store current domain for display
|
var current_domain = "" # Store current domain for display
|
||||||
|
|
||||||
func resolve_url(href: String) -> String:
|
func resolve_url(href: String) -> String:
|
||||||
if href.begins_with("http://") or href.begins_with("https://") or href.begins_with("gurt://"):
|
return URLUtils.resolve_url(current_domain, href)
|
||||||
return href
|
|
||||||
|
|
||||||
if current_domain.is_empty():
|
|
||||||
return href
|
|
||||||
|
|
||||||
var clean_domain = current_domain.rstrip("/")
|
|
||||||
|
|
||||||
var current_parts = clean_domain.split("/")
|
|
||||||
var host = current_parts[0]
|
|
||||||
var current_path_parts = Array(current_parts.slice(1)) if current_parts.size() > 1 else []
|
|
||||||
|
|
||||||
var final_path_parts = []
|
|
||||||
|
|
||||||
if href.begins_with("/"):
|
|
||||||
var href_path = href.substr(1) if href.length() > 1 else ""
|
|
||||||
if not href_path.is_empty():
|
|
||||||
final_path_parts = href_path.split("/")
|
|
||||||
else:
|
|
||||||
final_path_parts = current_path_parts.duplicate()
|
|
||||||
|
|
||||||
var href_parts = href.split("/")
|
|
||||||
for part in href_parts:
|
|
||||||
if part == "..":
|
|
||||||
if final_path_parts.size() > 0:
|
|
||||||
final_path_parts.pop_back()
|
|
||||||
elif part == "." or part == "":
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
final_path_parts.append(part)
|
|
||||||
|
|
||||||
var result = "gurt://" + host
|
|
||||||
if final_path_parts.size() > 0:
|
|
||||||
result += "/" + "/".join(final_path_parts)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
func handle_link_click(meta: Variant) -> void:
|
func handle_link_click(meta: Variant) -> void:
|
||||||
var href = str(meta)
|
var href = str(meta)
|
||||||
@@ -134,25 +99,36 @@ func _on_search_submitted(url: String) -> void:
|
|||||||
const GLOBE_ICON = preload("res://Assets/Icons/globe.svg")
|
const GLOBE_ICON = preload("res://Assets/Icons/globe.svg")
|
||||||
tab.stop_loading()
|
tab.stop_loading()
|
||||||
tab.set_icon(GLOBE_ICON)
|
tab.set_icon(GLOBE_ICON)
|
||||||
|
return
|
||||||
|
|
||||||
var html_bytes = result.html
|
var html_bytes = result.html
|
||||||
|
|
||||||
if result.has("display_url"):
|
if result.has("display_url"):
|
||||||
current_domain = result.display_url
|
current_domain = result.display_url
|
||||||
|
if not current_domain.begins_with("gurt://"):
|
||||||
|
current_domain = "gurt://" + current_domain
|
||||||
if not search_bar.has_focus():
|
if not search_bar.has_focus():
|
||||||
search_bar.text = current_domain
|
search_bar.text = result.display_url # Show clean version in search bar
|
||||||
|
else:
|
||||||
|
current_domain = url
|
||||||
|
|
||||||
render_content(html_bytes)
|
render_content(html_bytes)
|
||||||
|
|
||||||
|
# Stop loading spinner after successful render
|
||||||
|
tab.stop_loading()
|
||||||
else:
|
else:
|
||||||
print("Non-GURT URL entered: ", url)
|
print("Non-GURT URL entered: ", url)
|
||||||
|
|
||||||
func _on_search_focus_entered() -> void:
|
func _on_search_focus_entered() -> void:
|
||||||
if not current_domain.is_empty():
|
if not current_domain.is_empty():
|
||||||
search_bar.text = "gurt://" + current_domain
|
search_bar.text = current_domain
|
||||||
|
|
||||||
func _on_search_focus_exited() -> void:
|
func _on_search_focus_exited() -> void:
|
||||||
if not current_domain.is_empty():
|
if not current_domain.is_empty():
|
||||||
search_bar.text = current_domain
|
var display_text = current_domain
|
||||||
|
if display_text.begins_with("gurt://"):
|
||||||
|
display_text = display_text.substr(7)
|
||||||
|
search_bar.text = display_text
|
||||||
|
|
||||||
func render() -> void:
|
func render() -> void:
|
||||||
render_content(Constants.HTML_CONTENT)
|
render_content(Constants.HTML_CONTENT)
|
||||||
@@ -163,6 +139,19 @@ func render_content(html_bytes: PackedByteArray) -> void:
|
|||||||
for child in website_container.get_children():
|
for child in website_container.get_children():
|
||||||
child.queue_free()
|
child.queue_free()
|
||||||
|
|
||||||
|
var current_parent = website_container.get_parent()
|
||||||
|
if current_parent and current_parent.name == "BodyMarginContainer":
|
||||||
|
var original_parent = current_parent.get_parent()
|
||||||
|
var container_index = current_parent.get_index()
|
||||||
|
|
||||||
|
current_parent.remove_child(website_container)
|
||||||
|
|
||||||
|
original_parent.remove_child(current_parent)
|
||||||
|
current_parent.queue_free()
|
||||||
|
|
||||||
|
original_parent.add_child(website_container)
|
||||||
|
original_parent.move_child(website_container, container_index)
|
||||||
|
|
||||||
font_dependent_elements.clear()
|
font_dependent_elements.clear()
|
||||||
FontManager.clear_fonts()
|
FontManager.clear_fonts()
|
||||||
FontManager.set_refresh_callback(refresh_fonts)
|
FontManager.set_refresh_callback(refresh_fonts)
|
||||||
@@ -172,6 +161,9 @@ func render_content(html_bytes: PackedByteArray) -> void:
|
|||||||
|
|
||||||
parser.process_styles()
|
parser.process_styles()
|
||||||
|
|
||||||
|
if parse_result.external_css and not parse_result.external_css.is_empty():
|
||||||
|
await parser.process_external_styles(current_domain)
|
||||||
|
|
||||||
# Process and load all custom fonts defined in <font> tags
|
# Process and load all custom fonts defined in <font> tags
|
||||||
parser.process_fonts()
|
parser.process_fonts()
|
||||||
FontManager.load_all_fonts()
|
FontManager.load_all_fonts()
|
||||||
@@ -257,6 +249,8 @@ func render_content(html_bytes: PackedByteArray) -> void:
|
|||||||
|
|
||||||
if scripts.size() > 0 and lua_api:
|
if scripts.size() > 0 and lua_api:
|
||||||
parser.process_scripts(lua_api, null)
|
parser.process_scripts(lua_api, null)
|
||||||
|
if parse_result.external_scripts and not parse_result.external_scripts.is_empty():
|
||||||
|
await parser.process_external_scripts(lua_api, null, current_domain)
|
||||||
|
|
||||||
static func safe_add_child(parent: Node, child: Node) -> void:
|
static func safe_add_child(parent: Node, child: Node) -> void:
|
||||||
if child.get_parent():
|
if child.get_parent():
|
||||||
@@ -513,3 +507,14 @@ func refresh_fonts(font_name: String) -> void:
|
|||||||
if styles.has("font-family") and styles["font-family"] == font_name:
|
if styles.has("font-family") and styles["font-family"] == font_name:
|
||||||
if is_instance_valid(label):
|
if is_instance_valid(label):
|
||||||
StyleManager.apply_styles_to_label(label, styles, element, parser)
|
StyleManager.apply_styles_to_label(label, styles, element, parser)
|
||||||
|
|
||||||
|
func get_current_url() -> String:
|
||||||
|
return current_domain if not current_domain.is_empty() else ""
|
||||||
|
|
||||||
|
func reload_current_page() -> void:
|
||||||
|
if not current_domain.is_empty():
|
||||||
|
_on_search_submitted(current_domain)
|
||||||
|
|
||||||
|
func navigate_to_url(url: String) -> void:
|
||||||
|
var resolved_url = resolve_url(url)
|
||||||
|
_on_search_submitted(resolved_url)
|
||||||
|
|||||||
Reference in New Issue
Block a user