tickle-godot-frontend/ServerUI.gd
2022-06-25 19:37:09 +03:00

309 lines
8.7 KiB
GDScript

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
export(bool) var enable_file_autosave := true # if true, will save the active file's contents every save_file_timeout seconds.
export(float) var save_file_timeout := 4.0 # the time to save document content after the edited signal of TextEdit
enum ContextMenuOptions {
MOVE_UP,
MOVE_DOWN,
}
onready var server_path_label := $"%ServerPathLabel"
onready var port_spin_box := $"%PortSpinBox"
onready var start_server_button := $"%StartServerButton"
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, path) # port: int, path: String
signal stop_server_button_pressed() # emit from %StartServerButton, when the server is not running.
signal open_browser_button_pressed(port) # port: int
signal files_selection_changed(new_files) # new_files: Array<String>
var server_dir: String setget set_server_dir
var is_server_running: bool = false setget set_server_running
var working_files: Array = [] # Array<FileDef>
var current_file: FileDef = null
var current_tree_selection: TreeItem = 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()
var idx: int = 0
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)
fd.include_in_filestxt = in_filestxt
idx += 1
file_name = directory.get_next()
directory.list_dir_end()
_reconstruct_tree_from_working_files()
func _reconstruct_tree_from_working_files() -> void:
file_tree.clear()
var root: TreeItem = file_tree.create_item()
root.set_text(0, "Server files")
for i in working_files.size():
var fd = working_files[i] as FileDef
var item: TreeItem = file_tree.create_item(root)
item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
item.set_metadata(0, {"file_def": fd, "id": i})
item.set_editable(0, true)
item.set_text(0, fd.file_path)
item.set_checked(0, fd.include_in_filestxt)
func _on_FileTree_item_edited() -> void:
var item: TreeItem = file_tree.get_edited()
var fd: FileDef = item.get_metadata(0)["file_def"]
fd.include_in_filestxt = item.is_checked(0)
func _on_FileTree_item_selected() -> void:
var item: TreeItem = file_tree.get_selected()
current_tree_selection = item
current_file = item.get_metadata(0)["file_def"] 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:
current_tree_selection = file_tree.get_selected()
file_tree_context_menu.rect_position = position + file_tree.rect_global_position
if (current_tree_selection.get_metadata(0)["id"] as int) == 0:
file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_UP, true)
else:
file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_UP, false)
if (current_tree_selection.get_metadata(0)["id"] as int) == working_files.size() - 1:
file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_DOWN, true)
else:
file_tree_context_menu.set_item_disabled(ContextMenuOptions.MOVE_DOWN, false)
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
if is_server_running:
server_status_label.text = SERVER_STATUS_TEXT.RUNNING
start_server_button.text = "Stop server"
port_spin_box.editable = false
document_date_lineedit.editable = false
document_title_lineedit.editable = false
document_input_textedit.readonly = true
else:
server_status_label.text = SERVER_STATUS_TEXT.NOT_RUNNING
start_server_button.text = "Start server"
port_spin_box.editable = !false
document_date_lineedit.editable = !false
document_title_lineedit.editable = !false
document_input_textedit.readonly = !true
func _generate_filestxt():
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]
var f := File.new()
if f.open(server_dir.plus_file("files.txt"), File.WRITE) == OK:
f.store_string(files)
f.close()
else:
push_error("File.txt open for save error!") # TODO: show a user-facing error
func _on_StartServerButton_pressed() -> void:
if is_server_running:
emit_signal("stop_server_button_pressed")
else:
_generate_filestxt()
emit_signal("start_server_button_pressed", port_spin_box.value, server_dir)
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 = {
"file_path": path,
"date": "",
"title": "",
"content": "",
}
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():
continue
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_FileTreeContextMenu_id_pressed(id: int) -> void:
var idx = current_tree_selection.get_metadata(0)["id"] as int
var fd = working_files[idx] as FileDef
match id:
ContextMenuOptions.MOVE_DOWN:
working_files.remove(idx)
working_files.insert(idx + 1, fd)
_reconstruct_tree_from_working_files()
ContextMenuOptions.MOVE_UP:
working_files.remove(idx)
working_files.insert(idx - 1, fd)
_reconstruct_tree_from_working_files()
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
if !current_file.timer:
var t := Timer.new()
t.wait_time = save_file_timeout
t.one_shot = true
current_file.timer = t
add_child(t)
t.connect("timeout", self, "_on_EditedTimeout_timeout", [t, current_file])
else:
current_file.timer.stop()
current_file.timer.start()
func _on_EditedTimeout_timeout(timer: Timer, file: FileDef) -> void:
file.timer = null
timer.queue_free()
var f := File.new()
if f.open(server_dir.plus_file(file.file_path), File.WRITE) == OK:
f.store_string(file.content)
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
var timer: Timer