download system
This commit is contained in:
1
flumi/Assets/Icons/folder.svg
Normal file
1
flumi/Assets/Icons/folder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-folder-icon lucide-folder"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
||||
|
After Width: | Height: | Size: 360 B |
37
flumi/Assets/Icons/folder.svg.import
Normal file
37
flumi/Assets/Icons/folder.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3ottyh38w3db"
|
||||
path="res://.godot/imported/folder.svg-0d6e9c106058d2e35908257faa439409.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/folder.svg"
|
||||
dest_files=["res://.godot/imported/folder.svg-0d6e9c106058d2e35908257faa439409.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
1
flumi/Assets/Icons/link-2.svg
Normal file
1
flumi/Assets/Icons/link-2.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-link2-icon lucide-link-2"><path d="M9 17H7A5 5 0 0 1 7 7h2"/><path d="M15 7h2a5 5 0 1 1 0 10h-2"/><line x1="8" x2="16" y1="12" y2="12"/></svg>
|
||||
|
After Width: | Height: | Size: 339 B |
37
flumi/Assets/Icons/link-2.svg.import
Normal file
37
flumi/Assets/Icons/link-2.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://da33sld2tguf0"
|
||||
path="res://.godot/imported/link-2.svg-65859ed5f57233280efa03e60ad3c6bb.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://Assets/Icons/link-2.svg"
|
||||
dest_files=["res://.godot/imported/link-2.svg-65859ed5f57233280efa03e60ad3c6bb.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
106
flumi/Scenes/BrowserMenus/download_entry.tscn
Normal file
106
flumi/Scenes/BrowserMenus/download_entry.tscn
Normal file
@@ -0,0 +1,106 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://co3t7h2tavx10"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c1qh0fqvvh8tq" path="res://Scripts/Browser/DownloadEntry.gd" id="1_script"]
|
||||
[ext_resource type="Texture2D" uid="uid://cbwitcygwoqdo" path="res://Assets/Icons/download.svg" id="2_download"]
|
||||
[ext_resource type="Texture2D" uid="uid://da33sld2tguf0" path="res://Assets/Icons/link-2.svg" id="3_goveu"]
|
||||
[ext_resource type="Texture2D" uid="uid://d3ottyh38w3db" path="res://Assets/Icons/folder.svg" id="4_8t7bq"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8t7bq"]
|
||||
content_margin_left = 10.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 10.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.202723, 0.202723, 0.202723, 1)
|
||||
corner_radius_top_left = 15
|
||||
corner_radius_top_right = 15
|
||||
corner_radius_bottom_right = 15
|
||||
corner_radius_bottom_left = 15
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_8t7bq"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3j0oj"]
|
||||
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
|
||||
corner_radius_top_left = 50
|
||||
corner_radius_top_right = 50
|
||||
corner_radius_bottom_right = 50
|
||||
corner_radius_bottom_left = 50
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4krvm"]
|
||||
bg_color = Color(0.6, 0.6, 0.6, 0)
|
||||
draw_center = false
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer"]
|
||||
offset_right = 604.0
|
||||
offset_bottom = 24.0
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_8t7bq")
|
||||
script = ExtResource("1_script")
|
||||
|
||||
[node name="DownloadEntry" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="TimeLabel" type="RichTextLabel" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(65, 0)
|
||||
layout_mode = 2
|
||||
text = "2:00PM"
|
||||
fit_content = true
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(15, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FileIcon" type="TextureRect" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
size_flags_vertical = 4
|
||||
texture = ExtResource("2_download")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="FileNameLabel" type="RichTextLabel" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
text = "example-file.pdf"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="LinkButton" type="Button" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_8t7bq")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_3j0oj")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_4krvm")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_4krvm")
|
||||
icon = ExtResource("3_goveu")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="FileButton" type="Button" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(32, 32)
|
||||
layout_mode = 2
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_8t7bq")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_3j0oj")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_4krvm")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_4krvm")
|
||||
icon = ExtResource("4_8t7bq")
|
||||
icon_alignment = 1
|
||||
|
||||
[node name="DomainLabel" type="RichTextLabel" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(120, 0)
|
||||
layout_mode = 2
|
||||
theme_override_colors/default_color = Color(0.817521, 0.817521, 0.817521, 1)
|
||||
text = "example.com"
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="SizeLabel" type="RichTextLabel" parent="DownloadEntry"]
|
||||
custom_minimum_size = Vector2(80, 0)
|
||||
layout_mode = 2
|
||||
theme_override_colors/default_color = Color(0.7, 0.7, 0.7, 1)
|
||||
text = "2.5 MB"
|
||||
fit_content = true
|
||||
horizontal_alignment = 2
|
||||
vertical_alignment = 1
|
||||
|
||||
[connection signal="pressed" from="DownloadEntry/LinkButton" to="." method="_on_link_button_pressed"]
|
||||
[connection signal="pressed" from="DownloadEntry/FileButton" to="." method="_on_file_button_pressed"]
|
||||
88
flumi/Scenes/BrowserMenus/downloads.tscn
Normal file
88
flumi/Scenes/BrowserMenus/downloads.tscn
Normal file
@@ -0,0 +1,88 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://bxtm0d4q1w8vy"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cp3geyxt0f8tn" path="res://Scripts/Browser/DownloadsStore.gd" id="1_w8vy"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_main"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 15.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
corner_radius_top_left = 30
|
||||
corner_radius_top_right = 30
|
||||
corner_radius_bottom_right = 30
|
||||
corner_radius_bottom_left = 30
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_search"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_search"]
|
||||
bg_color = Color(0.168627, 0.168627, 0.168627, 1)
|
||||
corner_radius_top_left = 15
|
||||
corner_radius_top_right = 15
|
||||
corner_radius_bottom_right = 15
|
||||
corner_radius_bottom_left = 15
|
||||
expand_margin_left = 40.0
|
||||
|
||||
[sub_resource type="Theme" id="Theme_search"]
|
||||
LineEdit/styles/focus = SubResource("StyleBoxEmpty_search")
|
||||
LineEdit/styles/normal = SubResource("StyleBoxFlat_search")
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_search_bg"]
|
||||
content_margin_left = 10.0
|
||||
bg_color = Color(0.219501, 0.219501, 0.219501, 1)
|
||||
corner_radius_top_left = 15
|
||||
corner_radius_top_right = 15
|
||||
corner_radius_bottom_right = 15
|
||||
corner_radius_bottom_left = 15
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_container"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(0.154876, 0.154876, 0.154876, 1)
|
||||
corner_radius_top_left = 15
|
||||
corner_radius_top_right = 15
|
||||
corner_radius_bottom_right = 15
|
||||
corner_radius_bottom_left = 15
|
||||
|
||||
[node name="PopupPanel" type="PopupPanel"]
|
||||
initial_position = 1
|
||||
size = Vector2i(770, 710)
|
||||
visible = true
|
||||
max_size = Vector2i(770, 710)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_main")
|
||||
script = ExtResource("1_w8vy")
|
||||
|
||||
[node name="Main" type="VBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(710, 0)
|
||||
offset_left = 15.0
|
||||
offset_top = 15.0
|
||||
offset_right = 755.0
|
||||
offset_bottom = 695.0
|
||||
size_flags_horizontal = 4
|
||||
theme_override_constants/separation = 15
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="Main"]
|
||||
custom_minimum_size = Vector2(0, 45)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme = SubResource("Theme_search")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_search_bg")
|
||||
placeholder_text = "Search downloads..."
|
||||
caret_blink = true
|
||||
|
||||
[node name="PanelContainer2" type="PanelContainer" parent="Main"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_container")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="Main/PanelContainer2"]
|
||||
custom_minimum_size = Vector2(710, 500)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="DownloadEntryContainer" type="VBoxContainer" parent="Main/PanelContainer2/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
118
flumi/Scenes/UI/DownloadDialog.tscn
Normal file
118
flumi/Scenes/UI/DownloadDialog.tscn
Normal file
@@ -0,0 +1,118 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://dq4q0exy5aeey"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://be6fpnhb4p57v" path="res://Scripts/Browser/DownloadDialog.gd" id="1_3v2kl"]
|
||||
[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_0jrl0"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_c7s3a"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 15.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
corner_radius_top_left = 30
|
||||
corner_radius_top_right = 30
|
||||
corner_radius_bottom_right = 30
|
||||
corner_radius_bottom_left = 30
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v0gbf"]
|
||||
bg_color = Color(0.205117, 0.205117, 0.205117, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t7gjp"]
|
||||
draw_center = false
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_s5iik"]
|
||||
bg_color = Color(0.34452, 0.55695, 0.888021, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_0jrl0"]
|
||||
bg_color = Color(0.247059, 0.466667, 0.807843, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cuj6o"]
|
||||
bg_color = Color(0.247059, 0.466667, 0.807843, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[node name="PopupPanel" type="PopupPanel"]
|
||||
initial_position = 1
|
||||
size = Vector2i(293, 140)
|
||||
visible = true
|
||||
wrap_controls = false
|
||||
max_size = Vector2i(293, 140)
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_c7s3a")
|
||||
script = ExtResource("1_3v2kl")
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
custom_minimum_size = Vector2(240, 110)
|
||||
offset_left = 15.0
|
||||
offset_top = 15.0
|
||||
offset_right = 278.0
|
||||
offset_bottom = 858.0
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="FilenameLabel" type="Label" parent="VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
text = "File: example.png"
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="URLLabel" type="Label" parent="VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
text = "From: example.com"
|
||||
autowrap_mode = 3
|
||||
|
||||
[node name="HSeparator" type="HSeparator" parent="VBox"]
|
||||
custom_minimum_size = Vector2(10, 20)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 20
|
||||
alignment = 1
|
||||
|
||||
[node name="CancelButton" type="Button" parent="VBox/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(80, 35)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = ExtResource("2_0jrl0")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_v0gbf")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_t7gjp")
|
||||
text = "Deny"
|
||||
|
||||
[node name="OkButton" type="Button" parent="VBox/HBoxContainer"]
|
||||
custom_minimum_size = Vector2(80, 35)
|
||||
layout_mode = 2
|
||||
mouse_default_cursor_shape = 2
|
||||
theme = ExtResource("2_0jrl0")
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_s5iik")
|
||||
theme_override_styles/pressed = SubResource("StyleBoxFlat_0jrl0")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_cuj6o")
|
||||
text = "Accept"
|
||||
|
||||
[node name="FileDialog" type="FileDialog" parent="."]
|
||||
access = 2
|
||||
use_native_dialog = true
|
||||
|
||||
[connection signal="pressed" from="VBox/HBoxContainer/CancelButton" to="." method="_on_file_dialog_cancelled"]
|
||||
[connection signal="pressed" from="VBox/HBoxContainer/OkButton" to="." method="_on_download_confirmed"]
|
||||
[connection signal="canceled" from="FileDialog" to="." method="_on_file_dialog_cancelled"]
|
||||
[connection signal="file_selected" from="FileDialog" to="." method="_on_save_location_selected"]
|
||||
85
flumi/Scenes/UI/DownloadProgress.tscn
Normal file
85
flumi/Scenes/UI/DownloadProgress.tscn
Normal file
@@ -0,0 +1,85 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://b0gfxh1n7c5yu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://kj6oda7sey5s" path="res://Scripts/Browser/DownloadProgress.gd" id="1_1a3vh"]
|
||||
[ext_resource type="Theme" uid="uid://bn6rbmdy60lhr" path="res://Scenes/Styles/BrowserText.tres" id="2_lnt4f"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lnt4f"]
|
||||
content_margin_left = 15.0
|
||||
content_margin_top = 15.0
|
||||
content_margin_right = 15.0
|
||||
content_margin_bottom = 15.0
|
||||
bg_color = Color(0.105882, 0.105882, 0.105882, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_f51et"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0.184314, 0.184314, 0.184314, 1)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
corner_detail = 6
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_v5p1v"]
|
||||
bg_color = Color(0.247059, 0.466667, 0.807843, 1)
|
||||
corner_radius_top_left = 25
|
||||
corner_radius_top_right = 25
|
||||
corner_radius_bottom_right = 25
|
||||
corner_radius_bottom_left = 25
|
||||
|
||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_lnt4f"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_cs7kk"]
|
||||
bg_color = Color(0.15967, 0.15967, 0.15967, 1)
|
||||
corner_radius_top_left = 30
|
||||
corner_radius_top_right = 30
|
||||
corner_radius_bottom_right = 30
|
||||
corner_radius_bottom_left = 30
|
||||
|
||||
[node name="DownloadProgress" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(376, 80)
|
||||
offset_right = 376.0
|
||||
offset_bottom = 81.0
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_lnt4f")
|
||||
script = ExtResource("1_1a3vh")
|
||||
|
||||
[node name="HBox" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="HBox"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="FilenameLabel" type="Label" parent="HBox/VBox"]
|
||||
layout_mode = 2
|
||||
text = "Downloading file..."
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[node name="ProgressBar" type="ProgressBar" parent="HBox/VBox"]
|
||||
layout_mode = 2
|
||||
theme = ExtResource("2_lnt4f")
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_f51et")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_v5p1v")
|
||||
show_percentage = false
|
||||
|
||||
[node name="StatusLabel" type="Label" parent="HBox/VBox"]
|
||||
modulate = Color(0.7, 0.7, 0.7, 1)
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 12
|
||||
text = "Preparing..."
|
||||
|
||||
[node name="CancelButton" type="Button" parent="HBox"]
|
||||
custom_minimum_size = Vector2(30, 30)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 4
|
||||
theme_override_styles/focus = SubResource("StyleBoxEmpty_lnt4f")
|
||||
theme_override_styles/hover = SubResource("StyleBoxFlat_cs7kk")
|
||||
text = "✕"
|
||||
|
||||
[connection signal="pressed" from="HBox/CancelButton" to="." method="_on_cancel_pressed"]
|
||||
@@ -744,6 +744,8 @@ func _handle_dom_operation(operation: Dictionary):
|
||||
LuaCanvasUtils.handle_canvas_setLineWidth(operation, dom_parser)
|
||||
"canvas_setFont":
|
||||
LuaCanvasUtils.handle_canvas_setFont(operation, dom_parser)
|
||||
"request_download":
|
||||
_handle_download_request(operation)
|
||||
_:
|
||||
pass # Unknown operation type, ignore
|
||||
|
||||
@@ -990,3 +992,9 @@ func _notification(what: int):
|
||||
if timeout_manager:
|
||||
timeout_manager.cleanup_all_timeouts()
|
||||
threaded_vm.stop_lua_thread()
|
||||
|
||||
func _handle_download_request(operation: Dictionary):
|
||||
var download_data = operation.get("download_data", {})
|
||||
|
||||
var main_node = Engine.get_main_loop().current_scene
|
||||
main_node.download_manager.handle_download_request(download_data)
|
||||
|
||||
98
flumi/Scripts/Browser/DownloadDialog.gd
Normal file
98
flumi/Scripts/Browser/DownloadDialog.gd
Normal file
@@ -0,0 +1,98 @@
|
||||
class_name DownloadDialog
|
||||
extends PopupPanel
|
||||
|
||||
signal download_confirmed(download_data: Dictionary, save_path: String)
|
||||
signal download_cancelled(download_data: Dictionary)
|
||||
|
||||
@onready var ok_button: Button = $VBox/HBoxContainer/OkButton
|
||||
@onready var cancel_button: Button = $VBox/HBoxContainer/CancelButton
|
||||
@onready var file_dialog: FileDialog = $FileDialog
|
||||
|
||||
@onready var filename_label: Label = $VBox/FilenameLabel
|
||||
@onready var url_label: Label = $VBox/URLLabel
|
||||
|
||||
var download_data: Dictionary = {}
|
||||
|
||||
func show_download_dialog(data: Dictionary):
|
||||
download_data = data
|
||||
|
||||
var filename = data.get("filename", "download")
|
||||
var url = data.get("url", "")
|
||||
|
||||
filename_label.text = "File: " + filename
|
||||
|
||||
var current_site = data.get("current_site", "")
|
||||
if current_site != "":
|
||||
url_label.text = "From: " + current_site
|
||||
else:
|
||||
url_label.text = "From: " + URLUtils.extract_domain(url)
|
||||
|
||||
popup()
|
||||
_animate_entrance()
|
||||
ok_button.grab_focus()
|
||||
|
||||
func _animate_entrance():
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
var original_size = Vector2(size)
|
||||
var small_size = original_size * 0.8
|
||||
var size_difference = original_size - small_size
|
||||
var original_pos = position
|
||||
|
||||
size = Vector2i(small_size)
|
||||
position = original_pos + Vector2i(size_difference * 0.5)
|
||||
|
||||
var tween = create_tween()
|
||||
if tween:
|
||||
tween.set_parallel(true)
|
||||
var size_property = tween.tween_property(self, "size", Vector2i(original_size), 0.2)
|
||||
var pos_property = tween.tween_property(self, "position", original_pos, 0.2)
|
||||
|
||||
if size_property:
|
||||
size_property.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
|
||||
if pos_property:
|
||||
pos_property.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
|
||||
|
||||
func _on_download_confirmed():
|
||||
file_dialog.current_file = download_data.get("filename", "download")
|
||||
file_dialog.current_dir = OS.get_system_dir(OS.SYSTEM_DIR_DOWNLOADS)
|
||||
file_dialog.show()
|
||||
|
||||
func _animate_exit():
|
||||
if not is_inside_tree():
|
||||
queue_free()
|
||||
return
|
||||
|
||||
var current_size = Vector2(size)
|
||||
var small_size = current_size * 0.8
|
||||
var size_difference = current_size - small_size
|
||||
var current_pos = position
|
||||
var target_pos = current_pos + Vector2i(size_difference * 0.5)
|
||||
|
||||
var tween = create_tween()
|
||||
if tween:
|
||||
tween.set_parallel(true)
|
||||
var size_property = tween.tween_property(self, "size", Vector2i(small_size), 0.15)
|
||||
var pos_property = tween.tween_property(self, "position", target_pos, 0.15)
|
||||
|
||||
if size_property:
|
||||
size_property.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK)
|
||||
if pos_property:
|
||||
pos_property.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK)
|
||||
|
||||
await tween.finished
|
||||
|
||||
queue_free()
|
||||
|
||||
func _on_save_location_selected(path: String):
|
||||
download_confirmed.emit(download_data, path)
|
||||
_animate_exit()
|
||||
|
||||
func _on_file_dialog_cancelled():
|
||||
download_cancelled.emit(download_data)
|
||||
_animate_exit()
|
||||
|
||||
func _on_download_cancelled():
|
||||
download_cancelled.emit(download_data)
|
||||
_animate_exit()
|
||||
1
flumi/Scripts/Browser/DownloadDialog.gd.uid
Normal file
1
flumi/Scripts/Browser/DownloadDialog.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://be6fpnhb4p57v
|
||||
70
flumi/Scripts/Browser/DownloadEntry.gd
Normal file
70
flumi/Scripts/Browser/DownloadEntry.gd
Normal file
@@ -0,0 +1,70 @@
|
||||
class_name DownloadEntry
|
||||
extends PanelContainer
|
||||
|
||||
@onready var time_label: RichTextLabel = $DownloadEntry/TimeLabel
|
||||
@onready var filename_label: RichTextLabel = $DownloadEntry/FileNameLabel
|
||||
@onready var domain_label: RichTextLabel = $DownloadEntry/DomainLabel
|
||||
@onready var size_label: RichTextLabel = $DownloadEntry/SizeLabel
|
||||
@onready var link_button: Button = $DownloadEntry/LinkButton
|
||||
@onready var file_button: Button = $DownloadEntry/FileButton
|
||||
|
||||
var download_data: Dictionary = {}
|
||||
|
||||
func setup_download_entry(data: Dictionary):
|
||||
download_data = data
|
||||
|
||||
var filename = data.get("filename", "Unknown file")
|
||||
var url = data.get("url", "")
|
||||
var current_site = data.get("current_site", "")
|
||||
var file_size = data.get("size", 0)
|
||||
var timestamp = data.get("timestamp", Time.get_unix_time_from_system())
|
||||
|
||||
filename_label.text = filename
|
||||
|
||||
current_site = URLUtils.extract_domain(url) if url != "" else "Unknown source"
|
||||
domain_label.text = current_site
|
||||
|
||||
var size_text = NetworkRequest.format_bytes(file_size)
|
||||
size_label.text = size_text
|
||||
|
||||
var time_text = _format_time(timestamp)
|
||||
time_label.text = time_text
|
||||
|
||||
func _format_time(unix_timestamp: float) -> String:
|
||||
var datetime = Time.get_datetime_dict_from_unix_time(int(unix_timestamp))
|
||||
|
||||
# Format as "3:45PM"
|
||||
var hour = datetime.hour
|
||||
var minute = datetime.minute
|
||||
var am_pm = "AM"
|
||||
|
||||
if hour == 0:
|
||||
hour = 12
|
||||
elif hour > 12:
|
||||
hour -= 12
|
||||
am_pm = "PM"
|
||||
elif hour == 12:
|
||||
am_pm = "PM"
|
||||
|
||||
return "%d:%02d%s" % [hour, minute, am_pm]
|
||||
|
||||
func get_filename() -> String:
|
||||
return download_data.get("filename", "")
|
||||
|
||||
func get_domain() -> String:
|
||||
var url = download_data.get("url", "")
|
||||
return URLUtils.extract_domain(url) if url != "" else ""
|
||||
|
||||
func get_download_data() -> Dictionary:
|
||||
return download_data
|
||||
|
||||
func _on_link_button_pressed() -> void:
|
||||
DisplayServer.clipboard_set(download_data.get("url", ""))
|
||||
|
||||
func _on_file_button_pressed() -> void:
|
||||
var file_path = download_data.get("file_path", "")
|
||||
if file_path != "" and FileAccess.file_exists(file_path):
|
||||
var file_dir = file_path.get_base_dir()
|
||||
OS.shell_show_in_file_manager(file_dir)
|
||||
else:
|
||||
print("File not found: ", file_path)
|
||||
1
flumi/Scripts/Browser/DownloadEntry.gd.uid
Normal file
1
flumi/Scripts/Browser/DownloadEntry.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c1qh0fqvvh8tq
|
||||
211
flumi/Scripts/Browser/DownloadManager.gd
Normal file
211
flumi/Scripts/Browser/DownloadManager.gd
Normal file
@@ -0,0 +1,211 @@
|
||||
class_name DownloadManager
|
||||
extends Node
|
||||
|
||||
const DOWNLOAD_DIALOG = preload("res://Scenes/UI/DownloadDialog.tscn")
|
||||
const DOWNLOAD_PROGRESS = preload("res://Scenes/UI/DownloadProgress.tscn")
|
||||
const DOWNLOADS_HISTORY = preload("res://Scenes/BrowserMenus/downloads.tscn")
|
||||
|
||||
var active_downloads: Dictionary = {}
|
||||
var download_progress_container: VBoxContainer = null
|
||||
var downloads_history_ui: DownloadsStore = null
|
||||
var main_node: Main = null
|
||||
|
||||
func _init(main_reference: Main):
|
||||
main_node = main_reference
|
||||
|
||||
func _ensure_download_progress_container():
|
||||
if not download_progress_container:
|
||||
download_progress_container = VBoxContainer.new()
|
||||
download_progress_container.name = "DownloadProgressContainer"
|
||||
download_progress_container.size_flags_horizontal = Control.SIZE_SHRINK_END
|
||||
download_progress_container.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
|
||||
var anchor_container = Control.new()
|
||||
anchor_container.name = "DownloadAnchor"
|
||||
anchor_container.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
||||
anchor_container.position = Vector2(0, 130)
|
||||
anchor_container.offset_left = 381 # 376 + 5px padding
|
||||
anchor_container.add_child(download_progress_container)
|
||||
main_node.add_child(anchor_container)
|
||||
|
||||
func handle_download_request(download_data: Dictionary):
|
||||
print("Download requested: ", download_data)
|
||||
|
||||
var active_tab = main_node.get_active_tab()
|
||||
if active_tab and active_tab.current_url:
|
||||
download_data["current_site"] = URLUtils.extract_domain(active_tab.current_url)
|
||||
else:
|
||||
download_data["current_site"] = "Unknown site"
|
||||
|
||||
var dialog = DOWNLOAD_DIALOG.instantiate()
|
||||
main_node.add_child(dialog)
|
||||
|
||||
dialog.download_confirmed.connect(_on_download_confirmed)
|
||||
dialog.download_cancelled.connect(_on_download_cancelled)
|
||||
dialog.show_download_dialog(download_data)
|
||||
|
||||
func _on_download_confirmed(download_data: Dictionary, save_path: String):
|
||||
var download_id = download_data.get("id", "")
|
||||
var url = download_data.get("url", "")
|
||||
print(download_id, url)
|
||||
if download_id.is_empty() or url.is_empty():
|
||||
push_error("Invalid download data")
|
||||
return
|
||||
|
||||
_start_download(download_id, url, save_path, download_data)
|
||||
|
||||
func _on_download_cancelled(download_data: Dictionary):
|
||||
print("Download cancelled: ", download_data.get("filename", "Unknown"))
|
||||
|
||||
func _start_download(download_id: String, url: String, save_path: String, download_data: Dictionary):
|
||||
_ensure_download_progress_container()
|
||||
|
||||
var progress_ui = DOWNLOAD_PROGRESS.instantiate()
|
||||
|
||||
download_progress_container.add_child(progress_ui)
|
||||
|
||||
progress_ui.setup_download(download_id, download_data)
|
||||
progress_ui.download_cancelled.connect(_on_download_progress_cancelled)
|
||||
|
||||
var http_request = HTTPRequest.new()
|
||||
http_request.name = "DownloadRequest_" + download_id
|
||||
main_node.add_child(http_request)
|
||||
|
||||
active_downloads[download_id] = {
|
||||
"http_request": http_request,
|
||||
"save_path": save_path,
|
||||
"progress_ui": progress_ui,
|
||||
"start_time": Time.get_ticks_msec() / 1000.0,
|
||||
"total_bytes": 0,
|
||||
"downloaded_bytes": 0,
|
||||
"url": download_data.get("url", ""),
|
||||
"filename": download_data.get("filename", ""),
|
||||
"current_site": download_data.get("current_site", "")
|
||||
}
|
||||
|
||||
http_request.set_download_file(save_path)
|
||||
|
||||
http_request.request_completed.connect(func(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
_on_download_completed(download_id, result, response_code, headers, body)
|
||||
)
|
||||
|
||||
var headers = ["User-Agent: GURT Browser 1.0"]
|
||||
var request_error = http_request.request(url, headers)
|
||||
|
||||
if request_error != OK:
|
||||
var error_msg = "Failed to start download: " + str(request_error)
|
||||
print(error_msg)
|
||||
if progress_ui:
|
||||
progress_ui.set_error(error_msg)
|
||||
http_request.queue_free()
|
||||
active_downloads.erase(download_id)
|
||||
return
|
||||
|
||||
var timer = Timer.new()
|
||||
timer.name = "ProgressTimer_" + download_id
|
||||
timer.wait_time = 0.5
|
||||
timer.timeout.connect(func(): _update_download_progress(download_id))
|
||||
main_node.add_child(timer)
|
||||
timer.start()
|
||||
|
||||
func _update_download_progress(download_id: String):
|
||||
if not active_downloads.has(download_id):
|
||||
return
|
||||
|
||||
var download_info = active_downloads[download_id]
|
||||
var http_request = download_info.http_request
|
||||
var progress_ui = download_info.progress_ui
|
||||
|
||||
if http_request and progress_ui:
|
||||
var downloaded = http_request.get_downloaded_bytes()
|
||||
var total = http_request.get_body_size()
|
||||
|
||||
download_info.downloaded_bytes = downloaded
|
||||
download_info.total_bytes = total
|
||||
|
||||
var progress_percent = 0.0
|
||||
if total > 0:
|
||||
progress_percent = (float(downloaded) / float(total)) * 100.0
|
||||
|
||||
progress_ui.update_progress(progress_percent, downloaded, total)
|
||||
|
||||
func _on_download_completed(download_id: String, result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray):
|
||||
if not active_downloads.has(download_id):
|
||||
return
|
||||
|
||||
var download_info = active_downloads[download_id]
|
||||
var progress_ui = download_info.progress_ui
|
||||
var save_path = download_info.save_path
|
||||
|
||||
var timer = main_node.get_node_or_null("ProgressTimer_" + download_id)
|
||||
if timer:
|
||||
timer.queue_free()
|
||||
|
||||
if response_code >= 200 and response_code < 300 and result == HTTPRequest.RESULT_SUCCESS:
|
||||
var file = FileAccess.open(save_path, FileAccess.READ)
|
||||
if file:
|
||||
var file_size = file.get_length()
|
||||
file.close()
|
||||
|
||||
if progress_ui:
|
||||
progress_ui.set_completed(save_path)
|
||||
|
||||
_add_to_download_history(download_info, file_size, save_path)
|
||||
|
||||
print("Download completed: ", save_path)
|
||||
else:
|
||||
if progress_ui:
|
||||
progress_ui.set_error("Downloaded file not found")
|
||||
print("Downloaded file not found: ", save_path)
|
||||
else:
|
||||
var error_msg = "HTTP " + str(response_code) if response_code >= 400 else "Request failed (" + str(result) + ")"
|
||||
if progress_ui:
|
||||
progress_ui.set_error(error_msg)
|
||||
print("Download failed: ", error_msg)
|
||||
|
||||
if FileAccess.file_exists(save_path):
|
||||
DirAccess.remove_absolute(save_path)
|
||||
|
||||
download_info.http_request.queue_free()
|
||||
active_downloads.erase(download_id)
|
||||
|
||||
func _on_download_progress_cancelled(download_id: String):
|
||||
if not active_downloads.has(download_id):
|
||||
return
|
||||
|
||||
var download_info = active_downloads[download_id]
|
||||
|
||||
if download_info.http_request:
|
||||
download_info.http_request.cancel_request()
|
||||
download_info.http_request.queue_free()
|
||||
|
||||
var timer = main_node.get_node_or_null("ProgressTimer_" + download_id)
|
||||
if timer:
|
||||
timer.queue_free()
|
||||
|
||||
active_downloads.erase(download_id)
|
||||
print("Download cancelled: ", download_id)
|
||||
|
||||
func show_downloads_history():
|
||||
_ensure_downloads_history_ui()
|
||||
downloads_history_ui.popup_centered_ratio(0.8)
|
||||
|
||||
func _add_to_download_history(download_info: Dictionary, file_size: int, file_path: String):
|
||||
_ensure_downloads_history_ui()
|
||||
|
||||
var history_data = {
|
||||
"url": download_info.url,
|
||||
"filename": download_info.filename,
|
||||
"size": file_size,
|
||||
"timestamp": Time.get_unix_time_from_system(),
|
||||
"file_path": file_path,
|
||||
"current_site": download_info.get("current_site", "")
|
||||
}
|
||||
|
||||
downloads_history_ui.add_download_entry(history_data)
|
||||
|
||||
func _ensure_downloads_history_ui():
|
||||
if not downloads_history_ui:
|
||||
downloads_history_ui = DOWNLOADS_HISTORY.instantiate()
|
||||
downloads_history_ui.visible = false
|
||||
main_node.add_child(downloads_history_ui)
|
||||
1
flumi/Scripts/Browser/DownloadManager.gd.uid
Normal file
1
flumi/Scripts/Browser/DownloadManager.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://omjynclhfe8u
|
||||
129
flumi/Scripts/Browser/DownloadProgress.gd
Normal file
129
flumi/Scripts/Browser/DownloadProgress.gd
Normal file
@@ -0,0 +1,129 @@
|
||||
class_name DownloadProgress
|
||||
extends PanelContainer
|
||||
|
||||
signal download_cancelled(download_id: String)
|
||||
|
||||
@onready var filename_label: Label = $HBox/VBox/FilenameLabel
|
||||
@onready var progress_bar: ProgressBar = $HBox/VBox/ProgressBar
|
||||
@onready var status_label: Label = $HBox/VBox/StatusLabel
|
||||
@onready var cancel_button: Button = $HBox/CancelButton
|
||||
|
||||
var download_id: String = ""
|
||||
var download_data: Dictionary = {}
|
||||
var start_time: float = 0.0
|
||||
|
||||
func _ready():
|
||||
progress_bar.value = 0
|
||||
status_label.text = "Starting download..."
|
||||
|
||||
func setup_download(id: String, data: Dictionary):
|
||||
download_id = id
|
||||
download_data = data
|
||||
start_time = Time.get_ticks_msec() / 1000.0
|
||||
|
||||
var filename = data.get("filename", "Unknown file")
|
||||
filename_label.text = filename
|
||||
status_label.text = "Starting download..."
|
||||
progress_bar.value = 0
|
||||
|
||||
_animate_entrance()
|
||||
|
||||
func _animate_entrance():
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
var download_container = get_parent()
|
||||
var anchor_container = download_container.get_parent() if download_container else null
|
||||
|
||||
if anchor_container and anchor_container.name == "DownloadAnchor" and download_container.get_child_count() == 1:
|
||||
var tween = create_tween()
|
||||
if tween:
|
||||
var tween_property = tween.tween_property(anchor_container, "offset_left", -381, 0.3)
|
||||
if tween_property:
|
||||
tween_property.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
|
||||
else:
|
||||
call_deferred("_animate_individual_entrance")
|
||||
|
||||
func _animate_individual_entrance():
|
||||
if not is_inside_tree():
|
||||
return
|
||||
|
||||
var original_x = position.x
|
||||
position.x += 400 # Move off-screen to the right
|
||||
|
||||
var tween = create_tween()
|
||||
if tween:
|
||||
var slide_property = tween.tween_property(self, "position:x", original_x, 0.3)
|
||||
if slide_property:
|
||||
slide_property.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK)
|
||||
|
||||
func update_progress(progress_percent: float, bytes_downloaded: int = 0, total_bytes: int = 0):
|
||||
progress_bar.value = progress_percent
|
||||
|
||||
var elapsed_time = (Time.get_ticks_msec() / 1000.0) - start_time
|
||||
var status_text = ""
|
||||
|
||||
if total_bytes > 0:
|
||||
var speed_bps = bytes_downloaded / elapsed_time if elapsed_time > 0 else 0
|
||||
|
||||
var remaining_bytes = total_bytes - bytes_downloaded
|
||||
var eta_seconds = remaining_bytes / speed_bps if speed_bps > 0 else 0
|
||||
|
||||
status_text = NetworkRequest.format_bytes(bytes_downloaded) + " / " + NetworkRequest.format_bytes(total_bytes)
|
||||
if speed_bps > 0:
|
||||
status_text += " (" + NetworkRequest.format_bytes(int(speed_bps)) + "/s)"
|
||||
if eta_seconds > 0 and eta_seconds < 3600:
|
||||
status_text += " - %d seconds left" % int(eta_seconds)
|
||||
else:
|
||||
status_text = "%.0f%% complete" % progress_percent
|
||||
|
||||
status_label.text = status_text
|
||||
|
||||
func set_completed(file_path: String):
|
||||
progress_bar.value = 100
|
||||
status_label.text = "Download complete: " + file_path.get_file()
|
||||
cancel_button.text = "✓"
|
||||
cancel_button.disabled = true
|
||||
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
_animate_exit()
|
||||
|
||||
func set_error(error_message: String):
|
||||
status_label.text = "Error: " + error_message
|
||||
cancel_button.text = "✕"
|
||||
progress_bar.modulate = Color.RED
|
||||
|
||||
await get_tree().create_timer(4.0).timeout
|
||||
_animate_exit()
|
||||
|
||||
func _animate_exit():
|
||||
if not is_inside_tree():
|
||||
queue_free()
|
||||
return
|
||||
|
||||
var download_container = get_parent()
|
||||
var anchor_container = download_container.get_parent() if download_container else null
|
||||
|
||||
var is_last_download = download_container and download_container.get_child_count() == 1
|
||||
|
||||
var tween = create_tween()
|
||||
if tween:
|
||||
var slide_property = tween.tween_property(self, "position:x", position.x + 400, 0.25)
|
||||
if slide_property:
|
||||
slide_property.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK)
|
||||
|
||||
await tween.finished
|
||||
|
||||
if is_last_download and anchor_container and anchor_container.name == "DownloadAnchor":
|
||||
var container_tween = create_tween()
|
||||
if container_tween:
|
||||
var container_property = container_tween.tween_property(anchor_container, "offset_left", 381, 0.25)
|
||||
if container_property:
|
||||
container_property.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_BACK)
|
||||
await container_tween.finished
|
||||
|
||||
queue_free()
|
||||
|
||||
func _on_cancel_pressed():
|
||||
download_cancelled.emit(download_id)
|
||||
_animate_exit()
|
||||
1
flumi/Scripts/Browser/DownloadProgress.gd.uid
Normal file
1
flumi/Scripts/Browser/DownloadProgress.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://kj6oda7sey5s
|
||||
76
flumi/Scripts/Browser/DownloadsStore.gd
Normal file
76
flumi/Scripts/Browser/DownloadsStore.gd
Normal file
@@ -0,0 +1,76 @@
|
||||
class_name DownloadsStore
|
||||
extends PopupPanel
|
||||
|
||||
@onready var search_line_edit: LineEdit = $Main/LineEdit
|
||||
@onready var download_entry_container: VBoxContainer = $Main/PanelContainer2/ScrollContainer/DownloadEntryContainer
|
||||
|
||||
const DOWNLOAD_ENTRY = preload("res://Scenes/BrowserMenus/download_entry.tscn")
|
||||
var save_path = "user://downloads_history.json"
|
||||
|
||||
var download_entries: Array[DownloadEntry] = []
|
||||
|
||||
func _ready():
|
||||
search_line_edit.text_changed.connect(_on_search_text_changed)
|
||||
|
||||
load_download_history()
|
||||
|
||||
func add_download_entry(download_data: Dictionary):
|
||||
var entry = DOWNLOAD_ENTRY.instantiate()
|
||||
download_entry_container.add_child(entry)
|
||||
|
||||
entry.setup_download_entry(download_data)
|
||||
|
||||
download_entries.append(entry)
|
||||
|
||||
save_download_history()
|
||||
|
||||
func _on_search_text_changed(new_text: String):
|
||||
var search_term = new_text.to_lower().strip_edges()
|
||||
|
||||
for entry in download_entries:
|
||||
if search_term.is_empty():
|
||||
entry.visible = true
|
||||
else:
|
||||
var filename = entry.get_filename().to_lower()
|
||||
var domain = entry.get_domain().to_lower()
|
||||
entry.visible = filename.contains(search_term) or domain.contains(search_term)
|
||||
|
||||
func load_download_history():
|
||||
if not FileAccess.file_exists(save_path):
|
||||
return
|
||||
|
||||
var file = FileAccess.open(save_path, FileAccess.READ)
|
||||
if not file:
|
||||
print("Could not open downloads history file for reading")
|
||||
return
|
||||
|
||||
var json_text = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var json = JSON.new()
|
||||
var parse_result = json.parse(json_text)
|
||||
if parse_result != OK:
|
||||
print("Error parsing downloads history JSON")
|
||||
return
|
||||
|
||||
var downloads_data = json.data
|
||||
if downloads_data is Array:
|
||||
for download_data in downloads_data:
|
||||
add_download_entry(download_data)
|
||||
|
||||
func save_download_history():
|
||||
var file = FileAccess.open(save_path, FileAccess.WRITE)
|
||||
if not file:
|
||||
print("Could not open downloads history file for writing")
|
||||
return
|
||||
|
||||
var downloads_data = get_download_data_array()
|
||||
var json_text = JSON.stringify(downloads_data)
|
||||
file.store_string(json_text)
|
||||
file.close()
|
||||
|
||||
func get_download_data_array() -> Array[Dictionary]:
|
||||
var data_array: Array[Dictionary] = []
|
||||
for entry in download_entries:
|
||||
data_array.append(entry.get_download_data())
|
||||
return data_array
|
||||
1
flumi/Scripts/Browser/DownloadsStore.gd.uid
Normal file
1
flumi/Scripts/Browser/DownloadsStore.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cp3geyxt0f8tn
|
||||
@@ -25,6 +25,10 @@ func _input(_event: InputEvent) -> void:
|
||||
# CTRL+H - History
|
||||
_on_options_menu_id_pressed(4)
|
||||
get_viewport().set_input_as_handled()
|
||||
elif _event.keycode == KEY_J:
|
||||
# CTRL+J - Downloads
|
||||
_on_options_menu_id_pressed(5)
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
func _on_options_menu_id_pressed(id: int) -> void:
|
||||
if id == 0: # new tab
|
||||
@@ -36,6 +40,8 @@ func _on_options_menu_id_pressed(id: int) -> void:
|
||||
OS.create_process(OS.get_executable_path(), ["--incognito"])
|
||||
if id == 4: # history
|
||||
show_history()
|
||||
if id == 5: # downloads
|
||||
show_downloads()
|
||||
if id == 10: # exit
|
||||
get_tree().quit()
|
||||
|
||||
@@ -53,3 +59,6 @@ func show_history() -> void:
|
||||
func _on_history_closed() -> void:
|
||||
if history_scene:
|
||||
history_scene.hide()
|
||||
|
||||
func show_downloads() -> void:
|
||||
main.download_manager.show_downloads_history()
|
||||
|
||||
@@ -40,7 +40,7 @@ static func load_web_font(font_info: Dictionary) -> void:
|
||||
http_request.timeout = 30.0
|
||||
|
||||
http_request.request_completed.connect(func(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray):
|
||||
if response_code == 200:
|
||||
if response_code >= 200 and response_code < 300:
|
||||
|
||||
if body.size() > 0:
|
||||
var font = FontFile.new()
|
||||
|
||||
@@ -122,7 +122,7 @@ func _on_audio_download_completed(_result: int, response_code: int, headers: Pac
|
||||
var http_request = get_children().filter(func(child): return child is HTTPRequest)[0]
|
||||
http_request.queue_free()
|
||||
|
||||
if response_code != 200:
|
||||
if response_code < 200 or response_code >= 300:
|
||||
return
|
||||
|
||||
if body.size() == 0:
|
||||
|
||||
65
flumi/Scripts/Utils/Lua/Download.gd
Normal file
65
flumi/Scripts/Utils/Lua/Download.gd
Normal file
@@ -0,0 +1,65 @@
|
||||
class_name LuaDownloadUtils
|
||||
extends RefCounted
|
||||
|
||||
static var last_user_event_time: int = 0
|
||||
static var user_event_window_ms: int = 100
|
||||
static var next_download_id: int = 1
|
||||
|
||||
static func setup_download_api(vm: LuauVM):
|
||||
vm.lua_pushcallable(_lua_download_handler, "gurt.download")
|
||||
vm.lua_getglobal("gurt")
|
||||
if vm.lua_isnil(-1):
|
||||
vm.lua_pop(1)
|
||||
vm.lua_newtable()
|
||||
vm.lua_setglobal("gurt")
|
||||
vm.lua_getglobal("gurt")
|
||||
|
||||
vm.lua_pushvalue(-2)
|
||||
vm.lua_setfield(-2, "download")
|
||||
vm.lua_pop(2)
|
||||
|
||||
static func mark_user_event():
|
||||
last_user_event_time = Time.get_ticks_msec()
|
||||
|
||||
static func _check_if_likely_user_event(current_time: int) -> bool:
|
||||
var time_since_user_event = current_time - last_user_event_time
|
||||
return time_since_user_event < user_event_window_ms
|
||||
|
||||
static func _lua_download_handler(vm: LuauVM) -> int:
|
||||
var url: String = vm.luaL_checkstring(1)
|
||||
var filename: String = ""
|
||||
|
||||
if vm.lua_gettop() >= 2 and not vm.lua_isnil(2):
|
||||
filename = vm.luaL_checkstring(2)
|
||||
else:
|
||||
filename = url.get_file()
|
||||
if filename.is_empty():
|
||||
filename = "download"
|
||||
|
||||
var current_time = Time.get_ticks_msec()
|
||||
var is_likely_user_event = _check_if_likely_user_event(current_time)
|
||||
|
||||
if not is_likely_user_event:
|
||||
vm.luaL_error("Download can only be called from within a user interaction (like a click event)")
|
||||
return 0
|
||||
|
||||
var download_id = "download_" + str(next_download_id)
|
||||
next_download_id += 1
|
||||
|
||||
var download_data = {
|
||||
"id": download_id,
|
||||
"url": url,
|
||||
"filename": filename,
|
||||
"timestamp": Time.get_unix_time_from_system()
|
||||
}
|
||||
|
||||
var lua_api = vm.get_meta("lua_api") as LuaAPI
|
||||
if lua_api:
|
||||
var operation = {
|
||||
"type": "request_download",
|
||||
"download_data": download_data
|
||||
}
|
||||
lua_api.call_deferred("_handle_dom_operation", operation)
|
||||
|
||||
vm.lua_pushstring(download_id)
|
||||
return 1
|
||||
1
flumi/Scripts/Utils/Lua/Download.gd.uid
Normal file
1
flumi/Scripts/Utils/Lua/Download.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b3nu8c4qlxj45
|
||||
@@ -16,6 +16,7 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
|
||||
if signal_node.has_signal("pressed"):
|
||||
var wrapper = func():
|
||||
LuaAudioUtils.mark_user_event()
|
||||
LuaDownloadUtils.mark_user_event()
|
||||
subscription.lua_api._on_event_triggered(subscription)
|
||||
signal_node.pressed.connect(wrapper)
|
||||
subscription.connected_signal = "pressed"
|
||||
@@ -25,6 +26,7 @@ static func connect_element_event(signal_node: Node, event_name: String, subscri
|
||||
elif signal_node is Control:
|
||||
var wrapper = func(event: InputEvent):
|
||||
LuaAudioUtils.mark_user_event()
|
||||
LuaDownloadUtils.mark_user_event()
|
||||
subscription.lua_api._on_gui_input_click(event, subscription)
|
||||
signal_node.gui_input.connect(wrapper)
|
||||
subscription.connected_signal = "gui_input"
|
||||
|
||||
@@ -379,6 +379,7 @@ func _setup_additional_lua_apis():
|
||||
LuaJSONUtils.setup_json_api(lua_vm)
|
||||
LuaWebSocketUtils.setup_websocket_api(lua_vm)
|
||||
LuaAudioUtils.setup_audio_api(lua_vm)
|
||||
LuaDownloadUtils.setup_download_api(lua_vm)
|
||||
LuaCrumbsUtils.setup_crumbs_api(lua_vm)
|
||||
LuaRegexUtils.setup_regex_api(lua_vm)
|
||||
LuaURLUtils.setup_url_api(lua_vm)
|
||||
|
||||
@@ -35,6 +35,8 @@ const AUDIO = preload("res://Scenes/Tags/audio.tscn")
|
||||
const POSTPROCESS = preload("res://Scenes/Tags/postprocess.tscn")
|
||||
const CANVAS = preload("res://Scenes/Tags/canvas.tscn")
|
||||
|
||||
const DOWNLOAD_MANAGER = preload("res://Scripts/Browser/DownloadManager.gd")
|
||||
|
||||
const MIN_SIZE = Vector2i(750, 200)
|
||||
|
||||
var font_dependent_elements: Array = []
|
||||
@@ -42,6 +44,7 @@ var current_domain = ""
|
||||
var main_navigation_request: NetworkRequest = null
|
||||
var network_start_time: float = 0.0
|
||||
var network_end_time: float = 0.0
|
||||
var download_manager: DownloadManager = null
|
||||
|
||||
func should_group_as_inline(element: HTMLParser.HTMLElement) -> bool:
|
||||
if element.tag_name == "input":
|
||||
@@ -61,6 +64,9 @@ func _ready():
|
||||
|
||||
CertificateManager.initialize()
|
||||
|
||||
download_manager = DOWNLOAD_MANAGER.new(self)
|
||||
add_child(download_manager)
|
||||
|
||||
var original_scroll = website_container.get_parent()
|
||||
if original_scroll:
|
||||
original_scroll.visible = false
|
||||
|
||||
159
tests/download.html
Normal file
159
tests/download.html
Normal file
@@ -0,0 +1,159 @@
|
||||
<head>
|
||||
<title>Download API Demo</title>
|
||||
<icon src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Lua-Logo.svg/256px-Lua-Logo.svg.png">
|
||||
<meta name="theme-color" content="#8b5cf6">
|
||||
<meta name="description" content="Demonstrating Download API functionality">
|
||||
|
||||
<style>
|
||||
body { bg-[#faf5ff] p-6 }
|
||||
h1 { text-[#8b5cf6] text-3xl font-bold text-center }
|
||||
h2 { text-[#7c3aed] text-xl font-semibold }
|
||||
h3 { text-[#6d28d9] text-lg font-semibold }
|
||||
.container { bg-[#ffffff] p-6 rounded-lg shadow-lg max-w-4xl mx-auto }
|
||||
.button-group { flex gap-3 justify-center items-center flex-wrap }
|
||||
.download-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#8b5cf6] text-white hover:bg-[#7c3aed] }
|
||||
.special-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#ef4444] text-white hover:bg-[#dc2626] }
|
||||
.clear-button { px-4 py-2 rounded-lg font-medium cursor-pointer transition-colors bg-[#6b7280] text-white hover:bg-[#4b5563] }
|
||||
.log-area { bg-[#f1f5f9] p-4 rounded-lg min-h-32 font-mono text-sm max-h-96 overflow-auto }
|
||||
.info-box { bg-[#ede9fe] border border-[#8b5cf6] p-4 rounded-lg }
|
||||
.test-section { bg-[#f9fafb] p-4 rounded-lg border mb-4 }
|
||||
.code-block { bg-[#1e293b] text-[#e2e8f0] p-3 rounded font-mono text-sm mb-3 }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
local logArea = gurt.select('#log-area')
|
||||
local clearLogBtn = gurt.select('#clear-log-btn')
|
||||
local downloadImageBtn = gurt.select('#download-image')
|
||||
local downloadShitBtn = gurt.select('#download-shit')
|
||||
local downloadTextBtn = gurt.select('#download-text')
|
||||
local downloadJsonBtn = gurt.select('#download-json')
|
||||
|
||||
trace.log('Download API demo script started.')
|
||||
|
||||
local logMessages = {}
|
||||
|
||||
-- Function to add message to log
|
||||
local function addLog(message)
|
||||
table.insert(logMessages, Time.format(Time.now(), '%H:%M:%S') .. ' - ' .. message)
|
||||
if #logMessages > 50 then
|
||||
table.remove(logMessages, 1)
|
||||
end
|
||||
logArea.text = table.concat(logMessages, '\\n')
|
||||
end
|
||||
|
||||
-- Download Image Test
|
||||
downloadImageBtn:on('click', function()
|
||||
local downloadId = gurt.download("https://httpbin.org/image/png", "test-image.png")
|
||||
addLog("🖼️ Started image download: " .. downloadId)
|
||||
addLog("URL: https://httpbin.org/image/png")
|
||||
addLog("Filename: test-image.png")
|
||||
end)
|
||||
|
||||
-- Download Text File Test
|
||||
downloadTextBtn:on('click', function()
|
||||
local downloadId = gurt.download("https://httpbin.org/robots.txt", "robots.txt")
|
||||
addLog("📄 Started text download: " .. downloadId)
|
||||
addLog("URL: https://httpbin.org/robots.txt")
|
||||
addLog("Filename: robots.txt")
|
||||
end)
|
||||
|
||||
-- Download JSON Test
|
||||
downloadJsonBtn:on('click', function()
|
||||
local downloadId = gurt.download("https://httpbin.org/json", "sample-data.json")
|
||||
addLog("📊 Started JSON download: " .. downloadId)
|
||||
addLog("URL: https://httpbin.org/json")
|
||||
addLog("Filename: sample-data.json")
|
||||
end)
|
||||
|
||||
-- Download Large File Test (Ubuntu ISO)
|
||||
downloadShitBtn:on('click', function()
|
||||
local downloadId = gurt.download("https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-desktop-amd64.iso", "linux.iso")
|
||||
addLog("💿 Started large file download: " .. downloadId)
|
||||
addLog("URL: https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-desktop-amd64.iso")
|
||||
addLog("Filename: linux.iso")
|
||||
addLog("⚠️ Warning: This is a large file (~5GB)!")
|
||||
end)
|
||||
|
||||
-- Clear log button
|
||||
clearLogBtn:on('click', function()
|
||||
logMessages = {}
|
||||
logArea.text = 'Log cleared.'
|
||||
addLog('Download API demo ready')
|
||||
end)
|
||||
|
||||
-- Initialize
|
||||
addLog('Download API demo ready')
|
||||
addLog('Click the buttons above to test different download scenarios!')
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>📥 Download API Demo</h1>
|
||||
|
||||
<div style="container mt-6">
|
||||
<div style="info-box mb-6">
|
||||
<h3 style="text-[#5b21b6] font-semibold mb-2">Download API Usage:</h3>
|
||||
<p><code>local downloadId = gurt.download(url, filename)</code></p>
|
||||
<p>Returns a unique download ID that can be used to track the download progress.</p>
|
||||
</div>
|
||||
|
||||
<div style="test-section">
|
||||
<h3>Download API Test</h3>
|
||||
<p>Click the buttons below to test the download functionality:</p>
|
||||
<div style="button-group mb-4">
|
||||
<button id="download-image" style="download-button">🖼️ Download Test Image</button>
|
||||
<button id="download-text" style="download-button">📄 Download Text File</button>
|
||||
<button id="download-json" style="download-button">📊 Download JSON Data</button>
|
||||
</div>
|
||||
<div style="button-group mb-4">
|
||||
<button id="download-shit" style="special-button">💿 Download Shit (Ubuntu ISO)</button>
|
||||
</div>
|
||||
|
||||
<div style="code-block">
|
||||
gurt.select("#download-image"):on("click", function()
|
||||
local downloadId = gurt.download("https://httpbin.org/image/png", "test-image.png")
|
||||
print("Started download:", downloadId)
|
||||
end)
|
||||
|
||||
gurt.select("#download-text"):on("click", function()
|
||||
local downloadId = gurt.download("https://httpbin.org/robots.txt", "robots.txt")
|
||||
print("Started text download:", downloadId)
|
||||
end)
|
||||
|
||||
gurt.select("#download-json"):on("click", function()
|
||||
local downloadId = gurt.download("https://httpbin.org/json", "sample-data.json")
|
||||
print("Started JSON download:", downloadId)
|
||||
end)
|
||||
|
||||
gurt.select("#download-shit"):on("click", function()
|
||||
local downloadId = gurt.download("https://releases.ubuntu.com/24.04.3/ubuntu-24.04.3-desktop-amd64.iso", "linux.iso")
|
||||
end)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Download Log</h2>
|
||||
<div style="button-group mb-3">
|
||||
<button id="clear-log-btn" style="clear-button">🧹 Clear Log</button>
|
||||
</div>
|
||||
<div style="log-area mb-6">
|
||||
<pre id="log-area">Initializing...</pre>
|
||||
</div>
|
||||
|
||||
<div style="bg-[#f0f9ff] p-4 rounded-lg">
|
||||
<h3 style="text-[#0369a1] font-semibold mb-2">Download API Features:</h3>
|
||||
<ul style="text-[#075985] space-y-1 text-sm">
|
||||
<li><strong>gurt.download(url, filename):</strong> Initiates a download from the given URL</li>
|
||||
<li><strong>Return Value:</strong> Returns a unique download ID for tracking</li>
|
||||
<li><strong>File Types:</strong> Supports any file type (images, text, binary, etc.)</li>
|
||||
<li><strong>Large Files:</strong> Can handle large downloads like OS images</li>
|
||||
</ul>
|
||||
<h3 style="text-[#0369a1] font-semibold mt-4 mb-2">Test Cases:</h3>
|
||||
<ul style="text-[#075985] space-y-1 text-sm">
|
||||
<li><strong>Image Download:</strong> PNG image from httpbin.org</li>
|
||||
<li><strong>Text Download:</strong> robots.txt file</li>
|
||||
<li><strong>JSON Download:</strong> Sample JSON data</li>
|
||||
<li><strong>Large File:</strong> Ubuntu 24.04.3 ISO (~5GB) - Use with caution!</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
Reference in New Issue
Block a user