bugs squished
This commit is contained in:
		@@ -1,8 +1,15 @@
 | 
			
		||||
class_name CMD
 | 
			
		||||
 | 
			
		||||
var command_line_arguments: Dictionary = {}
 | 
			
		||||
 | 
			
		||||
func unsurround(value: String, quotes := PoolStringArray(['"', "'"])) -> String:
 | 
			
		||||
var _parsed := false
 | 
			
		||||
 | 
			
		||||
## @type Dictionary[String, String|bool]
 | 
			
		||||
var command_line_arguments: Dictionary = {} setget set_command_line_arguments, get_command_line_arguments
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Removes the first element find from the `quotes` array from the start and end of a string
 | 
			
		||||
## Also removes any whitespace resulting from removing the quoting elements
 | 
			
		||||
static func unsurround(value: String, quotes := PoolStringArray(['"', "'"])) -> String:
 | 
			
		||||
	for quote_str in quotes:
 | 
			
		||||
		if value.begins_with(quote_str) \
 | 
			
		||||
		and value.ends_with(quote_str) \
 | 
			
		||||
@@ -10,6 +17,7 @@ func unsurround(value: String, quotes := PoolStringArray(['"', "'"])) -> String:
 | 
			
		||||
			return value.trim_prefix(quote_str).trim_suffix(quote_str).strip_edges()
 | 
			
		||||
	return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Returns a dictionary of all arguments passed after `--` on the command line
 | 
			
		||||
## arguments take one of 2 forms:
 | 
			
		||||
## - `--arg` which is a boolean (using `--no-arg` for `false` is possible)
 | 
			
		||||
@@ -17,7 +25,7 @@ func unsurround(value: String, quotes := PoolStringArray(['"', "'"])) -> String:
 | 
			
		||||
##    unsurround the string
 | 
			
		||||
## This function does no evaluation and does not attempt to guess the type of
 | 
			
		||||
## arguments. You will receive either bools, or strings.
 | 
			
		||||
func _read_arguments() -> Dictionary:
 | 
			
		||||
static func parse_cmd_arguments() -> Dictionary:
 | 
			
		||||
	var arguments := {}
 | 
			
		||||
	for arg in OS.get_cmdline_args():
 | 
			
		||||
		var argument: String = arg.lstrip("--").to_lower()
 | 
			
		||||
@@ -36,12 +44,27 @@ func _read_arguments() -> Dictionary:
 | 
			
		||||
	return arguments
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_command_line_arguments(_arguments: Dictionary) -> void:
 | 
			
		||||
	printerr("get_command_line_arguments is a read only value")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_command_line_arguments() -> Dictionary:
 | 
			
		||||
	if not _parsed:
 | 
			
		||||
		_parsed = true
 | 
			
		||||
		command_line_arguments = parse_cmd_arguments()
 | 
			
		||||
	return command_line_arguments
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Returns a single argument passed after `--` on the command line
 | 
			
		||||
## if the argument does not exist, `default` is returned instead
 | 
			
		||||
## _parse_cmd_arguments() has to be called first
 | 
			
		||||
func get_argument(name: String, default = null):
 | 
			
		||||
	if command_line_arguments.has(name):
 | 
			
		||||
		return command_line_arguments[name]
 | 
			
		||||
	if get_command_line_arguments().has(name):
 | 
			
		||||
		return get_command_line_arguments()[name]
 | 
			
		||||
	return default
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Verifies an argument exists on the command line
 | 
			
		||||
## _parse_cmd_arguments() has to be called first
 | 
			
		||||
func has_argument(name: String) -> bool:
 | 
			
		||||
	return command_line_arguments.has(name)
 | 
			
		||||
	return get_command_line_arguments().has(name)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,41 @@
 | 
			
		||||
## Reads the config, sets values. Acts a singleton because it proxies a const
 | 
			
		||||
## file path.
 | 
			
		||||
class_name ConfigManager extends Resource
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
signal file_changed
 | 
			
		||||
signal time_sheet_reloaded
 | 
			
		||||
 | 
			
		||||
