Compare commits

...

7 Commits

16 changed files with 832 additions and 11 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
# Godot 4+ specific ignores
.godot/
.env

22
Classes/DotEnv.gd Normal file
View File

@ -0,0 +1,22 @@
extends Node
func _init() -> void:
var path = ""
if OS.has_feature("editor"):
path = ProjectSettings.globalize_path("res://.env")
else:
path = OS.get_executable_path().get_base_dir().path_join(".env")
if !FileAccess.file_exists(path):
return
var f := FileAccess.open(path, FileAccess.READ)
while !f.eof_reached():
var line := f.get_line()
var split := line.split("=")
OS.set_environment(split[0].strip_edges(), split[1].strip_edges())
func _ready() -> void:
queue_free()

447
Classes/ReleasesManager.gd Normal file
View File

@ -0,0 +1,447 @@
extends Node
const API_SLUG := "https://api.github.com/repos/"
const RELEASES_SLUG := "godotengine/godot/releases"
const ASSET_SLUG := "assets/%s"
const RELEASES_BASE_FOLDER := "user://releases"
const RELEASES_FILE := RELEASES_BASE_FOLDER + "/releases.json"
const TEMP_FOLDER := "user://_tmp/"
const FETCH_COOLDOWN := 60.0 # seconds
var releases: Releases
var github_token: String = OS.get_environment("GITHUB_TOKEN")
var http: HTTPRequest = HTTPRequest.new()
var is_downloading: bool = false
var threads: Array[Thread]
signal releases_fetched(releases: Releases)
signal version_downloaded(version: String)
signal export_templates_downloaded(version: String)
signal status_update(status: String)
enum Status{
IDLE,
RELEASE_DOWNLOAD_IN_PROGRESS,
TEMPLATE_DOWNLOAD_IN_PROGRESS,
DOWNLOAD_DONE,
FETCHING_RELEASES,
FETCHING_DONE,
VERSION_NOT_IN_INDEX,
RELEASE_ALREADY_EXISTS,
TEMPLATE_ALREADY_EXISTS,
UNPACKING_TEMPLATES,
UNPACKING_RELEASE,
RELEASE_INSTALLED,
TEMPLATE_INSTALLED,
}
const STATUS_MESSAGES := {
Status.IDLE: "Idle",
Status.RELEASE_DOWNLOAD_IN_PROGRESS: "Downloading Godot v%s",
Status.TEMPLATE_DOWNLOAD_IN_PROGRESS: "Downloading Export Templates for v%s",
Status.DOWNLOAD_DONE: "Done downloading",
Status.FETCHING_RELEASES: "Fetching releases",
Status.FETCHING_DONE: "Done fetching releases",
Status.VERSION_NOT_IN_INDEX: "Version not in index. Fetch first",
Status.RELEASE_ALREADY_EXISTS: "This version already exists",
Status.TEMPLATE_ALREADY_EXISTS: "Templates for this version already exist",
Status.UNPACKING_TEMPLATES: "Unpacking Export Templates for v%s",
Status.UNPACKING_RELEASE: "Unpacking Godot v%s",
Status.RELEASE_INSTALLED: "Godot v%s installed",
Status.TEMPLATE_INSTALLED: "Export Templates for Godot v%s installed",
}
func _ready() -> void:
DirAccess.make_dir_absolute(TEMP_FOLDER)
DirAccess.make_dir_absolute(RELEASES_BASE_FOLDER)
add_child(http)
releases = load_releases()
clean_tmp()
func fetch_releases(force_local: bool = false) -> void:
if force_local && FileAccess.file_exists(RELEASES_FILE):
releases_fetched.emit(releases)
return
if is_downloading:
print("Another download is in progress, try again later")
return
if releases.last_checked_at + FETCH_COOLDOWN > Time.get_unix_time_from_system():
print("already fetched recently, returning local")
releases_fetched.emit(releases)
return
var response_func = func(result: int, _response_code: int, _headers: PackedStringArray, body: PackedByteArray):
if result != OK:
return
releases.clear()
releases.last_checked_at = Time.get_unix_time_from_system()
var d: Array = JSON.parse_string(body.get_string_from_ascii())
for i in d:
# tag name is version name
var rm := releases.get_or_create_version_metadata(i.tag_name)
# we already have this one
if rm.binary_github_asset_id != -1:
continue
var file_name_placeholder := "Godot_v%s.zip"
# only linux for now
var file_name_template: String = "%s_linux.x86_64" % i.tag_name
var file_name_template_x11: String = "%s_x11.64" % i.tag_name
var file_name := file_name_placeholder % file_name_template
var file_name_x11 := file_name_placeholder % file_name_template_x11
var export_template: String = "Godot_v%s_export_templates.tpz" % i.tag_name
# go through assets of this release and find our os
for asset in i.assets:
if asset.name == file_name || asset.name == file_name_x11:
rm.binary_github_asset_id = asset.id
rm.binary_github_filename = asset.name
if asset.name == export_template:
rm.export_github_asset_id = asset.id
rm.export_github_filename = asset.name
# if we still have an empty item, it's too old to use
# (meaning it's not mirrored on github)
# so we remove it
if rm.is_empty():
releases.releases.erase(i.tag_name)
releases_fetched.emit(releases)
status_update.emit(STATUS_MESSAGES[Status.FETCHING_DONE])
is_downloading = false
save_releases()
var headers := [
"Accept: application/vnd.github+json",
"Authorization: Bearer %s" % github_token,
"X-GitHub-Api-Version: 2022-11-28",
]
is_downloading = true
status_update.emit(STATUS_MESSAGES[Status.FETCHING_RELEASES])
http.request(API_SLUG + RELEASES_SLUG, PackedStringArray(headers))
http.request_completed.connect(response_func, CONNECT_ONE_SHOT)
func download_release(version: String) -> void:
if is_downloading:
print("downloading something, try again later")
return
if !releases.releases.has(version):
# print("this version is not in the index yet or does not exist. fetch first")
status_update.emit(STATUS_MESSAGES[Status.VERSION_NOT_IN_INDEX])
return
if (releases.releases[version] as ReleaseMetadata).binary_path != "":
# print("already have this version")
status_update.emit(STATUS_MESSAGES[Status.RELEASE_ALREADY_EXISTS])
return
var rm: ReleaseMetadata = releases.releases[version]
var response_func = func(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray, filename: String):
if response_code == 200:
# download happens in place.
# we know the file is zip, so save it to temp first before unpacking
status_update.emit(STATUS_MESSAGES[Status.DOWNLOAD_DONE])
print("got it in place")
print("downloading url: ", rm.binary_github_filename)
var zip_filename := TEMP_FOLDER.path_join("%s.zip" % version)
print("making zip: ", zip_filename)
var f := FileAccess.open(zip_filename, FileAccess.WRITE)
if f.get_error() != OK:
print("zip file creation error:", f.get_error())
return
f.store_buffer(body)
f.close()
var zip := ZIPReader.new()
var zerr := zip.open(zip_filename)
if zerr != OK:
print("error opening zip file: ", zerr)
return
var res := zip.read_file(filename.trim_suffix(".zip"))
zip.close()
clean_tmp()
status_update.emit(STATUS_MESSAGES[Status.UNPACKING_RELEASE] % version)
var d := DirAccess.open(RELEASES_BASE_FOLDER)
d.make_dir_recursive(version)
var new_file := RELEASES_BASE_FOLDER.path_join(version)
new_file = new_file.path_join("godot")
f = FileAccess.open(new_file, FileAccess.WRITE)
f.store_buffer(res)
f.close()
rm.binary_path = new_file
version_downloaded.emit(version)
status_update.emit(STATUS_MESSAGES[Status.RELEASE_INSTALLED] % version)
is_downloading = false
save_releases()
elif response_code == 302:
# not in place
print("tried downloading, but got a 302 which is not currently supported")
var headers = [
"Accept: application/octet-stream",
"Authorization: Bearer %s" % github_token,
"X-GitHub-Api-Version: 2022-11-28",
]
var asset_url = API_SLUG.path_join(RELEASES_SLUG).path_join(ASSET_SLUG) % rm.binary_github_asset_id
status_update.emit(STATUS_MESSAGES[Status.RELEASE_DOWNLOAD_IN_PROGRESS] % version)
is_downloading = true
http.request(asset_url, PackedStringArray(headers))
http.request_completed.connect(response_func.bind(rm.binary_github_filename), CONNECT_ONE_SHOT)
func download_export_templates(version: String) -> void:
if is_downloading:
print("downloading something, try again later")
return
if !releases.releases.has(version):
# print("this version is not in the index yet or does not exist. fetch first")
status_update.emit(STATUS_MESSAGES[Status.VERSION_NOT_IN_INDEX])
return
if (releases.releases[version] as ReleaseMetadata).export_templates_path != "":
# print("already have templates for this version")
status_update.emit(STATUS_MESSAGES[Status.TEMPLATE_ALREADY_EXISTS])
return
var rm: ReleaseMetadata = releases.releases[version]
var response_func = func(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray):
if response_code == 200:
status_update.emit(STATUS_MESSAGES[Status.DOWNLOAD_DONE])
print("got it in place")
print("downloading url: ", rm.export_github_filename)
var zip_filename := TEMP_FOLDER.path_join("%s_export.tpz" % version)
print("making zip: ", zip_filename)
var f := FileAccess.open(zip_filename, FileAccess.WRITE)
if f.get_error() != OK:
print("zip file creation error: ", f.get_error())
return
f.store_buffer(body)
f.close()
var zip := ZIPReader.new()
var zerr := zip.open(zip_filename)
if zerr != OK:
print("error opening zip file: ", zerr)
return
var files := zip.get_files()
var d := DirAccess.open(RELEASES_BASE_FOLDER)
d.make_dir_recursive(version.path_join("templates"))
var templates_folder := RELEASES_BASE_FOLDER.path_join(version)
status_update.emit(STATUS_MESSAGES[Status.UNPACKING_TEMPLATES] % version)
for file in files:
var t := Thread.new()
threads.append(t)
t.start(unpack_zip_file.bind(zip_filename, file, templates_folder.path_join(file), t, version))
zip.close()
# don't clean tmp just yet, since there might be other threads
# that are still unpacking from the same zip.
# side note: why do we have to have the zip in filesystem instead
# of memory? this would be much easier...
rm.export_templates_path = ProjectSettings.globalize_path(templates_folder.path_join("templates"))
export_templates_downloaded.emit(version)
is_downloading = false
save_releases()
elif response_code == 302:
# not in place
print("tried downloading, but got a 302 which is not currently supported")
var headers = [
"Accept: application/octet-stream",
"Authorization: Bearer %s" % github_token,
"X-GitHub-Api-Version: 2022-11-28",
]
var asset_url = API_SLUG.path_join(RELEASES_SLUG).path_join(ASSET_SLUG) % rm.export_github_asset_id
status_update.emit(STATUS_MESSAGES[Status.TEMPLATE_DOWNLOAD_IN_PROGRESS] % version)
is_downloading = true
http.request(asset_url, PackedStringArray(headers))
http.request_completed.connect(response_func, CONNECT_ONE_SHOT)
func unpack_zip_file(zip_file: String, from_file: String, dest_file: String, thread: Thread, version: String) -> void:
print("extracting file ", from_file, " to: ", dest_file)
var z := ZIPReader.new()
var f := z.open(zip_file)
if f != OK:
print("error opening zip file: ", zip_file, " from thread ", thread, ", exiting early.")
_clean_threads.call_deferred(thread)
return
var file := FileAccess.open(dest_file, FileAccess.WRITE)
file.store_buffer(z.read_file(from_file))
file.close()
z.close()
_clean_threads.call_deferred(thread, version)
func _clean_threads(thread: Thread, version: String) -> void:
threads.erase(thread)
thread.wait_to_finish()
if threads.is_empty():
status_update.emit(STATUS_MESSAGES[Status.TEMPLATE_INSTALLED] % version)
clean_tmp()
func save_releases() -> void:
var f := FileAccess.open(RELEASES_FILE, FileAccess.WRITE)
f.store_string(releases.to_json())
func load_releases() -> Releases:
if !FileAccess.file_exists(RELEASES_FILE):
return Releases.new()
var f := FileAccess.open(RELEASES_FILE, FileAccess.READ)
return Releases.from_json(f.get_as_text())
func _exit_tree() -> void:
for t in threads:
t.wait_to_finish()
clean_tmp()
func get_version_metadata(version: String) -> ReleaseMetadata:
return releases.releases.get(version)
func get_installed_versions() -> Dictionary:
var res: Dictionary
for version in releases.releases:
if is_version_installed(version):
res[version] = releases.releases[version]
return res
func clean_tmp() -> void:
var d := DirAccess.open(TEMP_FOLDER)
if !d:
return
var to_delete: Array[String] = []
d.list_dir_begin()
var file_name := d.get_next()
while file_name != "":
to_delete.append(file_name)
file_name = d.get_next()
d.list_dir_end()
for f in to_delete:
d.remove(f)
# helper methods
func is_version_installed(version: String) -> bool:
return get_version_metadata(version).binary_path != ""
func is_version_templates_installed(version) -> bool:
return get_version_metadata(version).export_templates_path != ""
class ReleaseMetadata:
var binary_path: String
var is_mono: bool
var export_templates_path: String
var binary_github_asset_id: int = -1
var binary_github_filename: String
var export_github_asset_id: int = -1
var export_github_filename: String
func is_empty() -> bool:
return \
binary_github_asset_id == -1 && export_github_asset_id == -1
func to_d() -> Dictionary:
return {
"binary_path": binary_path,
"is_mono": is_mono,
"export_templates_path": export_templates_path,
"binary_github_asset_id": binary_github_asset_id,
"export_github_asset_id": export_github_asset_id,
"binary_github_filename": binary_github_filename,
"export_github_filename": export_github_filename,
}
static func from_d(d: Dictionary) -> ReleaseMetadata:
var res := ReleaseMetadata.new()
res.binary_path = d.binary_path
res.is_mono = d.is_mono
res.export_templates_path = d.export_templates_path
res.binary_github_asset_id = d.binary_github_asset_id
res.export_github_asset_id = d.export_github_asset_id
res.binary_github_filename = d.binary_github_filename
res.export_github_filename = d.export_github_filename
return res
class Releases:
# version[String]: ReleaseMetadata
var releases: Dictionary
var last_checked_at: float
var os: String
func clear() -> void:
for r in releases.keys():
if (releases[r] as ReleaseMetadata).binary_path == "":
releases.erase(r)
func get_or_create_version_metadata(version: String) -> ReleaseMetadata:
if releases.has(version):
return releases[version]
var r := ReleaseMetadata.new()
releases[version] = r
return r
func is_empty() -> bool:
return releases.is_empty()
func to_json() -> String:
var releases_dict := {}
for i in releases:
releases_dict[i] = (releases[i] as ReleaseMetadata).to_d()
var d := {
"os": os,
"last_checked_at": last_checked_at,
"releases": releases_dict,
}
return JSON.stringify(d, "\t", false)
static func from_json(json: String) -> Releases:
var d: Dictionary = JSON.parse_string(json)
var releases_dict := {}
for i in d.releases:
releases_dict[i] = ReleaseMetadata.from_d(d.releases[i])
var r := Releases.new()
r.releases = releases_dict
r.os = d.os
r.last_checked_at = d.last_checked_at
return r

