Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions spec/System/TestBuildListHelpers_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
describe("BuildListHelpers", function()
local originalBuildPath
local originalCloudErrorPopup
local originalFileSearch
local originalOpen
local buildListHelpers
local fileHeaders
local searches
local fileOpenCount
local searchCount
local cloudErrorPath

before_each(function()
originalBuildPath = main.buildPath
originalCloudErrorPopup = main.OpenCloudErrorPopup
originalFileSearch = _G.NewFileSearch
originalOpen = io.open
buildListHelpers = LoadModule("Modules/BuildListHelpers")
fileHeaders = { }
searches = { }
fileOpenCount = 0
searchCount = 0
cloudErrorPath = nil
main.buildPath = "Builds/"
main.OpenCloudErrorPopup = function(_, path)
cloudErrorPath = path
end
_G.NewFileSearch = function(pattern, foldersOnly)
searchCount = searchCount + 1
local entries = searches[(foldersOnly and "folders:" or "files:")..pattern]
if not entries or not entries[1] then return end
local index = 1
return {
GetFileName = function() return entries[index].name end,
GetFileModifiedTime = function() return entries[index].modified or 0 end,
NextFile = function()
index = index + 1
return entries[index] ~= nil
end,
}
end
io.open = function(path)
fileOpenCount = fileOpenCount + 1
if fileHeaders[path] == nil then return end
return {
read = function() return fileHeaders[path] or nil end,
close = function() end,
}
end
end)

after_each(function()
main.buildPath = originalBuildPath
main.OpenCloudErrorPopup = originalCloudErrorPopup
_G.NewFileSearch = originalFileSearch
io.open = originalOpen
end)

it("filters a recursive index without rescanning files", function()
searches["files:Builds/*.xml"] = { { name = "Root.xml", modified = 1 } }
searches["folders:Builds/*"] = { { name = "League", modified = 2 } }
searches["files:Builds/League/*.xml"] = { { name = "Nested.xml", modified = 3 } }
fileHeaders["Builds/Root.xml"] = '<Build level="80" className="Witch" ascendClassName="Occultist">'
fileHeaders["Builds/League/Nested.xml"] = '<Build level="90" className="Shadow" ascendClassName="Assassin">'

local index = buildListHelpers.ScanFolder("")
local scansAfterIndex = searchCount
local opensAfterIndex = fileOpenCount
local directEntries = buildListHelpers.FilterList(index, "", "")
local classMatches = buildListHelpers.FilterList(index, "", "CLASS:assassin")

assert.are.same(3, #index)
assert.are.same(2, #directEntries)
assert.are.same("Nested.xml", classMatches[1].fileName)
assert.are.same("League/", classMatches[1].subPath)
assert.are.same(scansAfterIndex, searchCount)
assert.are.same(opensAfterIndex, fileOpenCount)
end)

it("reports cloud read failures", function()
searches["files:Builds/*.xml"] = { { name = "Cloud.xml" } }
fileHeaders["Builds/Cloud.xml"] = false

local index = buildListHelpers.ScanFolder("")

assert.are.same(0, #index)
assert.are.same("Builds/Cloud.xml", cloudErrorPath)
end)

it("blocks descendant folder targets and selects duplicate filenames by path", function()
local rootFolder = { folderName = "Alpha", subPath = "" }
local childFolder = { folderName = "Beta", subPath = "Alpha/" }
local firstBuild = { fileName = "Same.xml", subPath = "Alpha/", fullFileName = "Builds/Alpha/Same.xml" }
local secondBuild = { fileName = "Same.xml", subPath = "Other/", fullFileName = "Builds/Other/Same.xml" }
local listMode = {
list = { firstBuild, secondBuild },
subPath = "",
BuildList = function() end,
}
local control = new("BuildListControl", nil, { 0, 0, 500, 500 }, listMode)

control:SelByFullFileName("Builds/Other/Same.xml")

assert.are.same(secondBuild, control.selValue)
assert.is_false(control:CanDragToValue(1, childFolder, { selValue = rootFolder }))
assert.is_true(buildListHelpers.CanMoveToSubPath(rootFolder, "Other/"))
control:SelByFullFileName("Builds/Missing.xml")
assert.is_nil(control.selValue)
end)
end)
74 changes: 52 additions & 22 deletions src/Classes/BuildListControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
--
local ipairs = ipairs
local s_format = string.format
local buildListHelpers = LoadModule("Modules/BuildListHelpers")

local BuildListClass = newClass("BuildListControl", "ListControl", function(self, anchor, rect, listMode)
self.ListControl(anchor, rect, 20, "VERTICAL", false, listMode.list)
Expand All @@ -29,13 +30,21 @@ local BuildListClass = newClass("BuildListControl", "ListControl", function(self
function self.controls.path:ReceiveDrag(type, build, source)
if type == "Build" then
for index, folder in ipairs(self.folderList) do
if index < #self.folderList and folder.button:IsMouseOver() then
if build.folderName then
main:MoveFolder(build.folderName, main.buildPath..build.subPath, main.buildPath..folder.path)
else
os.rename(build.fullFileName, listMode:GetDestName(folder.path, build.fileName))
if folder.button:IsMouseOver() then
if buildListHelpers.CanMoveToSubPath(build, folder.path) then
if build.folderName then
main:MoveFolder(build.folderName, main.buildPath..build.subPath, main.buildPath..folder.path)
else
local destPath = listMode:GetDestName(folder.path, build.fileName)
local res, msg = os.rename(build.fullFileName, destPath)
if not res then
main:OpenMessagePopup("Error", "Couldn't move '"..build.fullFileName.."' to '"..destPath.."': "..(msg or ""))
return
end
end
listMode:BuildList()
end
listMode:BuildList()
break
end
end
end
Expand All @@ -46,18 +55,22 @@ local BuildListClass = newClass("BuildListControl", "ListControl", function(self
end
end)

function BuildListClass:SelByFileName(selFileName)
for index, build in ipairs(self.list) do
if build.fileName == selFileName then
self:SelectIndex(index)
break
function BuildListClass:SelByFullFileName(fullFileName)
if fullFileName then
for index, build in ipairs(self.list) do
if build.fullFileName == fullFileName then
self:SelectIndex(index)
return
end
end
end
self.selIndex = nil
self.selValue = nil
end

function BuildListClass:LoadBuild(build)
if build.folderName then
self.controls.path:SetSubPath(self.listMode.subPath .. build.folderName .. "/")
self.controls.path:SetSubPath(build.subPath .. build.folderName .. "/")
else
main:SetMode("BUILD", build.fullFileName, build.buildName)
end
Expand Down Expand Up @@ -126,7 +139,7 @@ function BuildListClass:RenameBuild(build, copyOnName)
end
end
self.listMode:BuildList()
self:SelByFileName(newFileName)
self:SelByFullFileName(main.buildPath..build.subPath..newFileName)
end
main:ClosePopup()
self.listMode:SelectControl(self)
Expand Down Expand Up @@ -171,12 +184,21 @@ end
function BuildListClass:GetRowValue(column, index, build)
if column == 1 then
local label
local subPathPrefix = ""
if build.subPath and self.listMode and self.listMode.subPath and build.subPath ~= self.listMode.subPath then
local baseSub = self.listMode.subPath
if build.subPath:sub(1, #baseSub) == baseSub then
subPathPrefix = build.subPath:sub(#baseSub + 1)
else
subPathPrefix = build.subPath
end
end
if build.folderName then
label = ">> " .. build.folderName
label = ">> " .. subPathPrefix .. build.folderName
else
label = build.buildName or "?"
label = subPathPrefix .. (build.buildName or "?")
end
if self.cutBuild and self.cutBuild.buildName == build.buildName and self.cutBuild.folderName == build.folderName then
if self.cutBuild and self.cutBuild.buildName == build.buildName and self.cutBuild.folderName == build.folderName and self.cutBuild.subPath == build.subPath then
return "^xC0B0B0"..label
else
return label
Expand Down Expand Up @@ -205,18 +227,26 @@ end
function BuildListClass:ReceiveDrag(type, build, source)
if type == "Build" then
if self.hoverValue and self.hoverValue.folderName then
if build.folderName then
main:MoveFolder(build.folderName, main.buildPath..build.subPath, main.buildPath..self.hoverValue.subPath..self.hoverValue.folderName.."/")
else
os.rename(build.fullFileName, self.listMode:GetDestName(self.listMode.subPath..self.hoverValue.folderName.."/", build.fileName))
local targetSubPath = self.hoverValue.subPath .. self.hoverValue.folderName .. "/"
if buildListHelpers.CanMoveToSubPath(build, targetSubPath) then
if build.folderName then
main:MoveFolder(build.folderName, main.buildPath..build.subPath, main.buildPath..targetSubPath)
else
local destPath = self.listMode:GetDestName(targetSubPath, build.fileName)
local res, msg = os.rename(build.fullFileName, destPath)
if not res then
main:OpenMessagePopup("Error", "Couldn't move '"..build.fullFileName.."' to '"..destPath.."': "..(msg or ""))
return
end
end
self.listMode:BuildList()
end
self.listMode:BuildList()
end
end
end

function BuildListClass:CanDragToValue(index, build, source)
return build.folderName and source.selValue ~= build
return build.folderName and source.selValue ~= build and buildListHelpers.CanMoveToSubPath(source.selValue, build.subPath .. build.folderName .. "/")
end

function BuildListClass:OnSelClick(index, build, doubleClick)
Expand Down
21 changes: 16 additions & 5 deletions src/Classes/CompareTab.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1538,12 +1538,22 @@ function CompareTabClass:OpenImportFolderPopup()
controls = { },
}
function listHost:BuildList()
self.buildIndex = buildListHelpers.ScanFolder(self.subPath)
self:FilterBuildList()
end
function listHost:FilterBuildList()
wipeTable(self.list)
local scanned = buildListHelpers.ScanFolder(self.subPath, searchText)
for _, entry in ipairs(scanned) do
for _, entry in ipairs(buildListHelpers.FilterList(self.buildIndex, self.subPath, searchText)) do
t_insert(self.list, entry)
end
self:SortList()
end
function listHost:SortList()
local selectedFullFileName = controls.buildList and controls.buildList.selValue and controls.buildList.selValue.fullFileName
buildListHelpers.SortList(self.list, sortMode)
if controls.buildList then
controls.buildList:SelByFullFileName(selectedFullFileName)
end
end
function listHost:SelectControl(control)
-- Focus is managed by the popup's ControlHost; this is a no-op for the popup list.
Expand Down Expand Up @@ -1572,12 +1582,13 @@ function CompareTabClass:OpenImportFolderPopup()
-- Search box and sort dropdown sit above the build list.
controls.searchText = new("EditControl", {"TOPLEFT", nil, "TOPLEFT"}, {15, 25, 450, 20}, "", "Search", "%c%(%)", 100, function(buf)
searchText = buf
listHost:BuildList()
listHost:FilterBuildList()
end, nil, nil, true)
controls.searchText:SetPlaceholder("(e.g. class:assassin myfilename)")
controls.sort = new("DropDownControl", {"TOPLEFT", nil, "TOPLEFT"}, {475, 25, 210, 20}, buildListHelpers.buildSortDropList, function(index, value)
sortMode = value.sortMode
main.buildSortMode = value.sortMode
buildListHelpers.SortList(listHost.list, sortMode)
listHost:SortList()
end)
controls.sort:SelByValue(sortMode, "sortMode")

Expand All @@ -1590,7 +1601,7 @@ function CompareTabClass:OpenImportFolderPopup()
-- navigate folders, import builds, and suppress rename/delete/drag behaviors.
function controls.buildList:LoadBuild(build)
if build.folderName then
self.controls.path:SetSubPath(self.listMode.subPath .. build.folderName .. "/")
self.controls.path:SetSubPath(build.subPath .. build.folderName .. "/")
else
importBuildEntry(build)
end
Expand Down
4 changes: 4 additions & 0 deletions src/Classes/EditControl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ function EditClass:Draw(viewPort, noTooltip)
DrawImage(nil, caretX, textY, 1, textHeight)
end
else
if self.buf == '' and self.placeholder then
SetDrawColor(self.disableCol)
DrawString(textX, textY, "LEFT", textHeight, self.font, self.placeholder)
end
local pre = self.textCol .. self.buf:sub(1, self.caret - 1)
local post = self.buf:sub(self.caret)
if self.protected then
Expand Down
Loading
Loading