const CONFIG_PATH := "user://settings.cfg"
 | 
			
		||||
 | 
			
		||||
var timesheet: TimeSheet setget ,get_timesheet
 | 
			
		||||
 | 
			
		||||
var _config := ConfigFile.new()
 | 
			
		||||
var _watcher: FileWatcher
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# SIGNAL
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
signal time_sheet_loaded
 | 
			
		||||
 | 
			
		||||
func emit_loaded() -> void:
 | 
			
		||||
	emit_signal("time_sheet_loaded")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# TIMESHEET FILE LOADING AND PARSING
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
var timesheet: TimeSheet setget ,get_timesheet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_timesheet() -> TimeSheet:
 | 
			
		||||
	if timesheet == null:
 | 
			
		||||
		timesheet = _load_timesheet(get_current_file())
 | 
			
		||||
		timesheet = _load_timesheet(get_current_timesheet_file_path())
 | 
			
		||||
	return timesheet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _load_timesheet(path: String) -> TimeSheet:
 | 
			
		||||
	timesheet = TimeSheet.restore(path)
 | 
			
		||||
	if timesheet == null:
 | 
			
		||||
	var new_timesheet := TimeSheet.restore(path)
 | 
			
		||||
	if new_timesheet == null:
 | 
			
		||||
		return null
 | 
			
		||||
	_watcher = FileWatcher.new()
 | 
			
		||||
	_watcher.file_name = path
 | 
			
		||||
@@ -30,72 +46,94 @@ func _load_timesheet(path: String) -> TimeSheet:
 | 
			
		||||
	#timesheet.connect("entry_started", self, "_on_entry_started")
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	#timesheet.connect("entry_stopped", self, "_on_entry_stopped")
 | 
			
		||||
	return timesheet
 | 
			
		||||
	return new_timesheet
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func reload_timesheet() -> void:
 | 
			
		||||
	var new_timesheet = _load_timesheet(get_current_file())
 | 
			
		||||
	var new_timesheet = _load_timesheet(get_current_timesheet_file_path())
 | 
			
		||||
	if new_timesheet == null:
 | 
			
		||||
		printerr("failed to load new timesheet")
 | 
			
		||||
		return
 | 
			
		||||
	timesheet = new_timesheet
 | 
			
		||||
	emit_signal("time_sheet_reloaded")
 | 
			
		||||
	emit_loaded()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var current_file: String = "" setget set_current_file, get_current_file
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# TIMESHEET FILE PATH
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
var current_timesheet_file_path: String = "" setget set_current_timesheet_file_path, get_current_timesheet_file_path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_current_file(value: String) -> void:
 | 
			
		||||
func set_current_timesheet_file_path(value: String) -> void:
 | 
			
		||||
	timesheet = _load_timesheet(value)
 | 
			
		||||
	if timesheet == null:
 | 
			
		||||
		return
 | 
			
		||||
	current_file = value
 | 
			
		||||
	current_timesheet_file_path = value
 | 
			
		||||
	_config.set_value("MAIN", "file", value)
 | 
			
		||||
	emit_signal("file_changed")
 | 
			
		||||
	emit_loaded()
 | 
			
		||||
	save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_current_file() -> String:
 | 
			
		||||
