diff --git a/exp_legacy/module/config/_file_loader.lua b/exp_legacy/module/config/_file_loader.lua index 937d01c8..7da8d3f0 100644 --- a/exp_legacy/module/config/_file_loader.lua +++ b/exp_legacy/module/config/_file_loader.lua @@ -23,8 +23,6 @@ return { --- GUI "modules.gui.readme", - -- "modules.gui.rocket-info", - "modules.gui.task-list", "modules.gui.warp-list", "modules.gui.player-list", "modules.gui.vlayer", diff --git a/exp_legacy/module/locale/en/gui.cfg b/exp_legacy/module/locale/en/gui.cfg index 89a55d6c..ee4512fe 100644 --- a/exp_legacy/module/locale/en/gui.cfg +++ b/exp_legacy/module/locale/en/gui.cfg @@ -54,28 +54,6 @@ progress-caption=__1__% progress-tooltip=This silo has launched __1__ rockets launch-failed=Failed to launch rocket, please wait a few seconds and try again. -[task-list] -main-caption=Task List [img=info] -main-tooltip=Task List -sub-tooltip=Tasks that remain to be done\n- You can use richtext to include images [img=utility/not_enough_repair_packs_icon] or [color=blue]color[/color] your tasks. -no-tasks=No tasks found! -no-tasks-tooltip=Click on the plus button to the top right of this window to add a new task! -last-edit=Last edited by __1__ at __2__ -add-tooltip=Add new task -confirm=Confirm -confirm-tooltip=Save task (minimum of 5 characters long) -discard=Discard -discard-tooltip=Discard task/changes -delete=Delete -delete-tooltip=Delete task -close-tooltip=Close task details -edit=Edit task -edit-tooltip=Currently being edited by: __1__ -edit-tooltip-none=Currently being edited by: Nobody -create-footer-header=Create task -edit-footer-header=Edit task -view-footer-header=Task details - [warp-list] main-caption=Warp List [img=info] main-tooltip=Warp List diff --git a/exp_legacy/module/locale/zh-CN/gui.cfg b/exp_legacy/module/locale/zh-CN/gui.cfg index 0c2959ab..61a0b846 100644 --- a/exp_legacy/module/locale/zh-CN/gui.cfg +++ b/exp_legacy/module/locale/zh-CN/gui.cfg @@ -54,28 +54,6 @@ progress-caption=__1__ % progress-tooltip=該火箭發射井發射了 __1__ 次 launch-failed=火箭發射失敗, 請過一會再試。 -[task-list] -main-caption=工作流程 [img=info] -main-tooltip=工作流程 -sub-tooltip=需要完成的工作流程\n- 你可以用富文本來加上圖片 [img=utility/not_enough_repair_packs_icon] 或 [color=blue] 顏色[/color]。 -no-tasks=沒有工作流程 -no-tasks-tooltip=按加號加入工作流程 -last-edit=最後由 __1__ 在 __2__ 修改 -add-tooltip=加入工作流程 -confirm=確認 -confirm-tooltip=儲存工作流程 (最少要5個字長) -discard=放棄 -discard-tooltip=放棄更改動 -delete=刪除 -delete-tooltip=刪除工作流程 -close-tooltip=關閉工作流程 -edit=修改工作流程 -edit-tooltip=現被 __1__ 修改中 -edit-tooltip-none=現沒有被人修改 -create-footer-header=加入工作流程 -edit-footer-header=修改工作流程 -view-footer-header=工作流程細節 - [warp-list] main-caption=傳送陣清單 [img=info] main-tooltip=傳送陣清單 diff --git a/exp_legacy/module/locale/zh-TW/gui.cfg b/exp_legacy/module/locale/zh-TW/gui.cfg index 0c2959ab..61a0b846 100644 --- a/exp_legacy/module/locale/zh-TW/gui.cfg +++ b/exp_legacy/module/locale/zh-TW/gui.cfg @@ -54,28 +54,6 @@ progress-caption=__1__ % progress-tooltip=該火箭發射井發射了 __1__ 次 launch-failed=火箭發射失敗, 請過一會再試。 -[task-list] -main-caption=工作流程 [img=info] -main-tooltip=工作流程 -sub-tooltip=需要完成的工作流程\n- 你可以用富文本來加上圖片 [img=utility/not_enough_repair_packs_icon] 或 [color=blue] 顏色[/color]。 -no-tasks=沒有工作流程 -no-tasks-tooltip=按加號加入工作流程 -last-edit=最後由 __1__ 在 __2__ 修改 -add-tooltip=加入工作流程 -confirm=確認 -confirm-tooltip=儲存工作流程 (最少要5個字長) -discard=放棄 -discard-tooltip=放棄更改動 -delete=刪除 -delete-tooltip=刪除工作流程 -close-tooltip=關閉工作流程 -edit=修改工作流程 -edit-tooltip=現被 __1__ 修改中 -edit-tooltip-none=現沒有被人修改 -create-footer-header=加入工作流程 -edit-footer-header=修改工作流程 -view-footer-header=工作流程細節 - [warp-list] main-caption=傳送陣清單 [img=info] main-tooltip=傳送陣清單 diff --git a/exp_legacy/module/modules/control/tasks.lua b/exp_legacy/module/modules/control/tasks.lua deleted file mode 100644 index 6a03a089..00000000 --- a/exp_legacy/module/modules/control/tasks.lua +++ /dev/null @@ -1,176 +0,0 @@ ---[[-- Control Module - Tasks -- Stores tasks for each force. -@control Tasks -@alias Tasks - -@usage-- Making and then editing a new task -local task_id = Tasks.add_task(game.player.force.name, nil, game.player.name) - -Tasks.update_task(task_id, 'We need more iron!', game.player.name) - -]] - -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore -local Storage = require("modules/exp_util/storage") - ---- Stores all data for the warp gui -local TaskData = Datastore.connect("TaskData") -TaskData:set_serializer(function(raw_key) return raw_key.task_id end) - -local Tasks = {} - --- Storage lookup table for force name to task ids -local force_tasks = { _uid = 1 } -Storage.register(force_tasks, function(tbl) - force_tasks = tbl -end) - ---- Setters. --- functions used to created and alter tasks --- @section setters - ---[[-- Add a new task for a force, the task can be placed into a certain position for that force -@tparam string force_name the name of the force to add the task for -@tparam[opt] string player_name the player who added this task, will cause them to be listed under editing -@tparam[opt] string task_title the task title, if not given default is used -@tparam[opt] string task_body the task body, if not given default is used -@treturn string the uid of the task which was created - -@usage-- Adding a new task for your force -local task_id = Tasks.add_task(game.player.force.name, game.player.name, nil, nil) - -]] -function Tasks.add_task(force_name, player_name, task_title, task_body) - -- Get a new task id - local task_id = tostring(force_tasks._uid) - force_tasks._uid = force_tasks._uid + 1 - - -- Get the existing tasks for this force - local task_ids = force_tasks[force_name] --[[@as table?]] - if not task_ids then - task_ids = {} - force_tasks[force_name] = task_ids --[[@as any]] - end - - -- Insert the task id into the forces tasks - table.insert(task_ids, task_id) - - -- Add the new task to the store - TaskData:set(task_id, { - task_id = task_id, - force_name = force_name, - title = task_title or "", - body = task_body or "", - last_edit_name = player_name or "", - last_edit_time = game.tick, - currently_editing = {}, - }) - - return task_id -end - ---[[-- Removes a task and any data that is linked with it -@tparam string task_id the uid of the task which you want to remove - -@usage-- Removing a task -Tasks.remove_task(task_id) - -]] -function Tasks.remove_task(task_id) - local task = TaskData:get(task_id) - local force_name = task.force_name - table.remove_element(force_tasks[force_name], task_id) - TaskData:remove(task_id) -end - ---[[-- Update the message and last edited information for a task -@tparam string task_id the uid of the task to update -@tparam string player_name the name of the player who made the edit -@tparam string task_title the title of the task to update to -@tparam string task_body the body of the task to update to - -@usage-- Updating the message for on a task -Task.update_task(task_id, game.player.name, 'We need more iron!', 'Build more iron outposts.') - -]] -function Tasks.update_task(task_id, player_name, task_title, task_body) - TaskData:update(task_id, function(_, task) - task.last_edit_name = player_name - task.last_edit_time = game.tick - task.title = task_title - task.body = task_body - end) -end - ---[[-- Set the editing state for a player, can be used as a warning or to display a text field -@tparam string task_id the uid of the task that you want to effect -@tparam string player_name the name of the player you want to set the state for -@tparam boolean state the new state to set editing to - -@usage-- Setting your editing state to true -Tasks.set_editing(task_id, game.player.name, true) - -]] -function Tasks.set_editing(task_id, player_name, state) - TaskData:update(task_id, function(_, task) - task.currently_editing[player_name] = state - end) -end - ---[[-- Adds an update handler for when a task is added, removed, or updated -@tparam function handler the handler which is called when a task is updated - -@usage-- Add a game print when a task is updated -Tasks.on_update(function(task) - game.print(task.force_name..' now has the task: '..task.message) -end) - -]] -function Tasks.on_update(handler) - TaskData:on_update(handler) -end - ---- Getters. --- function used to get information about tasks --- @section getters - ---[[-- Gets the task information that is linked with this id -@tparam string task_id the uid of the task you want to get -@treturn table the task information - -@usage-- Getting task information outside of on_update -local task = Tasks.get_task(task_id) - -]] -function Tasks.get_task(task_id) - return TaskData:get(task_id) -end - ---[[-- Gets all the task ids that a force has -@tparam string force_name the name of the force that you want the task ids for -@treturn table an array of all the task ids - -@usage-- Getting the task ids for a force -local task_ids = Tasks.get_force_task_ids(game.player.force.name) - -]] -function Tasks.get_force_task_ids(force_name) - return force_tasks[force_name] or {} -end - ---[[-- Gets the editing state for a player -@tparam string task_id the uid of the task you want to check -@tparam string player_name the name of the player that you want to check -@treturn boolean weather the player is currently editing this task - -@usage-- Check if a player is editing a task or not -local editing = Tasks.get_editing(task_id, game.player.name) - -]] -function Tasks.get_editing(task_id, player_name) - local task = TaskData:get(task_id) - return task.currently_editing[player_name] -end - --- Module Return -return Tasks diff --git a/exp_legacy/module/modules/gui/task-list.lua b/exp_legacy/module/modules/gui/task-list.lua deleted file mode 100644 index 3cbdf040..00000000 --- a/exp_legacy/module/modules/gui/task-list.lua +++ /dev/null @@ -1,794 +0,0 @@ ---[[-- Gui Module - Task List - - Adds a task list to the game which players can add, remove and edit items on - @gui Task-List - @alias task_list -]] - -local ExpUtil = require("modules/exp_util") -local Gui = require("modules/exp_gui") -local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event -local Roles = require("modules.exp_legacy.expcore.roles") --- @dep expcore.roles -local Datastore = require("modules.exp_legacy.expcore.datastore") --- @dep expcore.datastore -local config = require("modules.exp_legacy.config.gui.tasks") --- @dep config.gui.tasks -local Tasks = require("modules.exp_legacy.modules.control.tasks") --- @dep modules.control.tasks - -local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } - ---- Stores all data for the task gui by player -local TaskGuiData = Datastore.connect("TaskGuiData") -TaskGuiData:set_serializer(Datastore.name_serializer) -local PlayerIsEditing = TaskGuiData:combine("PlayerIsEditing") -PlayerIsEditing:set_default(false) -local PlayerIsCreating = TaskGuiData:combine("PlayerIsCreating") -PlayerIsCreating:set_default(false) -local PlayerSelected = TaskGuiData:combine("PlayerSelected") -PlayerSelected:set_default(nil) - --- Styles used for sprite buttons -local Styles = { - sprite22 = { - height = 22, - width = 22, - padding = -2, - }, - footer_button = { - height = 29, - maximal_width = 268, - horizontally_stretchable = true, - padding = -2, - }, -} - ---- If a player is allowed to use the edit buttons -local function check_player_permissions(player, task) - if task then - -- When a task is given check if the player can edit it - local allow_edit_task = config.allow_edit_task - - -- Check if the player being the last to edit will override existing permisisons - if config.user_can_edit_own_tasks and task.last_edit_name == player.name then - return true - end - - -- Check player has permisison based on value in the config - if allow_edit_task == "all" then - return true - elseif allow_edit_task == "admin" then - return player.admin - elseif allow_edit_task == "expcore.roles" then - return Roles.player_allowed(player, config.expcore_roles_allow_edit_task) - end - - -- Return false as all other condidtions have not been met - return false - else - -- When a task is not given check if the player can add a new task - local allow_add_task = config.allow_add_task - - -- Check player has permisison based on value in the config - if allow_add_task == "all" then - return true - elseif allow_add_task == "admin" then - return player.admin - elseif allow_add_task == "expcore.roles" then - return Roles.player_allowed(player, config.expcore_roles_allow_add_task) - end - - -- Return false as all other condidtions have not been met - return false - end -end - ---- Elements - ---- Button displayed in the header bar, used to add a new task --- @element add_new_task -local add_new_task = Gui.define("add_new_task") - :draw{ - type = "sprite-button", - sprite = "utility/add", - tooltip = { "task-list.add-tooltip" }, - style = "tool_button", - name = Gui.from_name, - } - :style(Styles.sprite22) - :on_click( - function(def, player, element) - -- Disable editing - PlayerIsEditing:set(player, false) - -- Clear selected - PlayerSelected:set(player, nil) - -- Open task create footer - PlayerIsCreating:set(player, true) - end - ) - ---- Header displayed when no tasks are in the task list --- @element no_tasks_found -local no_tasks_found = Gui.define("no_tasks_found") - :draw( - function(_, parent) - local header = - parent.add{ - name = "no_tasks_found_element", - type = "frame", - style = "negative_subheader_frame", - } - header.style.horizontally_stretchable = true - header.style.bottom_margin = 0 - -- Flow used for centering the content in the subheader - local center = - header.add{ - type = "flow", - } - center.style.vertical_align = "center" - center.style.horizontal_align = "center" - center.style.horizontally_stretchable = true - center.add{ - name = "header_label", - type = "label", - style = "bold_label", - caption = { "", "[img=utility/warning_white] ", { "task-list.no-tasks" } }, - tooltip = { "task-list.no-tasks-tooltip" }, - } - return header - end - ) - ---- Frame element with the right styling --- @element subfooter_frame -local subfooter_frame = Gui.define("task_list_subfooter_frame") - :draw{ - type = "frame", - name = Gui.from_argument(1), - direction = "vertical", - style = "subfooter_frame", - } - :style{ - height = 0, - padding = 5, - use_header_filler = false, - } - - ---- Label element preset --- @element subfooter_label -local subfooter_label = Gui.define("task_list_subfooter_label") - :draw{ - name = "footer_label", - type = "label", - style = "frame_title", - caption = Gui.from_argument(1), - } - ---- Action flow that contains action buttons --- @element subfooter_actions -local subfooter_actions = Gui.define("task_list_subfooter_actions") - :draw{ - type = "flow", - name = "actions", - } - ---- Button element with a flow around it to fix duplicate name inside of the scroll flow --- @element task_list_item -local task_list_item = Gui.define("task_list_item") - :draw( - function(def, parent, task) - local flow = parent.add{ - type = "flow", - name = "task-" .. task.task_id, - caption = task.task_id, - } - - flow.style.horizontally_stretchable = true - - local button = flow.add{ - name = def.name, - type = "button", - style = "list_box_item", - caption = task.title, - tooltip = { "task-list.last-edit", task.last_edit_name, format_time(task.last_edit_time) }, - } - - button.style.horizontally_stretchable = true - button.style.horizontally_squashable = true - - return button - end - ) - :on_click( - function(def, player, element) - local task_id = element.parent.caption - PlayerSelected:set(player, task_id) - end - ) - ---- Scrollable list of all tasks --- @element task_list -local task_list = Gui.define("task_list") - :draw( - function(_, parent) - local scroll_pane = - parent.add{ - name = "scroll", - type = "scroll-pane", - direction = "vertical", - horizontal_scroll_policy = "never", - vertical_scroll_policy = "auto", - style = "scroll_pane_under_subheader", - } - scroll_pane.style.horizontally_stretchable = true - scroll_pane.style.padding = 0 - scroll_pane.style.maximal_height = 224 - - local flow = - scroll_pane.add{ - name = "task_list", - type = "flow", - direction = "vertical", - } - flow.style.vertical_spacing = 0 - flow.style.horizontally_stretchable = true - - return flow - end - ) - ---- Button element inside the task view footer to start editing a task --- @element task_view_edit_button -local task_view_edit_button = Gui.define("task_view_edit_button") - :draw{ - type = "button", - name = Gui.from_name, - caption = { "", "[img=utility/rename_icon] ", { "task-list.edit" } }, - tooltip = { "task-list.edit-tooltip" }, - style = "shortcut_bar_button", - }:style(Styles.footer_button):on_click( - function(def, player, element) - local selected = PlayerSelected:get(player) - PlayerIsEditing:set(player, true) - - Tasks.set_editing(selected, player.name, true) - end - ) - ---- Button to close the task view footer --- @element task_view_close_button -local task_view_close_button = Gui.define("task_view_close_button") - :draw{ - type = "sprite-button", - sprite = "utility/collapse", - style = "frame_action_button", - tooltip = { "task-list.close-tooltip" }, - } - :style(Styles.sprite22) - :on_click( - function(def, player, element) - PlayerSelected:set(player, nil) - end - ) - ---- Button to delete the task inside the task view footer --- @element task_view_delete_button -local task_view_delete_button = Gui.define("task_view_delete_button") - :draw{ - type = "button", - name = Gui.from_name, - caption = { "", "[img=utility/trash] ", { "task-list.delete" } }, - tooltip = { "task-list.delete-tooltip" }, - style = "shortcut_bar_button_red", - } - :style(Styles.footer_button) - :on_click( - function(def, player, element) - local selected = PlayerSelected:get(player) - PlayerSelected:set(player, nil) - Tasks.remove_task(selected) - end - ) - ---- Subfooter inside the tasklist container that holds all the elements for viewing a task --- @element task_view_footer -local task_view_footer = Gui.define("task_view_footer") - :draw( - function(_, parent) - local footer = subfooter_frame(parent, "view") - local flow = footer.add{ type = "flow" } - subfooter_label(flow, { "task-list.view-footer-header" }) - local alignment = Gui.elements.aligned_flow(flow) - task_view_close_button(alignment) - local title_label = - footer.add{ - type = "label", - name = "title", - } - title_label.style.padding = 4 - title_label.style.font = "default-bold" - title_label.style.single_line = false - local body_label = - footer.add{ - type = "label", - name = "body", - } - body_label.style.padding = 4 - body_label.style.single_line = false - - local action_flow = subfooter_actions(footer) - task_view_delete_button(action_flow) - task_view_edit_button(action_flow) - return footer - end - ) - -local message_pattern = "(.-)\n(.*)" - ---- Parse a string into a message object with title and body --- @tparam string str message data -local function parse_message(str) - -- Trim the spaces of the string - local trimmed = string.gsub(str, "^%s*(.-)%s*$", "%1") - local message = { title = "", body = "" } - local title, body = string.match(trimmed, message_pattern) - if not title then - -- If it doesn't match the pattern return the str as a title - message.title = trimmed - else - message.title = title - message.body = body - end - return message -end - --- Button variable initialisation because it is used inside the textfield element events -local task_edit_confirm_button -local task_create_confirm_button - ---- Textfield element used in both the task create and edit footers --- @element task_message_textfield -local task_message_textfield = Gui.define("task_message_textfield") - :draw{ - name = Gui.from_name, - type = "text-box", - text = "", - }:style{ - maximal_width = 268, - minimal_height = 100, - horizontally_stretchable = true, - } - :on_text_changed( - function(def, player, element) - local is_editing = PlayerIsEditing:get(player) - local is_creating = PlayerIsCreating:get(player) - - local valid = string.len(element.text) > 5 - - if is_creating then - element.parent.actions[task_create_confirm_button.name].enabled = valid - elseif is_editing then - element.parent.actions[task_edit_confirm_button.name].enabled = valid - end - end - ) - ---- Button to confirm the changes inside the task edit footer --- @element task_edit_confirm_button -task_edit_confirm_button = Gui.define("task_edit_confirm_button") - :draw{ - type = "button", - name = Gui.from_name, - caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } }, - tooltip = { "task-list.confirm-tooltip" }, - style = "shortcut_bar_button_green", - } - :style(Styles.footer_button) - :on_click( - function(def, player, element) - local selected = PlayerSelected:get(player) - PlayerIsEditing:set(player, false) - local new_message = element.parent.parent[task_message_textfield.name].text - local parsed = parse_message(new_message) - Tasks.update_task(selected, player.name, parsed.title, parsed.body) - Tasks.set_editing(selected, player.name, nil) - end - ) - ---- Button to discard the changes inside the task edit footer --- @element edit_task_discard_button -local edit_task_discard_button = Gui.define("edit_task_discard_button") - :draw{ - type = "button", - caption = { "", "[img=utility/close_black] ", { "task-list.discard" } }, - tooltip = { "task-list.discard-tooltip" }, - style = "shortcut_bar_button_red", - } - :style(Styles.footer_button) - :on_click( - function(def, player, element) - local selected = PlayerSelected:get(player) - Tasks.set_editing(selected, player.name, nil) - PlayerIsEditing:set(player, false) - end - ) - ---- Subfooter inside the tasklist container that holds all the elements for editing a task --- @element task_edit_footer -local task_edit_footer = Gui.define("task_edit_footer") - :draw( - function(_, parent) - local footer = subfooter_frame(parent, "edit") - subfooter_label(footer, { "task-list.edit-footer-header" }) - - task_message_textfield(footer) - - local action_flow = subfooter_actions(footer) - - edit_task_discard_button(action_flow) - task_edit_confirm_button(action_flow) - - return footer - end - ) - ---- Button to confirm the changes inside the task create footer --- @element task_create_confirm_button -task_create_confirm_button = Gui.define("task_create_confirm_button") - :draw{ - type = "button", - name = Gui.from_name, - caption = { "", "[img=utility/check_mark] ", { "task-list.confirm" } }, - tooltip = { "task-list.confirm-tooltip" }, - style = "shortcut_bar_button_green", - enabled = false, - } - :style(Styles.footer_button) - :on_click( - function(def, player, element) - local message = element.parent.parent[task_message_textfield.name].text - PlayerIsCreating:set(player, false) - local parsed = parse_message(message) - local task_id = Tasks.add_task(player.force.name, player.name, parsed.title, parsed.body) - PlayerSelected:set(player, task_id) - end - ) - ---- Button to discard the changes inside the task create footer --- @element task_create_discard_button -local task_create_discard_button = Gui.define("task_create_discard_button") - :draw{ - type = "button", - caption = { "", "[img=utility/close_black] ", { "task-list.discard" } }, - tooltip = { "task-list.discard-tooltip" }, - style = "shortcut_bar_button_red", - } - :style(Styles.footer_button) - :on_click( - function(def, player, element) - PlayerIsCreating:set(player, false) - end - ) - ---- Subfooter inside the tasklist container that holds all the elements to create a new task --- @element task_create_footer -local task_create_footer = Gui.define("task_create_footer") - :draw( - function(_, parent) - local footer = subfooter_frame(parent, "create") - subfooter_label(footer, { "task-list.create-footer-header" }) - - task_message_textfield(footer) - - local action_flow = subfooter_actions(footer) - - task_create_discard_button(action_flow) - task_create_confirm_button(action_flow) - - return footer - end - ) - ---- Clear and repopulate the task list with all current tasks -local repopulate_task_list = function(task_list_element) - local force = Gui.get_player(task_list_element).force - local task_ids = Tasks.get_force_task_ids(force.name) - task_list_element.clear() - - -- Set visibility of the no_tasks_found element depending on the amount of tasks still in the task manager - task_list_element.parent.parent.no_tasks_found_element.visible = #task_ids == 0 - - -- Add each task to the flow - for _, task_id in ipairs(task_ids) do - -- Add the task - local task = Tasks.get_task(task_id) - task_list_item(task_list_element, task) - end -end - ---- Main task list container for the left flow --- @element task_list_container -local task_list_container = Gui.define("task_list_container") - :draw( - function(def, parent) - -- Draw the internal container - local container = Gui.elements.container(parent, 268) - container.style.maximal_width = 268 - - -- Draw the header - local header = Gui.elements.header(container, { - name = "header", - caption = { "task-list.main-caption" }, - tooltip = { "task-list.sub-tooltip" }, - }) - - -- Draw the new task button - local player = Gui.get_player(parent) - local add_new_task_element = add_new_task(header) - add_new_task_element.visible = check_player_permissions(player) - - -- Draw no task found element - no_tasks_found(container) - - -- Draw task list element - local task_list_element = task_list(container) - repopulate_task_list(task_list_element) - - local task_view_footer_element = task_view_footer(container) - local task_edit_footer_element = task_edit_footer(container) - local task_create_footer_element = task_create_footer(container) - task_view_footer_element.visible = false - task_edit_footer_element.visible = false - task_create_footer_element.visible = false - -- Return the external container - return container.parent - end - ) - ---- Add the element to the left flow with a toolbar button -Gui.add_left_element(task_list_container, function(player) - local task_ids = Tasks.get_force_task_ids(player.force.name) - return #task_ids > 0 -end) -Gui.toolbar.create_button{ - name = "task_list_toggle", - left_element = task_list_container, - sprite = "utility/not_enough_repair_packs_icon", - tooltip = { "task-list.main-tooltip" }, - visible = function(player, element) - return Roles.player_allowed(player, "gui/task-list") - end -} - --- Function to update a single task and some of the elements inside the container -local update_task = function(player, task_list_element, task_id) - local task = Tasks.get_task(task_id) - local task_ids = Tasks.get_force_task_ids(player.force.name) - -- Set visibility of the no_tasks_found element depending on the amount of tasks still in the task manager - task_list_element.parent.parent.no_tasks_found_element.visible = #task_ids == 0 - - -- Task no longer exists so should be removed from the list - if not task then - task_list_element["task-" .. task_id].destroy() - return - end - - local flow = task_list_element["task-" .. task_id] - if not flow then - -- If task does not exist yet add it to the list - task_list_item(task_list_element, task) - else - -- If the task exists update the caption and tooltip - local button = flow[task_list_item.name] - button.caption = task.title - button.tooltip = { "task-list.last-edit", task.last_edit_name, format_time(task.last_edit_time) } - end -end - --- Update the footer task edit view -local update_task_edit_footer = function(player, task_id) - local task = Tasks.get_task(task_id) - local container = Gui.get_left_element(task_list_container, player) - local edit_flow = container.frame.edit - - local message_element = edit_flow[task_message_textfield.name] - - message_element.focus() - message_element.text = task.title .. "\n" .. task.body -end - --- Update the footer task view -local update_task_view_footer = function(player, task_id) - local task = Tasks.get_task(task_id) - local container = Gui.get_left_element(task_list_container, player) - local view_flow = container.frame.view - local has_permission = check_player_permissions(player, task) - - local title_element = view_flow.title - local body_element = view_flow.body - local edit_button_element = view_flow.actions[task_view_edit_button.name] - local delete_button_element = view_flow.actions[task_view_delete_button.name] - - edit_button_element.visible = has_permission - delete_button_element.visible = has_permission - title_element.caption = task.title - body_element.caption = task.body - - local players_editing = table.get_keys(task.currently_editing) - if #players_editing > 0 then - edit_button_element.tooltip = { "task-list.edit-tooltip", table.concat(players_editing, ", ") } - else - edit_button_element.tooltip = { "task-list.edit-tooltip-none" } - end -end - ---- When a new task is added it will update the task list for everyone on that force ---- Or when a task is updated it will update the specific task elements -Tasks.on_update( - function(task_id, curr_state, prev_state) - -- Get the force to update, task is nil when removed - local force - if curr_state then - force = game.forces[curr_state.force_name] - else - force = game.forces[prev_state.force_name] - end - - -- Update the task for all the players on the force - for _, player in pairs(force.connected_players) do - -- Update the task view elements if the player currently being looped over has this specific task selected - local selected = PlayerSelected:get(player) - if selected == task_id then - if curr_state then - update_task_view_footer(player, selected) - else - PlayerSelected:set(player, nil) - end - end - - local container = Gui.get_left_element(task_list_container, player) - local task_list_element = container.frame.scroll.task_list - - -- Update the task that was changed - update_task(player, task_list_element, task_id) - end - end -) - --- When a player is creating a new task. -PlayerIsCreating:on_update( - function(player_name, curr_state, _) - local player = game.players[player_name] - - local container = Gui.get_left_element(task_list_container, player) - local create = container.frame.create - - -- Clear the textfield - local message_element = container.frame.create[task_message_textfield.name] - local confirm_button_element = container.frame.create.actions[task_create_confirm_button.name] - message_element.focus() - message_element.text = "" - confirm_button_element.enabled = false - - if curr_state then - create.visible = true - else - create.visible = false - end - end -) - --- When a player selects a different warp from the list -PlayerSelected:on_update( - function(player_name, curr_state, prev_state) - local player = game.players[player_name] - - local container = Gui.get_left_element(task_list_container, player) - local task_list_element = container.frame.scroll.task_list - local view_flow = container.frame.view - local edit_flow = container.frame.edit - local is_editing = PlayerIsEditing:get(player) - local is_creating = PlayerIsCreating:get(player) - - -- If the selection has an previous state re-enable the button list element - if prev_state then - task_list_element["task-" .. prev_state][task_list_item.name].enabled = true - end - - if curr_state then - -- Disable the selected element - task_list_element["task-" .. curr_state][task_list_item.name].enabled = false - - -- Update the view footer - update_task_view_footer(player, curr_state) - - -- If a player is creating then remove the creation dialogue - if is_creating then - PlayerIsCreating:set(player, false) - end - - -- Depending on if the player is currently editing change the current task edit footer to the current task - if is_editing then - update_task_edit_footer(player, curr_state) - Tasks.set_editing(prev_state, player.name, nil) - Tasks.set_editing(curr_state, player.name, true) - view_flow.visible = false - edit_flow.visible = true - else - view_flow.visible = true - edit_flow.visible = false - end - else - -- If curr_state nil then hide footer elements and set editing to nil for prev_state - if prev_state and Tasks.get_task(prev_state) then - Tasks.set_editing(prev_state, player.name, nil) - end - view_flow.visible = false - edit_flow.visible = false - end - end -) - --- When the edit view opens or closes -PlayerIsEditing:on_update( - function(player_name, curr_state, _) - local player = game.players[player_name] - - local container = Gui.get_left_element(task_list_container, player) - local view_flow = container.frame.view - local edit_flow = container.frame.edit - - local selected = PlayerSelected:get(player) - if curr_state then - update_task_edit_footer(player, selected) - view_flow.visible = false - edit_flow.visible = true - else - view_flow.visible = true - edit_flow.visible = false - end - end -) - ---- Makes sure the right buttons are present when roles change -local function role_update_event(event) - local player = game.players[event.player_index] - local frame = Gui.get_left_element(task_list_container, player).frame - -- Update the view task - local selected = PlayerSelected:get(player) - if selected then - update_task_view_footer(player, selected) - PlayerSelected:set(player, selected) - -- button to edit the task. - -- Resetting the players selected task to make sure the player does not see an - end - - -- Update the new task button and create footer in case the user can now add them - local has_permission = check_player_permissions(player) - local add_new_task_element = frame.header[add_new_task.name] - add_new_task_element.visible = has_permission - local is_creating = PlayerIsCreating:get(player) - if is_creating and not has_permission then - PlayerIsCreating:set(player, false) - end -end - -Event.add(Roles.events.on_role_assigned, role_update_event) -Event.add(Roles.events.on_role_unassigned, role_update_event) - ---- Redraw all tasks and clear editing/creating after joining or changing force -local function reset_task_list(event) - -- Repopulate the task list - local player = game.players[event.player_index] - local container = Gui.get_left_element(task_list_container, player) - local task_list_element = container.frame.scroll.task_list - repopulate_task_list(task_list_element) - - -- Check if the selected task is still valid - local selected = PlayerSelected:get(player) - if selected and Tasks.get_task(selected) ~= nil then - PlayerIsCreating:set(player, false) - PlayerIsEditing:set(player, false) - PlayerSelected:set(player, nil) - end -end - -Event.add(defines.events.on_player_joined_game, reset_task_list) -Event.add(defines.events.on_player_changed_force, reset_task_list) diff --git a/exp_scenario/module/control.lua b/exp_scenario/module/control.lua index 41b2eb95..65c3a5fb 100644 --- a/exp_scenario/module/control.lua +++ b/exp_scenario/module/control.lua @@ -79,3 +79,4 @@ add(require("modules/exp_scenario/gui/quick_actions")) add(require("modules/exp_scenario/gui/research_milestones")) -- add(require("modules/exp_scenario/gui/science_production")) add(require("modules/exp_scenario/gui/surveillance")) +add(require("modules/exp_scenario/gui/task_list")) diff --git a/exp_scenario/module/gui/task_list.lua b/exp_scenario/module/gui/task_list.lua new file mode 100644 index 00000000..e777aa3d --- /dev/null +++ b/exp_scenario/module/gui/task_list.lua @@ -0,0 +1,951 @@ +--[[-- Gui - Task List +Adds a task list to the game which players can add, remove and edit items on +]] + +local Gui = require("modules/exp_gui") +local Roles = require("modules.exp_legacy.expcore.roles") +local config = require("modules.exp_legacy.config.gui.tasks") + +local ExpUtil = require("modules/exp_util") +local format_time = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true } + +--- @class ExpGui_TaskList.elements +local Elements = {} + +local Styles = { + sprite22 = Gui.styles.sprite{ + size = 22 + }, + footer_button = Gui.styles.sprite{ + height = 29, + maximal_width = 268, + horizontally_stretchable = true, + }, +} + +--- @class ExpGui_TaskList.Task +--- @field id number +--- @field last_user LuaPlayer +--- @field last_edit_tick number +--- @field editing table +--- @field title string +--- @field body string +--- @field new boolean? +--- @field deleted boolean? + +--- Check if a player can create a new task +--- @param player LuaPlayer +--- @return boolean +local function has_permission_create_task(player) + local allow_add_task = config.allow_add_task + + if allow_add_task == "all" then + return true + elseif allow_add_task == "admin" then + return player.admin + elseif allow_add_task == "expcore.roles" then + return Roles.player_allowed(player, config.expcore_roles_allow_add_task) + end + + return false +end + +--- Check if a player can edit an existing task +--- @param player LuaPlayer +--- @param task ExpGui_TaskList.Task +--- @return boolean +local function has_permission_edit_task(player, task) + local allow_edit_task = config.allow_edit_task + + -- Check if editing your own task allows bypassing other permissions + if config.user_can_edit_own_tasks and task.last_user.index == player.index then + return true + end + + -- Check player has permission based on value in the config + if allow_edit_task == "all" then + return true + elseif allow_edit_task == "admin" then + return player.admin + elseif allow_edit_task == "expcore.roles" then + return Roles.player_allowed(player, config.expcore_roles_allow_edit_task) + end + + return false +end + +--- @class ExpGui_TaskList.element.new_task_button.elements +--- @field container LuaGuiElement + +--- Button displayed in the header bar, used to add a new task +--- @class ExpGui_TaskList.element.new_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.new_task_button = Gui.define("task_list/new_task_button") + :track_all_elements() + :draw{ + type = "sprite-button", + sprite = "utility/add", + tooltip = { "exp-gui_task-list.tooltip-new" }, + style = "tool_button", + } + :style(Styles.sprite22) + :element_data{ + container = Gui.from_argument(1) + } + :on_click(function(def, player, new_task_button) + --- @cast def ExpGui_TaskList.element.new_task_button + local container = def.data[new_task_button].container + Elements.container.open_edit_task(container, { + id = Elements.container.next_task_id(), + last_edit_tick = game.tick, + last_user = player, + editing = {}, + title = "", + body = "", + new = true, + }) + end) --[[ @as any ]] + +--- Refresh the visibility based on player permissions +--- @param new_task_button LuaGuiElement +function Elements.new_task_button.refresh(new_task_button) + local player = Gui.get_player(new_task_button) + new_task_button.visible = has_permission_create_task(player) +end + +--- Refresh the visibility based on player permissions +--- @param player LuaPlayer +function Elements.new_task_button.refresh_player(player) + local visible = has_permission_create_task(player) + for _, new_task_button in Elements.new_task_button:tracked_elements(player) do + new_task_button.visible = visible + end +end + +--- @class ExpGui_TaskList.elements.view_task_button.elements +--- @field task ExpGui_TaskList.Task +--- @field container LuaGuiElement + +--- Button used to view a task details, the caption is the task title +--- @class ExpGui_TaskList.element.view_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, task: ExpGui_TaskList.Task, container: LuaGuiElement): LuaGuiElement +Elements.view_task_button = Gui.define("task_list/view_task_button") + :draw(function(def, parent, task) + --- @cast def ExpGui_TaskList.element.view_task_button + --- @cast task ExpGui_TaskList.Task + return parent.add{ + type = "button", + style = "list_box_item", + caption = task.title, + tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) }, + } + end) + :style{ + width = 268, + horizontally_stretchable = true, + } + :element_data{ + task = Gui.from_argument(1), + container = Gui.from_argument(2), + } + :on_click(function(def, player, view_task_button) + --- @cast def ExpGui_TaskList.element.view_task_button + local elements = def.data[view_task_button] + Elements.container.open_view_task(elements.container, elements.task) + end) --[[ @as any ]] + +--- Refresh the title and tooltip of the view task button +--- @param view_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.view_task_button.refresh(view_task_button, task) + view_task_button.caption = task.title + view_task_button.tooltip = { "exp-gui_task-list.tooltip-last-edit", task.last_user.name, format_time(task.last_edit_tick) } +end + +--- Label used to signal that no tasks are open +--- @class ExpGui_TaskList.elements.no_tasks_header: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.no_tasks_header = Gui.define("task_list/no_tasks_header") + :track_all_elements() + :draw(function(def, parent, ...) + local subheader = Gui.elements.subframe_base(parent, "negative_subheader_frame") + + local label = subheader.add{ + type = "label", + style = "bold_label", + caption = { "exp-gui_task-list.caption-no-tasks" }, + tooltip = { "exp-gui_task-list.tooltip-no-tasks" }, + } + + label.style.width = 268 + label.style.horizontal_align = "center" + + return subheader + end) + :style{ + padding = { 2, 0 }, + bottom_margin = 0, + } --[[ @as any ]] + +--- Refresh the visibility of the no tasks label +--- @param no_tasks_header LuaGuiElement +function Elements.no_tasks_header.refresh(no_tasks_header) + local force = Gui.get_player(no_tasks_header).force --[[ @as LuaForce ]] + no_tasks_header.visible = not Elements.container.has_tasks(force) +end + +--- Refresh the visibility of the no tasks label +--- @param player LuaPlayer +function Elements.no_tasks_header.refresh_player(player) + local visible = not Elements.container.has_tasks(player.force --[[ @as LuaForce ]]) + for _, no_tasks_header in Elements.no_tasks_header:tracked_elements(player) do + no_tasks_header.visible = visible + end +end + +--- Refresh the visibility of the no tasks label +--- @param force LuaForce +function Elements.no_tasks_header.refresh_force_online(force) + local visible = not Elements.container.has_tasks(force) + for _, no_tasks_header in Elements.no_tasks_header:online_elements(force) do + no_tasks_header.visible = visible + end +end + +--- A table containing all of the current tasks +--- @class ExpGui_TaskList.elements.task_table: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.task_list = Gui.define("task_list/task_list") + :draw(function(_, parent) + local task_list = parent.add{ + type = "scroll-pane", + vertical_scroll_policy = "auto", + horizontal_scroll_policy = "never", + style = "scroll_pane_under_subheader", + } + + local task_list_style = task_list.style + task_list_style.horizontally_stretchable = true + task_list_style.maximal_height = 224 + task_list_style.padding = 0 + + -- Cant modify vertical spacing on scroll pane style so need a sub flow + task_list = task_list.add{ type = "flow", direction = "vertical" } + + task_list_style = task_list.style + task_list_style.horizontally_stretchable = true + task_list_style.vertical_spacing = 0 + task_list_style.padding = 0 + + -- Add the no tasks header + local no_tasks_header = Elements.no_tasks_header(task_list) + Elements.no_tasks_header.refresh(no_tasks_header) + + return task_list + end) + :element_data{} --[[ @as any ]] + +--- Adds a task to the task list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.add_task(task_list, task) + local container = assert(task_list.parent.parent.parent) + local view_task_buttons = Elements.task_list.data[task_list] + view_task_buttons[task.id] = Elements.view_task_button(task_list, task, container) +end + +--- Remove a task from the task list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.remove_task(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + Gui.destroy_if_valid(view_task_buttons[task.id]) + view_task_buttons[task.id] = nil +end + +--- Refresh a task on the list +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_list.refresh_task(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + Elements.view_task_button.refresh(view_task_buttons[task.id], task) +end + +--- Refresh all tasks on the list +--- @param task_list LuaGuiElement +--- @param tasks ExpGui_TaskList.Task[] +function Elements.task_list.refresh_tasks(task_list, tasks) + local view_task_buttons = Elements.task_list.data[task_list] + local done = {} + + -- Refresh all valid tasks + for _, task in ipairs(tasks) do + done[task.id] = true + local view_task_button = view_task_buttons[task.id] + if view_task_button then + Elements.view_task_button.refresh(view_task_button, task) + else + Elements.task_list.add_task(task_list, task) + end + end + + -- Remove tasks buttons that are no longer required + for id, view_task_button in pairs(view_task_buttons) do + if not done[id] then + view_task_button.destroy() + end + end +end + +--- Select a task button, if nil then all buttons are reset +--- @param task_list LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.task_list.select_task_button(task_list, task) + local view_task_buttons = Elements.task_list.data[task_list] + local task_button = task and view_task_buttons[task.id] + if task_button and not task_button.enabled then + return + end + for _, view_task_button in pairs(view_task_buttons) do + view_task_button.enabled = true + end + if task_button then + task_button.enabled = false + end +end + +--- The base footer used for view and edit +--- @class ExpGui_TaskList.elements.task_footer: ExpElement +--- @overload fun(parent: LuaGuiElement): LuaGuiElement +Elements.task_footer = Gui.define("task_list/task_footer") + :draw{ + type = "frame", + direction = "vertical", + style = "subfooter_frame", + } + :style{ + height = 0, + padding = 5, + use_header_filler = false, + } --[[ @as any ]] + +--- @class ExpGui_TaskList.elements.close_task_button.elements +--- @field container LuaGuiElement + +--- Button used to close an open task details +--- @class ExpGui_TaskList.element.close_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.close_task_button = Gui.define("task_list/close_task_button") + :draw{ + type = "sprite-button", + sprite = "utility/collapse", + style = "frame_action_button", + tooltip = { "exp-gui_task-list.tooltip-close" }, + } + :style(Styles.sprite22) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, close_task_button) + --- @cast def ExpGui_TaskList.element.close_task_button + local elements = def.data[close_task_button] + Elements.container.close_footers(elements.container) + end) --[[ @as any ]] + +--- @class ExpGui_TaskList.elements.task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field container LuaGuiElement + +--- Button used to edit a task +--- @class ExpGui_TaskList.element.edit_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.edit_task_button = Gui.define("task_list/edit_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-edit" }, + tooltip = { "exp-gui_task-list.tooltip-edit" }, + style = "shortcut_bar_button", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, edit_task_button) + --- @cast def ExpGui_TaskList.element.edit_task_button + local elements = def.data[edit_task_button] + Elements.container.open_edit_task(elements.container, assert(elements.task)) + end) --[[ @as any ]] + +--- Refresh the edit button +--- @param edit_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.edit_task_button.refresh(edit_task_button, task) + local player = Gui.get_player(edit_task_button) + Elements.edit_task_button.data[edit_task_button].task = task + edit_task_button.visible = has_permission_edit_task(player, task) + + if next(task.editing) then + local player_names = table.get_values(task.editing) + edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit", table.concat(player_names, ", ") } + else + edit_task_button.tooltip = { "exp-gui_task-list.tooltip-edit-none" } + end +end + +--- Button used to delete a task +--- @class ExpGui_TaskList.element.delete_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.delete_task_button = Gui.define("task_list/delete_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-delete" }, + tooltip = { "exp-gui_task-list.tooltip-delete" }, + style = "shortcut_bar_button_red", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, delete_task_button) + --- @cast def ExpGui_TaskList.element.delete_task_button + local elements = def.data[delete_task_button] + Elements.container.remove_task(player.force --[[ @as LuaForce ]], elements.task) + Elements.container.close_footers(elements.container) + end) --[[ @as any ]] + +--- Refresh the delete button +--- @param delete_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.delete_task_button.refresh(delete_task_button, task) + local player = Gui.get_player(delete_task_button) + Elements.delete_task_button.data[delete_task_button].task = task + delete_task_button.visible = has_permission_edit_task(player, task) +end + +--- @class ExpGui_TaskList.elements.view_task_footer.elements +--- @field task ExpGui_TaskList.Task? +--- @field body_label LuaGuiElement +--- @field title_label LuaGuiElement +--- @field delete_task_button LuaGuiElement +--- @field edit_task_button LuaGuiElement + +--- The view footer used for view task details +--- @class ExpGui_TaskList.elements.view_task_footer: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.view_task_footer = Gui.define("task_list/view_task_footer") + :draw(function(def, parent, container) + --- @cast def ExpGui_TaskList.elements.view_task_footer + local view_task_footer = Elements.task_footer(parent) + + local header_flow = view_task_footer.add{ type = "flow" } + header_flow.add{ + type = "label", + style = "frame_title", + caption = { "exp-gui_task-list.caption-view-footer" }, + } + header_flow.style.right_padding = 1 + header_flow.add{ type = "empty-widget" }.style.horizontally_stretchable = true + Elements.close_task_button(header_flow, container) + + local title_label = view_task_footer.add{ type = "label" } + local title_label_style = title_label.style + title_label_style.font = "default-bold" + title_label_style.single_line = false + title_label_style.padding = 4 + + local body_label = view_task_footer.add{ type = "label" } + body_label.style.single_line = false + body_label.style.padding = 4 + + local action_flow = view_task_footer.add{ type = "flow" } + local delete_task_button = Elements.delete_task_button(action_flow, container) + local edit_task_button = Elements.edit_task_button(action_flow, container) + + def.data[view_task_footer] = { + task = nil, + body_label = body_label, + title_label = title_label, + delete_task_button = delete_task_button, + edit_task_button = edit_task_button, + } + + return view_task_footer + end) --[[ @as any ]] + +--- Refresh the view task with new task details +--- @param view_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.view_task_footer.refresh(view_task_footer, task) + local elements = Elements.view_task_footer.data[view_task_footer] + Elements.delete_task_button.refresh(elements.delete_task_button, task) + Elements.edit_task_button.refresh(elements.edit_task_button, task) + elements.title_label.caption = task.title + elements.body_label.caption = task.body + elements.task = task +end + +--- Refresh the view task with previously selected task +--- If task is provided, then will only update if task is the selected task +--- @param view_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.view_task_footer.update(view_task_footer, task) + local elements = Elements.view_task_footer.data[view_task_footer] + if elements.task and (task == nil or task == elements.task) then + Elements.view_task_footer.refresh(view_task_footer, elements.task) + end +end + +--- Refresh the view task with the previously selected task +--- @param player LuaPlayer +function Elements.view_task_footer.update_player(player) + for _, view_task_footer in Elements.view_task_footer:tracked_elements(player) do + Elements.view_task_footer.update(view_task_footer) + end +end + +--- @class ExpGui_TaskList.elements.task_message_textfield.elements +--- @field confirm_task_button LuaGuiElement? + +--- Textfield element used in both the task create and edit footers +--- @class ExpGui_TaskList.elements.task_message_textfield: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, confirm_task_button: LuaGuiElement?): LuaGuiElement +Elements.task_message_textfield = Gui.define("task_list/task_message_textfield") + :draw{ + type = "text-box", + text = "", + } + :style{ + maximal_width = 268, + minimal_height = 100, + horizontally_stretchable = true, + } + :element_data{ + confirm_task_button = Gui.from_argument(1), + } + :on_text_changed(function(def, player, task_message_textfield) + --- @cast def ExpGui_TaskList.elements.task_message_textfield + local confirm_task_button = def.data[task_message_textfield].confirm_task_button + confirm_task_button.enabled = string.len(task_message_textfield.text) > 5 + end) --[[ @as any ]] + +--- Set the confirm task button to update on text changed +--- @param task_message_textfield LuaGuiElement +--- @param confirm_task_button LuaGuiElement +function Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) + Elements.task_message_textfield.data[task_message_textfield].confirm_task_button = confirm_task_button +end + +--- Refresh the task message field +--- @param task_message_textfield LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.task_message_textfield.refresh(task_message_textfield, task) + local elements = Elements.task_message_textfield.data[task_message_textfield] + local message = task.new and "" or task.title .. "\n" .. task.body + elements.confirm_task_button.enabled = string.len(message) > 5 + task_message_textfield.text = message + task_message_textfield.focus() +end + +--- @class ExpGui_TaskList.elements.confirm_task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field task_message_textfield LuaGuiElement +--- @field container LuaGuiElement + +--- Textfield element used in both the task create and edit footers +--- @class ExpGui_TaskList.elements.confirm_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, task_message_textfield: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.confirm_task_button = Gui.define("task_list/confirm_task_button") + :draw{ + type = "button", + name = Gui.from_name, + caption = { "exp-gui_task-list.caption-confirm" }, + tooltip = { "exp-gui_task-list.tooltip-confirm" }, + style = "shortcut_bar_button_green", + } + :style(Styles.footer_button) + :element_data{ + task_message_textfield = Gui.from_argument(1), + container = Gui.from_argument(2), + } + :on_click(function(def, player, confirm_task_button) + --- @cast def ExpGui_TaskList.elements.confirm_task_button + local elements = def.data[confirm_task_button] + local task_message_textfield = elements.task_message_textfield + local task = assert(elements.task) + + local parsed = Elements.confirm_task_button.parse(task_message_textfield.text) + task.last_edit_tick = game.tick + task.last_user = player + task.title = parsed.title + task.body = parsed.body + + Elements.container.close_footers(elements.container) + + local force = player.force --[[ @as LuaForce ]] + if task.new then + Elements.container.add_task(force, task) + else + Elements.container.refresh_force_online(force, task) + end + end) --[[ @as any ]] + +--- Refresh the confirm task button +--- @param confirm_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.confirm_task_button.refresh(confirm_task_button, task) + Elements.confirm_task_button.data[confirm_task_button].task = task +end + +--- Parse a task message into its two parts +--- @param message string +--- @return { title: string, body: string } +function Elements.confirm_task_button.parse(message) + -- Trim the spaces of the string + local trimmed = string.gsub(message, "^%s*(.-)%s*$", "%1") + local title, body = string.match(trimmed, "(.-)\n(.*)") + local parsed = { title = title, body = body } + if not title then + -- If it doesn't match the pattern return the str as a title + parsed.title = trimmed + parsed.body = "" + end + return parsed +end + +--- @class ExpGui_TaskList.elements.discard_task_button.elements +--- @field task ExpGui_TaskList.Task? +--- @field container LuaGuiElement + +--- Button used to close an open task details +--- @class ExpGui_TaskList.element.discard_task_button: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.discard_task_button = Gui.define("task_list/discard_task_button") + :draw{ + type = "button", + caption = { "exp-gui_task-list.caption-discard" }, + tooltip = { "exp-gui_task-list.tooltip-discard" }, + style = "shortcut_bar_button_red", + } + :style(Styles.footer_button) + :element_data{ + container = Gui.from_argument(1), + } + :on_click(function(def, player, discard_task_button) + --- @cast def ExpGui_TaskList.element.discard_task_button + local elements = def.data[discard_task_button] + local task = assert(elements.task) + if task.new then + Elements.container.close_footers(elements.container) + else + Elements.container.open_view_task(elements.container, task) + end + end) --[[ @as any ]] + +--- Refresh the discard task button +--- @param discard_task_button LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.discard_task_button.refresh(discard_task_button, task) + Elements.discard_task_button.data[discard_task_button].task = task +end + +--- @class ExpGui_TaskList.elements.edit_task_footer.elements +--- @field task ExpGui_TaskList.Task? +--- @field header LuaGuiElement +--- @field task_message_textfield LuaGuiElement +--- @field discard_task_button LuaGuiElement +--- @field confirm_task_button LuaGuiElement + +--- The view footer used for view task details +--- @class ExpGui_TaskList.elements.edit_task_footer: ExpElement +--- @field data table +--- @overload fun(parent: LuaGuiElement, container: LuaGuiElement): LuaGuiElement +Elements.edit_task_footer = Gui.define("task_list/edit_task_footer") + :draw(function(def, parent, container) + --- @cast def ExpGui_TaskList.elements.edit_task_footer + local edit_task_footer = Elements.task_footer(parent) + + local header = edit_task_footer.add{ + type = "label", + style = "frame_title", + caption = { "exp-gui_task-list.caption-edit-footer" }, + } + + local task_message_textfield = Elements.task_message_textfield(edit_task_footer) + + local action_flow = edit_task_footer.add{ type = "flow" } + local discard_task_button = Elements.discard_task_button(action_flow, container) + local confirm_task_button = Elements.confirm_task_button(action_flow, task_message_textfield, container) + Elements.task_message_textfield.set_confirm_task_button(task_message_textfield, confirm_task_button) + + def.data[edit_task_footer] = { + task = nil, + header = header, + task_message_textfield = task_message_textfield, + discard_task_button = discard_task_button, + confirm_task_button = confirm_task_button, + } + + return edit_task_footer + end) --[[ @as any ]] + +--- Refresh the view task with new task details +--- @param edit_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.edit_task_footer.refresh(edit_task_footer, task) + local player = Gui.get_player(edit_task_footer) + local elements = Elements.edit_task_footer.data[edit_task_footer] + task.editing[player.index] = player.name + + if elements.task and elements.task ~= task then + elements.task.editing[player.index] = nil + end + + elements.header.caption = { + task.new and "exp-gui_task-list.caption-create-footer" or "exp-gui_task-list.caption-edit-footer" + } + + Elements.task_message_textfield.refresh(elements.task_message_textfield, task) + Elements.confirm_task_button.refresh(elements.confirm_task_button, task) + Elements.discard_task_button.refresh(elements.discard_task_button, task) + elements.task = task +end + +--- Refresh the view task with previously selected task +--- If task is provided, then will only update if task is the selected task +--- @param edit_task_footer LuaGuiElement +--- @param task ExpGui_TaskList.Task? +function Elements.edit_task_footer.update(edit_task_footer, task) + local elements = Elements.edit_task_footer.data[edit_task_footer] + if elements.task and (task == nil or task == elements.task) then + Elements.edit_task_footer.refresh(edit_task_footer, elements.task) + end +end + +--- Refresh the view task with the previously selected task +--- @param player LuaPlayer +function Elements.edit_task_footer.update_player(player) + for _, edit_task_footer in Elements.edit_task_footer:tracked_elements(player) do + Elements.edit_task_footer.update(edit_task_footer) + end +end + +--- Clear the previously edited task +--- @param edit_task_footer LuaGuiElement +function Elements.edit_task_footer.clear(edit_task_footer) + local elements = Elements.edit_task_footer.data[edit_task_footer] + if elements.task then + local player = Gui.get_player(edit_task_footer) + elements.task.editing[player.index] = nil + elements.task = nil + end +end + +--- @class ExpGui_TaskList.elements.container.elements +--- @field task_list LuaGuiElement +--- @field view_task_footer LuaGuiElement +--- @field edit_task_footer LuaGuiElement + +--- Container added to the left gui flow +--- @class ExpGui_TaskList.elements.container: ExpElement +--- @field data table | table | table<"global_data", { next_task_id: number }> +Elements.container = Gui.define("task_list/container") + :track_all_elements() + :draw(function(def, parent) + --- @cast def ExpGui_TaskList.elements.container + local container = Gui.elements.container(parent) -- width 268 + local root = Gui.elements.container.get_root_element(container) + local elements = {} + + -- Add the header + local header = Gui.elements.header(container, { + caption = { "exp-gui_task-list.caption-main" }, + tooltip = { "exp-gui_task-list.tooltip-sub" }, + }) + + -- Add buttons to the header + local new_task_button = Elements.new_task_button(header, root) + Elements.new_task_button.refresh(new_task_button) + + -- Add the task table and footers + elements.task_list = Elements.task_list(container) + + -- Add tasks to the list if there are any + local player = Gui.get_player(parent) + local force = player.force --[[ @as LuaForce ]] + local tasks = def.data[force] + if tasks then + for _, task in ipairs(tasks) do + Elements.task_list.add_task(elements.task_list, task) + end + end + + -- Add the footers + elements.view_task_footer = Elements.view_task_footer(container, root) + elements.edit_task_footer = Elements.edit_task_footer(container, root) + elements.view_task_footer.visible = false + elements.edit_task_footer.visible = false + + -- Set the data and return + def.data[root] = elements --[[ @as any ]] + return root + end) + :global_data{ next_task_id = 1 } + :force_data{} --[[ @as any ]] + +--- Check if a force has tasks +--- @param force LuaForce +--- @return boolean +function Elements.container.has_tasks(force) + local tasks = Elements.container.data[force] + return tasks and #tasks > 0 +end + +--- Get the next task id +--- @return number +function Elements.container.next_task_id() + local next_task_id = Elements.container.data.global_data.next_task_id + Elements.container.data.global_data.next_task_id = next_task_id + 1 + return next_task_id +end + +--- Add a new task for a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.add_task(force, task) + local tasks = Elements.container.data[force] + tasks[#tasks + 1] = task + task.deleted = nil + task.new = nil + + Elements.no_tasks_header.refresh_force_online(force) + for _, container in Elements.container:online_elements(force) do + local task_list = Elements.container.data[container].task_list + Elements.task_list.add_task(task_list, task) + end +end + +--- Remove a task from a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.remove_task(force, task) + local tasks = Elements.container.data[force] + table.remove_element(tasks, task) + task.deleted = true + + Elements.no_tasks_header.refresh_force_online(force) + for _, container in Elements.container:online_elements(force) do + local task_list = Elements.container.data[container].task_list + Elements.task_list.remove_task(task_list, task) + end +end + +--- Open the view footer +--- @param container LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.container.open_view_task(container, task) + local elements = Elements.container.data[container] + Elements.view_task_footer.refresh(elements.view_task_footer, task) + Elements.edit_task_footer.clear(elements.edit_task_footer) + Elements.task_list.select_task_button(elements.task_list, task) + elements.view_task_footer.visible = true + elements.edit_task_footer.visible = false +end + +--- Open the edit footer +--- @param container LuaGuiElement +--- @param task ExpGui_TaskList.Task +function Elements.container.open_edit_task(container, task) + local elements = Elements.container.data[container] + Elements.edit_task_footer.refresh(elements.edit_task_footer, task) + Elements.task_list.select_task_button(elements.task_list, task) + elements.edit_task_footer.visible = true + elements.view_task_footer.visible = false +end + +--- Close the footers +--- @param container LuaGuiElement +function Elements.container.close_footers(container) + local elements = Elements.container.data[container] + Elements.edit_task_footer.clear(elements.edit_task_footer) + Elements.task_list.select_task_button(elements.task_list) + elements.view_task_footer.visible = false + elements.edit_task_footer.visible = false +end + +--- Refresh all tasks for a player +--- @param player LuaPlayer +function Elements.container.refresh_player(player) + local tasks = Elements.container.data[ player.force --[[ @as LuaForce ]] ] + for _, container in Elements.container:tracked_elements(player) do + local elements = Elements.container.data[container] + Elements.task_list.refresh_tasks(elements.task_list, tasks) + Elements.view_task_footer.update(elements.view_task_footer) + Elements.edit_task_footer.update(elements.edit_task_footer) + end +end + +--- Refresh a tasks for a force +--- @param force LuaForce +--- @param task ExpGui_TaskList.Task +function Elements.container.refresh_force_online(force, task) + for _, container in Elements.container:online_elements(force) do + local elements = Elements.container.data[container] + Elements.task_list.refresh_task(elements.task_list, task) + Elements.view_task_footer.update(elements.view_task_footer, task) + Elements.edit_task_footer.update(elements.edit_task_footer, task) + end +end + +--- Add the element to the left flow with a toolbar button +Gui.add_left_element(Elements.container, function(player) + return Elements.container.has_tasks(player.force --[[ @as LuaForce ]]) +end) + +Gui.toolbar.create_button{ + name = "toggle_task_list", + left_element = Elements.container, + sprite = "utility/not_enough_repair_packs_icon", + tooltip = { "exp-gui_task-list.tooltip-main" }, + visible = function(player, element) + return Roles.player_allowed(player, "gui/task-list") + end +} + +--- Update the gui when the player joins because it is likely to be outdated +--- @param event EventData.on_player_joined_game +local function refresh_player_tasks(event) + local player = Gui.get_player(event) + Elements.container.refresh_player(player) + Elements.no_tasks_header.refresh_player(player) +end + +--- Update the gui when the player joins because it is likely to be outdated +--- @param event EventData.on_player_joined_game +local function refresh_player_permissions(event) + local player = Gui.get_player(event) + Elements.new_task_button.refresh_player(player) + Elements.view_task_footer.update_player(player) + Elements.edit_task_footer.update_player(player) +end + +local e = defines.events + +return { + elements = Elements, + events = { + [e.on_player_joined_game] = refresh_player_tasks, + [e.on_player_changed_force] = refresh_player_tasks, + [Roles.events.on_role_assigned] = refresh_player_permissions, + [Roles.events.on_role_unassigned] = refresh_player_permissions, + } +} diff --git a/exp_scenario/module/locale/en.cfg b/exp_scenario/module/locale/en.cfg index 5c3e2b51..89ab2e3b 100644 --- a/exp_scenario/module/locale/en.cfg +++ b/exp_scenario/module/locale/en.cfg @@ -376,6 +376,28 @@ type-player=Player type-static=Static type-loop=Loop +[exp-gui_task-list] +caption-main=Task List [img=info] +tooltip-main=Task List +tooltip-sub=Tasks that remain to be done\n- You can use richtext to include images [img=utility/not_enough_repair_packs_icon] or [color=blue]color[/color] your tasks. +caption-no-tasks=No tasks found! +tooltip-no-tasks=Click on the plus button to the top right of this window to add a new task! +tooltip-last-edit=Last edited by __1__ at __2__ +tooltip-close=Close task details +tooltip-new=Add new task +caption-confirm=[img=utility/check_mark] Confirm +tooltip-confirm=Save changes (minimum of 5 characters long) +caption-discard=[img=utility/close_black] Discard +tooltip-discard=Discard changes +caption-delete=[img=utility/trash] Delete +tooltip-delete=Delete task +caption-edit=[img=utility/rename_icon] Edit +tooltip-edit=Currently being edited by: __1__ +tooltip-edit-none=Currently being edited by: Nobody +caption-create-footer=Create task +caption-edit-footer=Edit task +caption-view-footer=Task details + [exp_afk-kick] kick-message=All players were kicked because everyone was AFK. diff --git a/exp_scenario/module/locale/zh-CN.cfg b/exp_scenario/module/locale/zh-CN.cfg index 8768808a..bc3d4162 100644 --- a/exp_scenario/module/locale/zh-CN.cfg +++ b/exp_scenario/module/locale/zh-CN.cfg @@ -376,6 +376,28 @@ type-player=用戶 type-static=靜態 type-loop=循環 +[exp-gui_task-list] +caption-main=工作流程 [img=info] +tooltip-main=工作流程 +tooltip-sub=需要完成的工作流程\n- 你可以用富文本來加上圖片 [img=utility/not_enough_repair_packs_icon] 或 [color=blue] 顏色[/color]。 +caption-no-tasks=沒有工作流程 +tooltip-no-tasks=按加號加入工作流程 +tooltip-last-edit=最後由 __1__ 在 __2__ 修改 +tooltip-close=關閉工作流程 +tooltip-new=加入工作流程 +caption-confirm=[img=utility/check_mark] 確認 +confirm-tooltip=儲存工作流程 (最少要5個字長) +caption-discard=[img=utility/close_black] 放棄 +tooltip-discard=放棄更改動 +caption-delete=[img=utility/trash] 刪除 +tooltip-delete=刪除工作流程 +caption-edit=[img=utility/rename_icon] 修改工作流程 +tooltip-edit=現被 __1__ 修改中 +tooltip-edit-none=現沒有被人修改 +caption-create-footer=加入工作流程 +caption-edit-footer=修改工作流程 +caption-view-footer=工作流程細節 + [exp_afk-kick] kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。 diff --git a/exp_scenario/module/locale/zh-TW.cfg b/exp_scenario/module/locale/zh-TW.cfg index 8768808a..bc3d4162 100644 --- a/exp_scenario/module/locale/zh-TW.cfg +++ b/exp_scenario/module/locale/zh-TW.cfg @@ -376,6 +376,28 @@ type-player=用戶 type-static=靜態 type-loop=循環 +[exp-gui_task-list] +caption-main=工作流程 [img=info] +tooltip-main=工作流程 +tooltip-sub=需要完成的工作流程\n- 你可以用富文本來加上圖片 [img=utility/not_enough_repair_packs_icon] 或 [color=blue] 顏色[/color]。 +caption-no-tasks=沒有工作流程 +tooltip-no-tasks=按加號加入工作流程 +tooltip-last-edit=最後由 __1__ 在 __2__ 修改 +tooltip-close=關閉工作流程 +tooltip-new=加入工作流程 +caption-confirm=[img=utility/check_mark] 確認 +confirm-tooltip=儲存工作流程 (最少要5個字長) +caption-discard=[img=utility/close_black] 放棄 +tooltip-discard=放棄更改動 +caption-delete=[img=utility/trash] 刪除 +tooltip-delete=刪除工作流程 +caption-edit=[img=utility/rename_icon] 修改工作流程 +tooltip-edit=現被 __1__ 修改中 +tooltip-edit-none=現沒有被人修改 +caption-create-footer=加入工作流程 +caption-edit-footer=修改工作流程 +caption-view-footer=工作流程細節 + [exp_afk-kick] kick-message=因地圖中沒有活躍玩家,所以所有人都已被請離。