diff --git a/ServerUI.gd b/ServerUI.gd new file mode 100644 index 0000000..ff3fc3a --- /dev/null +++ b/ServerUI.gd @@ -0,0 +1,208 @@ +extends Control + +const SERVER_STATUS_TEXT: Dictionary = { + "RUNNING": "Server is running!", + "NOT_RUNNING": "Server is not running." +} + +const ACCEPTED_FILE_FORMATS := ["md"] # server should ignore these file types when adding endpoints + +enum ContextMenuOptions { + MOVE_UP, + MOVE_DOWN, +} + +onready var server_path_label := $"%ServerPathLabel" +onready var port_spin_box := $"%PortSpinBox" +onready var server_status_label := $"%ServerStatusLabel" + +onready var document_title_lineedit := $"%DocTitleLineEdit" +onready var document_date_lineedit := $"%DocDateLineEdit" + +onready var file_tree := $"%FileTree" +onready var file_tree_context_menu := $"%FileTreeContextMenu" + +onready var document_input_textedit := $"%DocInputTextEdit" +onready var content_preview_richtextlabel := $"%ContentPreviewRichTextLabel" + +onready var server_folder_dialog := $"%ServerFolderDialog" + +signal server_folder_changed(new_path) # new_path: String +signal server_port_changed(new_port) # new_port: int +signal start_server_button_pressed(port) # port: int +signal open_browser_button_pressed(port) # port: int + +signal files_selection_changed(new_files) # new_files: Array + +var server_dir: String setget set_server_dir +var is_server_running: bool = false setget set_server_running + +var working_files: Array = [] # Array + +var current_file: FileDef = null + + +func _ready() -> void: + # TODO: put this in a config file + if !OS.has_feature("editor"): + self.server_dir = OS.get_executable_path().get_base_dir() + else: + self.server_dir = ProjectSettings.globalize_path("res://") + + +func set_server_dir(dir: String) -> void: + server_dir = dir + server_path_label.text = dir + + file_tree.clear() + working_files.clear() + + var root: TreeItem = file_tree.create_item() + root.set_text(0, "Server files") + + var directory := Directory.new() + if directory.open(dir) == OK: + directory.list_dir_begin() + var file_name: String = directory.get_next() + while file_name != "": + if !directory.current_is_dir() && (file_name.get_extension() in ACCEPTED_FILE_FORMATS): + var fd = FileDef(file_name, false) + working_files.append(fd) + + var in_filestxt := _is_file_in_filestxt(file_name) + + var item: TreeItem = file_tree.create_item(root) + item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK) + item.set_metadata(0, fd) + item.set_editable(0, true) + item.set_text(0, file_name) + + item.set_checked(0, in_filestxt) + fd.include_in_filestxt = in_filestxt + + file_name = directory.get_next() + + directory.list_dir_end() + + +func _on_FileTree_item_edited() -> void: + var item: TreeItem = file_tree.get_edited() + var fd: FileDef = item.get_metadata(0) + fd.include_in_filestxt = item.is_checked(0) + + +func _on_FileTree_item_selected() -> void: + var item: TreeItem = file_tree.get_selected() + current_file = item.get_metadata(0) as FileDef + + if !current_file.dirty: + var file_metadata := _get_file_metadata(current_file.file_path) + current_file.title = file_metadata["title"] + current_file.date = file_metadata["date"] + current_file.content = file_metadata["content"] + + document_input_textedit.text = current_file.content + document_date_lineedit.text = current_file.date + document_title_lineedit.text = current_file.title + + +func _on_FileTree_item_rmb_selected(position: Vector2) -> void: +# var item: TreeItem = file_tree.get_selected() + file_tree_context_menu.rect_position = position + file_tree.rect_global_position + file_tree_context_menu.popup() + + +func _on_OpenServerFolderButton_pressed() -> void: + server_folder_dialog.popup() + + +func set_server_running(running: bool) -> void: + is_server_running = running # TODO: logic for disabling and enabling certain nodes + + +func _generate_filestxt() -> String: + var files := "" + + for file in working_files: + file = file as FileDef + if file.include_in_filestxt: + files += "%s %s %s\n" % [file.file_path, file.date, file.title] + + return files + + +func _is_file_in_filestxt(path: String) -> bool: + var f := File.new() + if f.open(server_dir.plus_file("files.txt"), File.READ) == OK: + var text := f.get_as_text() + return path in text + else: + return false + + +func _get_file_metadata(path: String) -> Dictionary: + var f := File.new() + + var res = {} + + if f.open(server_dir.plus_file("files.txt"), File.READ) == OK: + var text := f.get_as_text() + var lines := text.split("\n") + for line in lines: + line = line as String + if line.empty(): + res = {} + var def = line.split(" ", true, 2) + if def[0] == path: + res["file_path"] = def[0] + res["date"] = def[1] if def.size() > 1 else "" + res["title"] = def[2] if def.size() > 2 else "" + break + + if f.open(server_dir.plus_file(path), File.READ) == OK: + res["content"] = f.get_as_text() + + return res + + +func _on_DocTitleLineEdit_text_changed(new_text: String) -> void: + if current_file: + current_file.title = new_text + current_file.dirty = true + + +func _on_DocDateLineEdit_text_changed(new_text: String) -> void: + if current_file: + current_file.date = new_text + current_file.dirty = true + + +func _on_DocInputTextEdit_text_changed() -> void: + if current_file: + var new_text: String = document_input_textedit.text + current_file.content = new_text + current_file.dirty = true + + +func FileDef( + file_path: String, + include_in_filestxt: bool, + title: String = "", + date: String = "") -> FileDef: + var fd := FileDef.new() + fd.file_path = file_path + fd.include_in_filestxt = include_in_filestxt + fd.title = title + fd.date = date + + return fd + + +class FileDef: + var file_path: String # relative + var include_in_filestxt: bool = true + var title: String # optional + var date: String # optional, YYYY-MM-DD + var dirty: bool = false + + var content: String diff --git a/ServerUI.tscn b/ServerUI.tscn new file mode 100644 index 0000000..0b62a7d --- /dev/null +++ b/ServerUI.tscn @@ -0,0 +1,255 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://ServerUI.gd" type="Script" id=1] + +[node name="ServerUI" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +anchor_left = 0.0380859 +anchor_top = 0.087 +anchor_right = 0.961914 +anchor_bottom = 0.936667 +margin_top = -0.200005 +custom_constants/separation = 9 +__meta__ = { +"_edit_use_anchors_": true +} + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"] +margin_right = 1182.0 +margin_bottom = 32.0 +alignment = 1 + +[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer"] +margin_left = 142.0 +margin_top = 9.0 +margin_right = 227.0 +margin_bottom = 23.0 +text = "Server folder:" + +[node name="ServerPathLabel" type="Label" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 231.0 +margin_top = 9.0 +margin_right = 470.0 +margin_bottom = 23.0 +text = "/home/username/long/path/to/server" + +[node name="OpenServerFolderButton" type="Button" parent="VBoxContainer/HBoxContainer"] +margin_left = 474.0 +margin_right = 533.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 0, 32 ) +text = "Open..." + +[node name="Label2" type="Label" parent="VBoxContainer/HBoxContainer"] +margin_left = 537.0 +margin_top = 9.0 +margin_right = 611.0 +margin_bottom = 23.0 +text = "Server port:" + +[node name="PortSpinBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 615.0 +margin_right = 689.0 +margin_bottom = 32.0 +min_value = 81.0 +max_value = 8000.0 +value = 3001.0 + +[node name="StartServerButton" type="Button" parent="VBoxContainer/HBoxContainer"] +margin_left = 693.0 +margin_right = 778.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 0, 32 ) +text = "Start server" + +[node name="ServerStatusLabel" type="Label" parent="VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 782.0 +margin_top = 9.0 +margin_right = 918.0 +margin_bottom = 23.0 +text = "Server is not running." + +[node name="OpenBrowserButton" type="Button" parent="VBoxContainer/HBoxContainer"] +margin_left = 922.0 +margin_right = 1040.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 0, 32 ) +size_flags_horizontal = 12 +text = "Open in browser" + +[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer"] +margin_top = 41.0 +margin_right = 1182.0 +margin_bottom = 611.0 +size_flags_vertical = 3 +split_offset = -263 + +[node name="VBoxContainer2" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"] +margin_right = 322.0 +margin_bottom = 570.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer2"] +margin_right = 322.0 +margin_bottom = 32.0 + +[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer2/HBoxContainer2"] +margin_top = 9.0 +margin_right = 30.0 +margin_bottom = 23.0 +size_flags_horizontal = 0 +text = "Files" + +[node name="RefreshFilesButton" type="Button" parent="VBoxContainer/HSplitContainer/VBoxContainer2/HBoxContainer2"] +unique_name_in_owner = true +margin_left = 262.0 +margin_right = 322.0 +margin_bottom = 32.0 +rect_min_size = Vector2( 0, 32 ) +size_flags_horizontal = 10 +text = "Refresh" + +[node name="FileTree" type="Tree" parent="VBoxContainer/HSplitContainer/VBoxContainer2"] +unique_name_in_owner = true +margin_top = 36.0 +margin_right = 322.0 +margin_bottom = 570.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +allow_rmb_select = true +hide_root = true + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer"] +margin_left = 334.0 +margin_right = 1182.0 +margin_bottom = 570.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer"] +margin_right = 848.0 +margin_bottom = 570.0 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer"] +margin_right = 848.0 +margin_bottom = 570.0 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer"] +margin_right = 848.0 +margin_bottom = 24.0 + +[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +margin_top = 5.0 +margin_right = 32.0 +margin_bottom = 19.0 +text = "Title:" + +[node name="DocTitleLineEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer"] +unique_name_in_owner = true +margin_left = 36.0 +margin_right = 848.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +placeholder_text = "(Optional)" + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 28.0 +margin_right = 848.0 +margin_bottom = 52.0 + +[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer2"] +margin_top = 5.0 +margin_right = 34.0 +margin_bottom = 19.0 +text = "Date:" + +[node name="DocDateLineEdit" type="LineEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer2"] +unique_name_in_owner = true +margin_left = 38.0 +margin_right = 848.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +placeholder_text = "(Optional)" + +[node name="HSplitContainer" type="HSplitContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer"] +margin_top = 56.0 +margin_right = 848.0 +margin_bottom = 570.0 +size_flags_vertical = 3 +split_offset = 328 + +[node name="ContentEditContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer"] +margin_right = 378.0 +margin_bottom = 514.0 +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer/ContentEditContainer"] +margin_right = 378.0 +margin_bottom = 14.0 +text = "Content" + +[node name="DocInputTextEdit" type="TextEdit" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer/ContentEditContainer"] +unique_name_in_owner = true +margin_top = 18.0 +margin_right = 378.0 +margin_bottom = 514.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +show_line_numbers = true +wrap_enabled = true + +[node name="ContentPreviewContainer" type="VBoxContainer" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer"] +margin_left = 390.0 +margin_right = 848.0 +margin_bottom = 514.0 +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer/ContentPreviewContainer"] +margin_right = 458.0 +margin_bottom = 14.0 +text = "Content Preview (Not accurate!)" + +[node name="ContentPreviewRichTextLabel" type="RichTextLabel" parent="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer/ContentPreviewContainer"] +unique_name_in_owner = true +margin_top = 18.0 +margin_right = 458.0 +margin_bottom = 514.0 +size_flags_vertical = 3 + +[node name="ServerFolderDialog" type="FileDialog" parent="."] +unique_name_in_owner = true +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +margin_left = -482.0 +margin_top = -281.0 +margin_right = 482.0 +margin_bottom = 281.0 +window_title = "Open a Directory" +mode = 2 +access = 2 + +[node name="FileTreeContextMenu" type="PopupMenu" parent="."] +unique_name_in_owner = true +margin_right = 95.0 +margin_bottom = 56.0 +rect_min_size = Vector2( 95, 56 ) +items = [ "Move Up", null, 0, false, false, 0, 0, null, "", false, "Move Down", null, 0, false, false, 1, 0, null, "", false ] + +[connection signal="pressed" from="VBoxContainer/HBoxContainer/OpenServerFolderButton" to="." method="_on_OpenServerFolderButton_pressed"] +[connection signal="item_edited" from="VBoxContainer/HSplitContainer/VBoxContainer2/FileTree" to="." method="_on_FileTree_item_edited"] +[connection signal="item_rmb_selected" from="VBoxContainer/HSplitContainer/VBoxContainer2/FileTree" to="." method="_on_FileTree_item_rmb_selected"] +[connection signal="item_selected" from="VBoxContainer/HSplitContainer/VBoxContainer2/FileTree" to="." method="_on_FileTree_item_selected"] +[connection signal="text_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer/DocTitleLineEdit" to="." method="_on_DocTitleLineEdit_text_changed"] +[connection signal="text_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HBoxContainer2/DocDateLineEdit" to="." method="_on_DocDateLineEdit_text_changed"] +[connection signal="text_changed" from="VBoxContainer/HSplitContainer/VBoxContainer/HBoxContainer/VBoxContainer/HSplitContainer/ContentEditContainer/DocInputTextEdit" to="." method="_on_DocInputTextEdit_text_changed"] +[connection signal="dir_selected" from="ServerFolderDialog" to="." method="set_server_dir"]