diff --git a/locale/en/gui.cfg b/locale/en/gui.cfg index d6385837..8a822b82 100644 --- a/locale/en/gui.cfg +++ b/locale/en/gui.cfg @@ -67,18 +67,26 @@ net-tooltip=Total net: __1__ no-packs=You have not made any science packs yet [task-list] -main-caption=Task List +main-caption=Task List [img=info] main-tooltip=Task List -sub-tooltip=Tasks that remain to be done -no-tasks=You have no tasks +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-tooltip=Save changes -cancel-tooltip=Discard changes +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 -discard-tooltip=Remove task - +create-footer-header=Create task +edit-footer-header=Edit task +view-footer-header=Task details [autofill] main-tooltip=Autofill settings toggle-section-caption=__1__ __2__ diff --git a/modules/control/tasks.lua b/modules/control/tasks.lua index ec0f8c38..04d61b4d 100644 --- a/modules/control/tasks.lua +++ b/modules/control/tasks.lua @@ -31,49 +31,39 @@ end) --[[-- 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] number task_number the order place to add the task to, appends to end if omited @tparam[opt] string player_name the player who added this task, will cause them to be listed under editing -@tparam[opt] string task_message the message that is used for this task, if not given default is used +@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, nil, game.player.name) +local task_id = Tasks.add_task(game.player.force.name, game.player.name, nil, nil) ]] -function Tasks.add_task(force_name, task_number, player_name, task_message) +function Tasks.add_task(force_name, player_name, task_title, task_body) -- Get a new task id local task_id = tostring(force_tasks._uid) - task_message = task_message or 'New Task' force_tasks._uid = force_tasks._uid + 1 -- Get the existing tasks for this force - local tasks = force_tasks[force_name] - if not tasks then - force_tasks[force_name] = {} - tasks = force_tasks[force_name] + local task_ids = force_tasks[force_name] + if not task_ids then + task_ids = {} + force_tasks[force_name] = task_ids end -- Insert the task id into the forces tasks - if task_number then - table.insert(tasks, task_number, task_id) - else - table.insert(tasks, task_id) - end - - -- Create the editing table - local editing = {} - if player_name then - editing[player_name] = true - end + 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, - message = task_message, + title = task_title or '', + body = task_body or '', last_edit_name = player_name or '', last_edit_time = game.tick, - currently_editing = editing + currently_editing = {} }) return task_id @@ -94,19 +84,21 @@ function Tasks.remove_task(task_id) end --[[-- Update the message and last edited information for a task -@tparam string task_id the uid of the task that you want to update -@tparam string new_message the message that you want to have for the task -@tparam[opt='server'] string player_name the name of the player who made the edit +@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, 'We need more iron!', game.player.name) +Task.update_task(task_id, game.player.name, 'We need more iron!', 'Build more iron outposts.') ]] -function Tasks.update_task(task_id, new_message, player_name) +function Tasks.update_task(task_id, player_name, task_title, task_body) TaskData:update(task_id, function(_, task) - task.last_edit_name = player_name or '' + task.last_edit_name = player_name task.last_edit_time = game.tick - task.message = new_message + task.title = task_title + task.body = task_body end) end @@ -181,4 +173,4 @@ function Tasks.get_editing(task_id, player_name) end -- Module Return -return Tasks \ No newline at end of file +return Tasks diff --git a/modules/gui/task-list.lua b/modules/gui/task-list.lua index a83b3aa5..4b5c8ed4 100644 --- a/modules/gui/task-list.lua +++ b/modules/gui/task-list.lua @@ -3,18 +3,37 @@ @gui Task-List @alias task_list ]] - -local Gui = require 'expcore.gui' --- @dep expcore.gui -local Event = require 'utils.event' --- @dep utils.event -local Roles = require 'expcore.roles' --- @dep expcore.roles -local config = require 'config.gui.tasks' --- @dep config.gui.tasks -local Tasks = require 'modules.control.tasks' --- @dep modules.control.tasks +local Gui = require "expcore.gui" --- @dep expcore.gui +local Event = require "utils.event" --- @dep utils.event +local Roles = require "expcore.roles" --- @dep expcore.roles +local Datastore = require "expcore.datastore" --- @dep expcore.datastore +local config = require "config.gui.tasks" --- @dep config.gui.tasks +local Tasks = require "modules.control.tasks" --- @dep modules.control.tasks local format_time = _C.format_time --- @dep expcore.common +--- 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 = { - sprite20 = Gui.sprite_style(20), - sprite22 = Gui.sprite_style(20, nil, { right_margin = -3 }) + 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 @@ -29,11 +48,11 @@ local function check_player_permissions(player, task) end -- Check player has permisison based on value in the config - if allow_edit_task == 'all' then + if allow_edit_task == "all" then return true - elseif allow_edit_task == 'admin' then + elseif allow_edit_task == "admin" then return player.admin - elseif allow_edit_task == 'expcore.roles' then + elseif allow_edit_task == "expcore.roles" then return Roles.player_allowed(player, config.expcore_roles_allow_edit_task) end @@ -44,11 +63,11 @@ local function check_player_permissions(player, 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 + if allow_add_task == "all" then return true - elseif allow_add_task == 'admin' then + elseif allow_add_task == "admin" then return player.admin - elseif allow_add_task == 'expcore.roles' then + elseif allow_add_task == "expcore.roles" then return Roles.player_allowed(player, config.expcore_roles_allow_add_task) end @@ -57,360 +76,709 @@ local function check_player_permissions(player, task) end end ---- Button displayed in the ehader bar, used to add a new task +--- Elements + +--- Button displayed in the header bar, used to add a new task -- @element add_new_task local add_new_task = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/add', - tooltip = {'task-list.add-tooltip'}, - style = 'tool_button' -} -:style(Styles.sprite20) -:on_click(function(player, _,_) - Tasks.add_task(player.force.name, nil, player.name) -end) + Gui.element { + type = "sprite-button", + sprite = "utility/add", + tooltip = {"task-list.add-tooltip"}, + style = "tool_button" +}:style(Styles.sprite22):on_click( + function(player, _, _) + -- Disable editing + PlayerIsEditing:set(player, false) + -- Clear selected + PlayerSelected:set(player, nil) + -- Open task create footer + PlayerIsCreating:set(player, true) + end +) ---- Button displayed next to tasks which the user is can edit, used to start editing a task --- @element edit_task -local edit_task = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/rename_icon_normal', - tooltip = {'task-list.edit-tooltip-none'}, - style = 'tool_button' -} -:style(Styles.sprite20) -:on_click(function(player, element, _) - local task_id = element.parent.name:sub(6) - Tasks.set_editing(task_id, player.name, true) -end) +--- Header displayed when no tasks are in the task list +-- @element no_tasks_found +local no_tasks_found = + Gui.element( + function(_, parent) + local header = + parent.add { + name = "no_tasks_found_element", + type = "frame", + style = "negative_subheader_frame" + } + header.style.horizontally_stretchable = true + -- Flow used for centering the content in the subheader + local center = + header.add { + type = "flow", + style = "centering_horizontal_flow" + } + 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 +) ---- Button displayed next to tasks which the user is can edit, used to delete a task from the list --- @element discard_task -local discard_task = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/trash', - tooltip = {'task-list.discard-tooltip'}, - style = 'tool_button' -} -:style(Styles.sprite20) -:on_click(function(_, element, _) - local task_id = element.parent.name:sub(6) - Tasks.remove_task(task_id) -end) - ---- Set of three elements which make up each row of the task table --- @element add_task_base -local add_task_base = -Gui.element(function(_, parent, task_id) - -- Add the task number label - local task_number = parent.add{ - name = 'count-'..task_id, - type = 'label', - caption = '0)' +--- Frame element with the right styling +-- @element subfooter_frame +local subfooter_frame = + Gui.element( + function(_, parent, name) + return parent.add { + type = "frame", + name = name, + direction = "vertical", + style = "subfooter_frame" + } + end +):style( + { + padding = 5, + use_header_filler = false, + horizontally_stretchable = true } - task_number.style.left_margin = 1 +) - -- Add a flow which will contain the task message and edit buttons - local task_flow = parent.add{ name = task_id, type = 'flow', } - task_flow.style.padding = 0 +--- Label element preset +-- @element subfooter_label +local subfooter_label = + Gui.element( + function(_, parent, caption) + return parent.add { + name = "footer_label", + type = "label", + style = "heading_1_label", + caption = caption + } + end +) - -- Add the two edit buttons outside the task flow - local edit_flow = Gui.alignment(parent, 'edit-'..task_id) - edit_task(edit_flow) - discard_task(edit_flow) +--- Action flow that contains action buttons +-- @element subfooter_actions +local subfooter_actions = + Gui.element( + function(_, parent) + return parent.add { + type = "flow", + name = "actions" + } + end +) - -- Return the task flow as the main element - return task_flow -end) +--- 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.element( + function(event_trigger, 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 = event_trigger, + type = "button", + style = "list_box_item", + caption = task.title + } + button.style.horizontally_stretchable = true + button.style.horizontally_squashable = true + return flow + end +):on_click( + function(player, element, _) + local task_id = element.parent.caption + PlayerSelected:set(player, task_id) + end +) --- Removes the three elements that are added as part of the task base -local function remove_task_base(parent, task_id) - Gui.destroy_if_valid(parent['count-'..task_id]) - Gui.destroy_if_valid(parent['edit-'..task_id]) - Gui.destroy_if_valid(parent[task_id]) +--- Scrollable list of all tasks +-- @element task_list +local task_list = + Gui.element( + 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 = 280 + + 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.element { + type = "button", + caption = {"", "[img=utility/rename_icon_normal] ", {"task-list.edit"}}, + tooltip = {"task-list.edit-tooltip"}, + style = "shortcut_bar_button" +}:style(Styles.footer_button):on_click( + function(player, _, _) + 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.element{ + type = "sprite-button", + sprite = "utility/collapse_dark", + hovered_sprite = "utility/collapse", + tooltip = {"task-list.close-tooltip"} +} +:style(Styles.sprite22):on_click( + function(player, _, _) + 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.element { + type = "button", + caption = {"", "[img=utility/trash] ", {"task-list.delete"}}, + tooltip = {"task-list.delete-tooltip"}, + style = "shortcut_bar_button_red" +}:style(Styles.footer_button):on_click( + function(player, _, _) + 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.element( + 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.alignment(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 displayed next to tasks which the user is currently editing, used to save changes --- @element confirm_edit -local task_editing -local confirm_edit = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/confirm_slot', - tooltip = {'task-list.confirm-tooltip'}, - style = 'shortcut_bar_button_green' -} -:style(Styles.sprite22) -:on_click(function(player, element, _) - local task_id = element.parent.name - local new_message = element.parent[task_editing.name].text - Tasks.set_editing(task_id, player.name) - Tasks.update_task(task_id, new_message, player.name) -end) +-- Button variable initialisation because it is used inside the textfield element events +local task_edit_confirm_button +local task_create_confirm_button ---- Button displayed next to tasks which the user is currently editing, used to discard changes --- @element cancel_edit -local cancel_edit = -Gui.element{ - type = 'sprite-button', - sprite = 'utility/close_black', - tooltip = {'task-list.cancel-tooltip'}, - style = 'shortcut_bar_button_red' -} -:style(Styles.sprite22) -:on_click(function(player, element, _) - local task_id = element.parent.name - Tasks.set_editing(task_id, player.name) -end) - ---- Editing state for a task, contrins a text field and the two edit buttons --- @element task_editing -task_editing = -Gui.element(function(event_trigger, parent, task) - local message = task.message - - -- Draw the element - local element = - parent.add{ - name = event_trigger, - type = 'textfield', - text = message, - clear_and_focus_on_right_click = true +--- Textfield element used in both the task create and edit footers +-- @element task_message_textfield +local task_message_textfield = + Gui.element { + type = "text-box", + text = "" +}:style( + { + maximal_width = 268, + minimal_height = 100, + horizontally_stretchable = true } +):on_text_changed( + function(player, element, _) + local isEditing = PlayerIsEditing:get(player) + local isCreating = PlayerIsCreating:get(player) - -- Add the edit buttons - cancel_edit(parent) - confirm_edit(parent) + local valid = string.len(element.text) > 5 - -- Return the element - return element -end) -:style{ - maximal_width = 110, - height = 20 -} -:on_confirmed(function(player, element, _) - local task_id = element.parent.name - local new_message = element.text - Tasks.set_editing(task_id, player.name) - Tasks.update_task(task_id, new_message, player.name) -end) - ---- Default state for a task, contains only a label with the task message --- @element task_label -local task_label = -Gui.element(function(_, parent, task) - local message = task.message - local last_edit_name = task.last_edit_name - local last_edit_time = task.last_edit_time - -- Draw the element - return parent.add{ - name = task_editing.name, - type = 'label', - caption = message, - tooltip = {'task-list.last-edit', last_edit_name, format_time(last_edit_time)} - } -end) -:style{ - single_line = false, - maximal_width = 150 -} - ---- Updates a task for a player -local function update_task(player, task_table, task_id) - local task = Tasks.get_task(task_id) - local task_ids = Tasks.get_force_task_ids(player.force.name) - local task_number = table.get_index(task_ids, task_id) - - -- Task no longer exists so should be removed from the list - if not task then - task_table.parent.no_tasks.visible = #task_ids == 0 - remove_task_base(task_table, task_id) - return + if isCreating then + element.parent.actions[task_create_confirm_button.name].enabled = valid + elseif isEditing then + element.parent.actions[task_edit_confirm_button.name].enabled = valid + end end +) - -- Get the task flow for this task - local task_flow = task_table[task_id] or add_task_base(task_table, task_id) - task_table.parent.no_tasks.visible = false - task_table['count-'..task_id].caption = task_number..')' - - -- Update the edit flow - local edit_flow = task_table['edit-'..task_id] - local player_allowed_edit = check_player_permissions(player, task) - local players_editing = table.get_keys(task.currently_editing) - local edit_task_element = edit_flow[edit_task.name] - local discard_task_element = edit_flow[discard_task.name] - - edit_task_element.visible = player_allowed_edit - discard_task_element.visible = player_allowed_edit - if #players_editing > 0 then - edit_task_element.hovered_sprite = 'utility/warning_icon' - edit_task_element.tooltip = {'task-list.edit-tooltip', table.concat(players_editing, ', ')} - else - edit_task_element.hovered_sprite = edit_task_element.sprite - edit_task_element.tooltip = {'task-list.edit-tooltip-none'} +--- Button to confirm the changes inside the task edit footer +-- @element task_edit_confirm_button +task_edit_confirm_button = + Gui.element { + type = "button", + 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(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 +) - -- Check if the player is was editing and/or currently editing - local task_entry = task_flow[task_editing.name] or task_label(task_flow, task) - local player_was_editing = task_entry.type == 'textfield' - local player_is_editing = task.currently_editing[player.name] +--- Button to discard the changes inside the task edit footer +-- @element edit_task_discard_button +local edit_task_discard_button = + Gui.element { + 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(player, _, _) + local selected = PlayerSelected:get(player) + Tasks.set_editing(selected, player.name, nil) + PlayerIsEditing:set(player, false) + end +) - -- Update the task flow - if not player_was_editing and not player_is_editing then - -- Update the task message label - local message = task.message +--- Subfooter inside the tasklist container that holds all the elements for editing a task +-- @element task_edit_footer +local task_edit_footer = + Gui.element( + 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.element { + type = "button", + 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(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.element { + 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(player, _, _) + 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.element( + 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_from_element(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) + local element = task_list_item(task_list_element, task) + -- Set tooltip local last_edit_name = task.last_edit_name local last_edit_time = task.last_edit_time - task_entry.caption = message - task_entry.tooltip = {'task-list.last-edit', last_edit_name, format_time(last_edit_time)} - - elseif player_was_editing and not player_is_editing then - -- Player was editing but is no longer, remove text field and add label - edit_task_element.enabled = true - task_flow.clear() - task_label(task_flow, task) - - elseif not player_was_editing and player_is_editing then - -- Player was not editing but now is, remove label and add text field - edit_task_element.enabled = false - task_flow.clear() - task_editing(task_flow, task).focus() - task_table.parent.scroll_to_element(task_flow, 'top-third') - - end -end - --- Update all the tasks for a player -local function update_all_tasks(player, scroll_table) - local task_ids = Tasks.get_force_task_ids(player.force.name) - if #task_ids > 0 then - for _, task_id in ipairs(task_ids) do - update_task(player, scroll_table, task_id) - end + element[task_list_item.name].tooltip = {"task-list.last-edit", last_edit_name, format_time(last_edit_time)} end end --- Main task list container for the left flow -- @element task_list_container local task_list_container = -Gui.element(function(event_trigger, parent) - -- Draw the internal container - local container = Gui.container(parent, event_trigger, 200) + Gui.element( + function(event_trigger, parent) + -- Draw the internal container + local container = Gui.container(parent, event_trigger, 268) + container.style.maximal_width = 268 + container.style.minimal_width = 268 - -- Draw the header - local header = Gui.header( - container, - {'task-list.main-caption'}, - {'task-list.sub-tooltip'}, - true - ) + -- Draw the header + local header = Gui.header(container, {"task-list.main-caption"}, {"task-list.sub-tooltip"}, true) - -- Draw the new task button - local player = Gui.get_player_from_element(parent) - local add_new_task_element = add_new_task(header) - add_new_task_element.visible = check_player_permissions(player) + -- Draw the new task button + local player = Gui.get_player_from_element(parent) + local add_new_task_element = add_new_task(header) + add_new_task_element.visible = check_player_permissions(player) - -- Draw the scroll table for the tasks - local scroll_table = Gui.scroll_table(container, 190, 3) - scroll_table.draw_horizontal_lines = true - scroll_table.vertical_centering = false + -- Draw no task found element + no_tasks_found(container) - -- Change the style of the scroll table - local scroll_table_style = scroll_table.style - scroll_table_style.top_cell_padding = 3 - scroll_table_style.bottom_cell_padding = 3 + -- Draw task list element + local task_list_element = task_list(container) + repopulate_task_list(task_list_element) - -- Draw the no tasks label - local no_tasks_label = - scroll_table.parent.add{ - name = 'no_tasks', - type = 'label', - caption = {'task-list.no-tasks'} - } - - -- Change the style of the no tasks label - local no_tasks_style = no_tasks_label.style - no_tasks_style.padding = {2, 4} - no_tasks_style.single_line = false - no_tasks_style.width = 200 - - -- Add any existing tasks - local task_ids = Tasks.get_force_task_ids(player.force.name) - if #task_ids > 0 then - no_tasks_label.visible = false - for _, task_id in ipairs(task_ids) do - update_task(player, scroll_table, task_id) - end + 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 - - -- Return the exteral container - return container.parent -end) -:add_to_left_flow(function(player) - local task_ids = Tasks.get_force_task_ids(player.force.name) - return #task_ids > 0 -end) +):add_to_left_flow( + function(player) + local task_ids = Tasks.get_force_task_ids(player.force.name) + return #task_ids > 0 + end +) --- Button on the top flow used to toggle the task list container -- @element toggle_left_element -Gui.left_toolbar_button('utility/not_enough_repair_packs_icon', {'task-list.main-tooltip'}, task_list_container, function(player) - return Roles.player_allowed(player, 'gui/task-list') -end) +Gui.left_toolbar_button( + "utility/not_enough_repair_packs_icon", + {"task-list.main-tooltip"}, + task_list_container, + function(player) + 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 element + -- If task does not exist yet add it to the list + if not task_list_element["task-" .. task_id] then + element = task_list_item(task_list_element, task) + else + -- If the task exists update the caption + element = task_list_element["task-" .. task_id] + element[task_list_item.name].caption = task.title + end + -- Set tooltip + local last_edit_name = task.last_edit_name + local last_edit_time = task.last_edit_time + element[task_list_item.name].tooltip = {"task-list.last-edit", last_edit_name, format_time(last_edit_time)} +end + +-- Update the footer task edit view +local update_task_edit_footer = function(player, task_id) + local task = Tasks.get_task(task_id) + local frame = Gui.get_left_element(player, task_list_container) + local edit_flow = frame.container.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 frame = Gui.get_left_element(player, task_list_container) + local view_flow = frame.container.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 -Tasks.on_update(function(task_id, task, old_task) - -- Get the force to update, task is nil when removed - local force - if task then - force = game.forces[task.force_name] - else - force = game.forces[old_task.force_name] - end +--- 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 - local task_ids = Tasks.get_force_task_ids(force.name) - for _, player in pairs(force.connected_players) do - local frame = Gui.get_left_element(player, task_list_container) - local scroll_table = frame.container.scroll.table - - -- Update the task that was changed - update_task(player, scroll_table, task_id) - - -- Update the numbering of the other tasks if the task was removed - if not task then - for task_number, next_task_id in pairs(task_ids) do - scroll_table['count-'..next_task_id].caption = task_number..')' + -- 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 frame = Gui.get_left_element(player, task_list_container) + local task_list_element = frame.container.scroll.task_list + + -- Update the task that was changed + update_task(player, task_list_element, task_id) end end +) -end) +-- When a player is creating a new task. +PlayerIsCreating:on_update( + function(player_name, curr_state, _) + local player = game.players[player_name] ---- Update the tasks when the player joins -Event.add(defines.events.on_player_joined_game, function(event) - local player = game.players[event.player_index] - local frame = Gui.get_left_element(player, task_list_container) - local scroll_table = frame.container.scroll.table - update_all_tasks(player, scroll_table) -end) + local frame = Gui.get_left_element(player, task_list_container) + local create = frame.container.create + + -- Clear the textfield + local message_element = frame.container.create[task_message_textfield.name] + local confirm_button_element = frame.container.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 frame = Gui.get_left_element(player, task_list_container) + local task_list_element = frame.container.scroll.task_list + local view_flow = frame.container.view + local edit_flow = frame.container.edit + local isEditing = PlayerIsEditing:get(player) + local isCreating = 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 isCreating 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 isEditing 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 frame = Gui.get_left_element(player, task_list_container) + local view_flow = frame.container.view + local edit_flow = frame.container.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 container = Gui.get_left_element(player, task_list_container).container + -- 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 tasks, incase the user can now edit them - local scroll_table = container.scroll.table - update_all_tasks(player, scroll_table) - - -- Update the new task button incase the user can now add them + -- 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 = container.header.alignment[add_new_task.name] - add_new_task_element.visible = check_player_permissions(player) + add_new_task_element.visible = has_permission + local isCreating = PlayerIsCreating:get(player) + if isCreating 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) \ No newline at end of file +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 frame = Gui.get_left_element(player, task_list_container) + local task_list_element = frame.container.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) \ No newline at end of file