func get_current_timesheet_file_path() -> String:
 | 
			
		||||
	var _default_path := OS.get_system_dir(OS.SYSTEM_DIR_DOCUMENTS, true).plus_file("mouse_timer.csv")
 | 
			
		||||
	return _config.get_value("MAIN", "file", _default_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# THEME FILE LOADING AND PARSING
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var theme: Theme setget , get_theme
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_theme() -> Theme:
 | 
			
		||||
		if theme == null:
 | 
			
		||||
			theme = ResourceLoader.load(theme_path, "Theme")
 | 
			
		||||
			theme = ResourceLoader.load(theme_file_path, "Theme")
 | 
			
		||||
		return theme
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# THEME FILE PATH
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
signal theme_changed
 | 
			
		||||
var theme_path: String = "" setget set_theme_path, get_theme_path
 | 
			
		||||
var theme_file_path: String = "" setget set_theme_file_path, get_theme_file_path
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_theme_path(value: String) -> void:
 | 
			
		||||
func set_theme_file_path(value: String) -> void:
 | 
			
		||||
	var new_theme: Theme = ResourceLoader.load(value, "Theme")
 | 
			
		||||
	if new_theme != null:
 | 
			
		||||
		theme = new_theme
 | 
			
		||||
	theme_path = value
 | 
			
		||||
	theme_file_path = value
 | 
			
		||||
	_config.set_value("MAIN", "theme", value)
 | 
			
		||||
	emit_signal("theme_changed")
 | 
			
		||||
	save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_theme_path() -> String:
 | 
			
		||||
func get_theme_file_path() -> String:
 | 
			
		||||
	return _config.get_value("MAIN", "theme", preload("res://assets/default_theme.theme").resource_path)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var last_task_name: String = "" setget set_last_task_name, get_last_task_name
 | 
			
		||||
#var current_task_name: String = "" setget set_current_task_name, get_current_task_name
 | 
			
		||||
#
 | 
			
		||||
#func set_current_task_name(value: String) -> void:
 | 
			
		||||
#		current_task_name = value
 | 
			
		||||
#		_config.set_value("MAIN", "current_task_name", value)
 | 
			
		||||
#		save()
 | 
			
		||||
#
 | 
			
		||||
#func get_current_task_name() -> String:
 | 
			
		||||
#		return _config.get_value("MAIN", "current_task_name", "")
 | 
			
		||||
 | 
			
		||||
func set_last_task_name(value: String) -> void:
 | 
			
		||||
		last_task_name = value
 | 
			
		||||
		_config.set_value("MAIN", "last_task_name", value)
 | 
			
		||||
		save()
 | 
			
		||||
 | 
			
		||||
func get_last_task_name() -> String:
 | 
			
		||||
		return _config.get_value("MAIN", "last_task_name", "")
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# SOUND OPTION
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
var sound_fx_on: bool = true setget set_sound_fx_on, get_sound_fx_on
 | 
			
		||||
 | 
			
		||||
@@ -108,6 +146,30 @@ func get_sound_fx_on() -> bool:
 | 
			
		||||
		return _config.get_value("MAIN", "sound_fx", true)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# SOME SETTINGS CACHE
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
var current_task_name := "" setget set_current_task_name, get_current_task_name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func set_current_task_name(value: String) -> void:
 | 
			
		||||
	current_task_name = value
 | 
			
		||||
	_config.set_value("CACHE", "current_task_name", value)
 | 
			
		||||
	save()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_current_task_name() -> String:
 | 
			
		||||
	return _config.get_value("CACHE", "current_task_name", "")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
###############################################################################
 | 
			
		||||
#
 | 
			
		||||
# BOOTSTRAP
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _init() -> void:
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	_config.load(CONFIG_PATH)
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,8 @@ func check() -> void:
 | 
			
		||||
	var new_modified := _file.get_modified_time(file_name)
 | 
			
		||||
	if new_modified != _last_modified:
 | 
			
		||||
		_last_modified = new_modified
 | 
			
		||||
	emit_signal("file_changed")
 | 
			
		||||
		print("file changed")
 | 
			
		||||
		emit_signal("file_changed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func start() -> void:
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,6 @@
 | 
			
		||||
class_name TimeEntry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
signal end_time_updated
 | 
			
		||||
signal closed
 | 
			
		||||
 | 
			
		||||
var name := ""
 | 
			
		||||
var is_closed := false
 | 
			
		||||
var start_time := TimeStamp.new()
 | 
			
		||||
@@ -13,20 +10,21 @@ var end_time := TimeStamp.new()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func start_recording() -> TimeEntry:
 | 
			
		||||
	start_time = start_time.from_current_time()
 | 
			
		||||
	end_time = end_time.from_current_time()
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	start_time.from_current_time()
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	end_time.from_current_time()
 | 
			
		||||
	return self
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func update() -> void:
 | 
			
		||||
	end_time = end_time.from_current_time()
 | 
			
		||||
	emit_signal("end_time_updated")
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	end_time.from_current_time()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func close() -> void:
 | 
			
		||||
	update()
 | 
			
		||||
	is_closed = true
 | 
			
		||||
	emit_signal("closed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_elapsed_seconds() -> int:
 | 
			
		||||
@@ -88,5 +86,6 @@ func to_dict() -> Dictionary:
 | 
			
		||||
		closed = is_closed, 
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func _to_string() -> String:
 | 
			
		||||
	return "%s\t%s\t%s"%[name, tr(Consts.ONGOING) if is_closed == false else end_time.to_string(), start_time]
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ var time_entry: TimeEntry
 | 
			
		||||
var children := {}
 | 
			
		||||
var time_entries := []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_child(parts: Array, or_create := false):
 | 
			
		||||
	# workaround for cyclic dependencies bug
 | 
			
		||||
	var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
 | 
			
		||||
	if parts.size() == 0:
 | 
			
		||||
		return self
 | 
			
		||||
@@ -21,14 +23,23 @@ func get_child(parts: Array, or_create := false):
 | 
			
		||||
	return children[part].get_child(parts, or_create)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func find_active_time_entry() -> TimeEntry:
 | 
			
		||||
	for _time_entry_tree_item in time_entries:
 | 
			
		||||
		var time_entry_tree_item := _time_entry_tree_item as TimeEntryTreeItem
 | 
			
		||||
		var current_time_entry := time_entry_tree_item.time_entry
 | 
			
		||||
		if not current_time_entry.is_closed:
 | 
			
		||||
			return current_time_entry
 | 
			
		||||
	return null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func append(new_time_entry: TimeEntry) -> void:
 | 
			
		||||
	var TimeEntryTreeItem = load("res://scripts/time_entry_tree_item.gd")
 | 
			
		||||
	var time_entry_tree_item = TimeEntryTreeItem.new()
 | 
			
		||||
	time_entry_tree_item.time_entry = new_time_entry
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	new_time_entry.connect("end_time_updated", time_entry_tree_item, "_on_end_time_updated")
 | 
			
		||||
	# new_time_entry.connect("end_time_updated", time_entry_tree_item, "_on_end_time_updated")
 | 
			
		||||
	# warning-ignore:return_value_discarded
 | 
			
		||||
	time_entry_tree_item.connect("end_time_updated", self, "_on_end_time_updated")
 | 
			
		||||
	# time_entry_tree_item.connect("end_time_updated", self, "_on_end_time_updated")
 | 
			
		||||
	time_entries.append(time_entry_tree_item)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,8 @@ class_name TimeSheet
 | 
			
		||||
 | 
			
		||||
var source_path := ""
 | 
			
		||||
var entries := []
 | 
			
		||||
var tree := TimeEntryTreeItem.new()
 | 
			
		||||
# warning-ignore:integer_division
 | 
			
		||||
var _last_update := Time.get_ticks_msec() / 1000
 | 
			
		||||
 | 
			
		||||
## Loads the data file
 | 
			
		||||
func load_file() -> bool:
 | 
			
		||||
@@ -25,20 +26,10 @@ func load_file() -> bool:
 | 
			
		||||
			continue
 | 
			
		||||
		var entry := TimeEntry.new().from_csv_line(line)
 | 
			
		||||
		entries.append(entry)
 | 
			
		||||
		# warning-ignore:return_value_discarded
 | 
			
		||||
		entry.connect("closed", self, "save")
 | 
			
		||||
	file.close()
 | 
			
		||||
	
 | 
			
		||||
	entries.sort_custom(self, "_sort_entries")
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	for entry_index in entries.size():
 | 
			
		||||
		var entry: TimeEntry = entries[entry_index]
 | 
			
		||||
		var parts: PoolStringArray = entry.name.split("/")
 | 
			
		||||
		var repo: TimeEntryTreeItem = tree.get_child(parts, true)
 | 
			
		||||
		repo.append(entry)
 | 
			
		||||
	
 | 
			
		||||
	return true
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -46,22 +37,56 @@ func _sort_entries(a: TimeEntry, b: TimeEntry) -> bool:
 | 
			
		||||
	return a.name < b.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func get_active_entry_from_name(task_name: String) -> TimeEntry:
 | 
			
		||||
	for _entry in entries:
 | 
			
		||||
		var current_time_entry := _entry as TimeEntry
 | 
			
		||||
		if current_time_entry.name == task_name and not current_time_entry.is_closed:
 | 
			
		||||
			return current_time_entry
 | 
			
		||||
	return null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Adds a new time entry to the tree and to the data file
 | 
			
		||||
func start_entry(entry_name: String) -> void:
 | 
			
		||||
func add_entry(entry_name: String) -> TimeEntry:
 | 
			
		||||
	var current_entry := TimeEntry.new().start_recording()
 | 
			
		||||
	current_entry.name = entry_name
 | 
			
		||||
	current_entry.closed = false
 | 
			
		||||
	current_entry.is_closed = false
 | 
			
		||||
	var file := File.new()
 | 
			
		||||
	var success := file.open(source_path, File.READ_WRITE)
 | 
			
		||||
	if success != OK:
 | 
			
		||||
		printerr("Could not open file")
 | 
			
		||||
		return
 | 
			
		||||
		return null
 | 
			
		||||
	file.seek_end()
 | 
			
		||||
	entries.append(current_entry)
 | 
			
		||||
	file.store_csv_line(current_entry.to_csv_line())
 | 
			
		||||
	emit_signal("entry_started")
 | 
			
		||||
	return current_entry
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func stop_entry(entry_name: String, do_save := true) -> bool:
 | 
			
		||||
	prints("stopping", entry_name)
 | 
			
		||||
	for _entry in entries:
 | 
			
		||||
		var current_time_entry := _entry as TimeEntry
 | 
			
		||||
		if current_time_entry.name == entry_name and not current_time_entry.is_closed:
 | 
			
		||||
			current_time_entry.close()
 | 
			
		||||
			if do_save:
 | 
			
		||||
				save()
 | 
			
		||||
			return true
 | 
			
		||||
	return false
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func toggle_entry(entry_name: String, do_save := true) -> void:
 | 
			
		||||
	if stop_entry(entry_name, do_save):
 | 
			
		||||
		return
 | 
			
		||||
	else:
 | 
			
		||||
		# warning-ignore:return_value_discarded
 | 
			
		||||
		add_entry(entry_name)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func update() -> void:
 | 
			
		||||
	# warning-ignore:integer_division
 | 
			
		||||
	var current_time := Time.get_ticks_msec() / 1000
 | 
			
		||||
	if current_time == _last_update:
 | 
			
		||||
		return
 | 
			
		||||
	_last_update = current_time
 | 
			
		||||
	for entry in entries:
 | 
			
		||||
		var time_entry := entry as TimeEntry
 | 
			
		||||
		if time_entry.is_closed == false:
 | 
			
		||||
@@ -74,10 +99,21 @@ func save() -> void:
 | 
			
		||||
	if success != OK:
 | 
			
		||||
		printerr("Could not open file")
 | 
			
		||||
		return
 | 
			
		||||
	prints("saving")
 | 
			
		||||
	for time_entry in entries:
 | 
			
		||||
		file.store_csv_line(time_entry.to_csv_line())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func make_items_tree() -> TimeEntryTreeItem:
 | 
			
		||||
	var tree := TimeEntryTreeItem.new()
 | 
			
		||||
	for entry_index in entries.size():
 | 
			
		||||
		var entry := entries[entry_index] as TimeEntry
 | 
			
		||||
		var parts := entry.name.split("/")
 | 
			
		||||
		var repo: TimeEntryTreeItem = tree.get_child(parts, true)
 | 
			
		||||
		repo.append(entry)
 | 
			
		||||
	return tree
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static func restore(file_path: String) -> TimeSheet:
 | 
			
		||||
	var timesheet = load("res://scripts/time_sheet.gd").new()
 | 
			
		||||
	timesheet.source_path = file_path
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user