View File

@ -1,6 +1,8 @@
extends VBoxContainer
class_name GroupMenu
const ITEM_SCENE := preload("res://UI/Components/InstallItem.tscn")
@onready var collapse_button: Button = %CollapseButton
@onready var header_icon: TextureRect = %HeaderIcon
@onready var header_text_label: Label = %HeaderTextLabel
@ -16,6 +18,8 @@ class_name GroupMenu
@onready var header_container: HBoxContainer = %HeaderContainer
@onready var install_edit_dialog: InstallEditDialog = %InstallEditDialog
var group_metadata: GroupMetadata
signal add_install_button_pressed
@ -32,8 +36,8 @@ func _ready() -> void:
set_children_visible(b.button_pressed)
)
add_install_button.pressed.connect(func(): add_install_button_pressed.emit())
add_install_button.pressed.connect(add_new_install)
install_edit_dialog.canceled.connect(install_edit_dialog.clear_ok_button_connections)
# edit_group_button.pressed.connect(setup_rename)
edit_group_button.pressed.connect(func(): edit_group_button_pressed.emit())
delete_group_button.pressed.connect(func(): delete_button_pressed.emit())
@ -41,9 +45,34 @@ func _ready() -> void:
header_text_label.gui_input.connect(_on_header_text_label_gui_input)
func add(item: InstallItem) -> int:
child_container.add_child(item)
return child_container.get_child_count()
func add_new_install() -> void:
install_edit_dialog.clear()
install_edit_dialog.ok_button_text = "Create"
install_edit_dialog.confirmed.connect(
func():
var im := install_edit_dialog.get_install_metadata()
var install_item: InstallItem = ITEM_SCENE.instantiate()
child_container.add_child(install_item)
install_item.setup_from_metadata(install_edit_dialog.get_install_metadata())
install_item.edit_item_button_pressed.connect(edit_install.bind(install_item))
group_metadata.installs.append(install_edit_dialog.get_install_metadata())
, CONNECT_ONE_SHOT)
install_edit_dialog.show()
func edit_install(install_item: InstallItem) -> void:
install_edit_dialog.clear()
install_edit_dialog.ok_button_text = "OK"
install_edit_dialog.install_metadata = install_item.install_metadata
install_edit_dialog.fill_from_install_metadata()
install_edit_dialog.confirmed.connect(
func():
install_item.setup_from_metadata(install_edit_dialog.get_install_metadata())
, CONNECT_ONE_SHOT)
install_edit_dialog.show()
func free_item(at_idx: int) -> void:

View File

@ -1,7 +1,8 @@
[gd_scene load_steps=3 format=3 uid="uid://bh1nf1xrowlpv"]
[gd_scene load_steps=4 format=3 uid="uid://bh1nf1xrowlpv"]
[ext_resource type="Script" path="res://UI/Components/GroupMenu.gd" id="1_h3o1w"]
[ext_resource type="Texture2D" uid="uid://de57eeeobflp4" path="res://icon.svg" id="2_crg6a"]
[ext_resource type="PackedScene" uid="uid://u0de32p0bb61" path="res://UI/Dialogs/InstallEditDialog.tscn" id="3_bapn7"]
[node name="GroupMenu" type="VBoxContainer"]
offset_right = 299.0
@ -78,3 +79,6 @@ text = "New Install"
[node name="HSeparator3" type="HSeparator" parent="."]
layout_mode = 2
theme_override_constants/separation = 16
[node name="InstallEditDialog" parent="." instance=ExtResource("3_bapn7")]
unique_name_in_owner = true

View File

@ -27,6 +27,10 @@ func set_icon(icon: Texture2D) -> void:
item_icon.texture = icon
func set_text(n: String) -> void:
name_label.text = n
func set_icon_visible(p_visible: bool) -> void:
item_icon.visible = p_visible
@ -60,3 +64,9 @@ func _on_name_label_gui_input(event: InputEvent) -> void:
(event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT:
get_viewport().set_input_as_handled()
setup_rename()
func setup_from_metadata(p_metadata: InstallMetadata) -> void:
install_metadata = p_metadata
set_text(install_metadata.name)
# TODO: add icon

View File

@ -26,6 +26,7 @@ layout_mode = 2
size_flags_horizontal = 3
mouse_filter = 1
vertical_alignment = 1
clip_text = true
[node name="EditItemButton" type="Button" parent="."]
unique_name_in_owner = true
@ -40,6 +41,7 @@ text = "X"
[node name="LaunchItemButton" type="Button" parent="."]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Launch"
text = ">"
[node name="Indent2" type="MarginContainer" parent="."]

View File

@ -14,6 +14,7 @@ text = "Editor Settings overrides"
[node name="OverrideItemContainer" type="VBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="CenterContainer" type="CenterContainer" parent="."]
layout_mode = 2

View File

@ -0,0 +1,47 @@
extends HBoxContainer
class_name ReleaseItem
@onready var version_label: Label = %VersionLabel
@onready var install_button: Button = %InstallButton
@onready var install_templates_button: Button = %InstallTemplatesButton
var version: String
signal install_button_pressed(version)
signal install_templates_button_pressed(version)
func _ready() -> void:
install_button.pressed.connect(func(): install_button_pressed.emit(version))
install_templates_button.pressed.connect(func(): install_templates_button_pressed.emit(version))
func set_version(v: String) -> void:
version = v
version_label.text = "Godot v%s" % v
func set_install_button_disabled(d: bool) -> void:
install_button.disabled = d
func set_templates_button_disabled(d: bool) -> void:
install_templates_button.disabled = d
func _on_version_downloaded(p_version: String) -> void:
if p_version != version:
# this isn't us
return
set_install_button_disabled(true)
ReleasesManager.version_downloaded.disconnect(_on_version_downloaded)
func _on_export_templates_downloaded(p_version: String) -> void:
if p_version != version:
# this isn't us
return
set_templates_button_disabled(true)
ReleasesManager.export_templates_downloaded.disconnect(_on_export_templates_downloaded)

View File

@ -0,0 +1,28 @@
[gd_scene load_steps=2 format=3 uid="uid://wpersx0asfqo"]
[ext_resource type="Script" path="res://UI/Components/ReleaseItem.gd" id="1_r5q2c"]
[node name="ReleaseItem" type="HBoxContainer"]
offset_right = 512.0
offset_bottom = 39.0
size_flags_horizontal = 3
script = ExtResource("1_r5q2c")
[node name="VersionLabel" type="Label" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
alignment = 2
[node name="InstallButton" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Install"
[node name="InstallTemplatesButton" type="Button" parent="HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Export Templates"

View File

@ -10,10 +10,10 @@ size = Vector2i(678, 419)
script = ExtResource("1_ic3yh")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 12.0
offset_top = 12.0
offset_right = 666.0
offset_bottom = 366.0
offset_left = 8.0
offset_top = 8.0
offset_right = 670.0
offset_bottom = 370.0
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2

View File

@ -0,0 +1,53 @@
extends ConfirmationDialog
class_name InstallEditDialog
@onready var name_line_edit: LineEdit = %NameLineEdit
@onready var icon_path_line_edit: LineEdit = %IconPathLineEdit
@onready var open_icon_file_dialog_button: Button = %OpenIconFileDialogButton
@onready var override_editor_settings_menu: OverrideEditorSettingsMenu = %OverrideEditorSettingsMenu
@onready var version_selector: OptionButton = %VersionSelector
var install_metadata: InstallMetadata
func clear() -> void:
name_line_edit.clear()
icon_path_line_edit.clear()
override_editor_settings_menu.clear()
install_metadata = InstallMetadata.new()
version_selector.clear()
for version in ReleasesManager.get_installed_versions():
version_selector.add_item(version)
version_selector.set_item_metadata(
version_selector.get_item_count() - 1,
ReleasesManager.get_version_metadata(version).binary_path)
func get_install_metadata() -> InstallMetadata:
install_metadata.name = name_line_edit.text
install_metadata.icon_path = icon_path_line_edit.text
install_metadata.local_overrides = override_editor_settings_menu.get_overrides()
install_metadata.binary_path = version_selector.get_selected_metadata()
return install_metadata
func fill_from_install_metadata() -> void:
name_line_edit.text = install_metadata.name
icon_path_line_edit.text = install_metadata.icon_path
override_editor_settings_menu.fill_from_dictionary(install_metadata.local_overrides)
var item_id: int = -1
for i in version_selector.get_item_count():
if version_selector.get_item_metadata(i) == install_metadata.binary_path:
item_id = i
break
version_selector.select(item_id)
func clear_ok_button_connections() -> void:
for connection in get_signal_connection_list("confirmed"):
var callable := connection["callable"] as Callable
var _signal := connection["signal"] as Signal
_signal.disconnect(callable)

View File

@ -0,0 +1,77 @@
[gd_scene load_steps=3 format=3 uid="uid://u0de32p0bb61"]
[ext_resource type="Script" path="res://UI/Dialogs/InstallEditDialog.gd" id="1_qfdve"]
[ext_resource type="PackedScene" uid="uid://ig4wkr1lxvbv" path="res://UI/Components/OverrideEditorSettingsMenu/OverrideEditorSettingsMenu.tscn" id="2_elqgy"]
[node name="InstallEditDialog" type="ConfirmationDialog"]
initial_position = 1
title = "Edit Install"
size = Vector2i(678, 419)
script = ExtResource("1_qfdve")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
offset_left = 8.0
offset_top = 8.0
offset_right = 670.0
offset_bottom = 370.0
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Name"
[node name="NameLineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer2"]
layout_mode = 2
text = "Icon"
[node name="IconPathLineEdit" type="LineEdit" parent="VBoxContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Optional"
[node name="OpenIconFileDialogButton" type="Button" parent="VBoxContainer/HBoxContainer2"]
unique_name_in_owner = true
layout_mode = 2
text = "F"
[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer3"]
layout_mode = 2
text = "Godot Version"
[node name="VersionSelector" type="OptionButton" parent="VBoxContainer/HBoxContainer3"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
custom_minimum_size = Vector2(0, 150)
layout_mode = 2
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="OverrideEditorSettingsMenu" parent="VBoxContainer/ScrollContainer" instance=ExtResource("2_elqgy")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="CenterContainer" type="CenterContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/CenterContainer"]
layout_mode = 2
text = "Install overrides take precedence over group overrides."
horizontal_alignment = 1

43
UI/Main/ReleasesView.gd Normal file
View File

@ -0,0 +1,43 @@
extends VBoxContainer
const ITEM_SCENE := preload("res://UI/Components/ReleaseItem.tscn")
@onready var release_items_container: VBoxContainer = %ReleaseItemsContainer
@onready var fetch_button: Button = %FetchButton
@onready var status_label: Label = %StatusLabel
func _ready() -> void:
ReleasesManager.releases_fetched.connect(releases_updated)
ReleasesManager.releases_fetched.connect(func(_x):
await get_tree().process_frame
fetch_button.disabled = false
)
fetch_button.pressed.connect(func():
ReleasesManager.fetch_releases()
fetch_button.disabled = true
)
ReleasesManager.fetch_releases(true)
ReleasesManager.status_update.connect(status_label.set_text)
func releases_updated(releases: ReleasesManager.Releases) -> void:
clear_releases()
for version in releases.releases:
var item: ReleaseItem = ITEM_SCENE.instantiate()
release_items_container.add_child(item)
item.set_version(version)
item.set_install_button_disabled(ReleasesManager.is_version_installed(version))
item.set_templates_button_disabled(ReleasesManager.is_version_templates_installed(version))
item.install_button_pressed.connect(ReleasesManager.download_release)
item.install_templates_button_pressed.connect(ReleasesManager.download_release)
ReleasesManager.version_downloaded.connect(item._on_version_downloaded)
ReleasesManager.export_templates_downloaded.connect(item._on_export_templates_downloaded)
func clear_releases() -> void:
for i in release_items_container.get_children():
i.queue_free()

50
UI/Main/ReleasesView.tscn Normal file
View File

@ -0,0 +1,50 @@
[gd_scene load_steps=2 format=3 uid="uid://irfav51b5hf6"]
[ext_resource type="Script" path="res://UI/Main/ReleasesView.gd" id="1_firao"]
[node name="ReleasesView" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_firao")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Godots"
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer"]
layout_mode = 2
[node name="ReleaseItemsContainer" type="VBoxContainer" parent="PanelContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="."]
layout_mode = 2
alignment = 1
[node name="CenterContainer" type="CenterContainer" parent="HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="FetchButton" type="Button" parent="HBoxContainer/CenterContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Fetch"
[node name="HBoxContainer" type="HBoxContainer" parent="HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
alignment = 2
[node name="StatusLabel" type="Label" parent="HBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
text = "Idle"

View File

@ -11,10 +11,17 @@ config_version=5
[application]
config/name="yagvm"
run/main_scene="res://UI/Main/GroupsView.tscn"
config/use_custom_user_dir=true
config/custom_user_dir_name="yagvm"
config/features=PackedStringArray("4.0", "Forward Plus")
run/low_processor_mode=true
config/icon="res://icon.svg"
[autoload]
DotEnv="*res://Classes/DotEnv.gd"
ReleasesManager="*res://Classes/ReleasesManager.gd"
[display]
window/subwindows/embed_subwindows=false