mirror of
https://github.com/IgorTimofeev/MineOS.git
synced 2025-12-20 11:09:21 +01:00
441 lines
13 KiB
Lua
441 lines
13 KiB
Lua
|
|
local computer = require("computer")
|
|
local component = require("component")
|
|
local GUI = require("GUI")
|
|
local buffer = require("doubleBuffering")
|
|
local fs = require("filesystem")
|
|
local web = require("web")
|
|
local json = require("json")
|
|
local color = require("color")
|
|
local image = require("image")
|
|
local base64 = require("base64")
|
|
local unicode = require("unicode")
|
|
local MineOSInterface = require("MineOSInterface")
|
|
local MineOSPaths = require("MineOSPaths")
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local user
|
|
local configPath = MineOSPaths.applicationData .. "/GitHub/Config.cfg"
|
|
local config = {
|
|
avatarColors = {
|
|
[11760002] = 0x3C3C3C,
|
|
}
|
|
}
|
|
|
|
local function saveConfig()
|
|
table.toFile(configPath, config)
|
|
end
|
|
|
|
if fs.exists(configPath) then
|
|
config = table.fromFile(configPath)
|
|
end
|
|
|
|
local addUserShit
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
local mainContainer, window = MineOSInterface.addWindow(GUI.tabbedWindow(1, 1, 108, 33))
|
|
|
|
local titlePanel = window:addChild(GUI.panel(1, 1, window.width, 3, 0x2D2D2D))
|
|
window.tabBar:moveToFront()
|
|
window.actionButtons:moveToFront()
|
|
|
|
window.backgroundPanel.colors.background = 0xF0F0F0
|
|
|
|
window.tabBar:addItem("Repositories")
|
|
window.tabBar:addItem("Gists")
|
|
window.tabBar:addItem("Followers")
|
|
window.tabBar:addItem("Following")
|
|
|
|
local searchInput = window:addChild(GUI.input(9, 2, 14, 1, 0x3C3C3C, 0xB4B4B4, 0x696969, 0x3C3C3C, 0xE1E1E1, "", "Search…"))
|
|
|
|
local progressIndicator = window:addChild(GUI.progressIndicator(1, 1, 0x1E1E1E, 0x99FF80, 0x00B640))
|
|
|
|
local userContainer = window:addChild(GUI.container(3, 5, 20, 1))
|
|
local contentContainer = window:addChild(GUI.container(userContainer.localX + userContainer.width + 2, 4, 1, 1))
|
|
|
|
local function request(api)
|
|
local url = "https://api.github.com/" .. api
|
|
-- GUI.alert(url)
|
|
|
|
local data = ""
|
|
local success, reason = web.rawRequest(
|
|
url,
|
|
nil,
|
|
{
|
|
["User-Agent"]="Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0",
|
|
["Authorization"]="Basic " .. config.authorization
|
|
},
|
|
function(chunk)
|
|
data = data .. chunk
|
|
|
|
mainContainer:drawOnScreen()
|
|
progressIndicator:roll()
|
|
end,
|
|
math.huge
|
|
)
|
|
|
|
if success then
|
|
return json:decode(data)
|
|
else
|
|
return false, "API request failed: " .. tostring(reason)
|
|
end
|
|
end
|
|
|
|
local function avatarDraw(self)
|
|
local textColor = 0xFFFFFF - self.color
|
|
buffer.drawRectangle(self.x, self.y, self.width, self.height, self.color, textColor, " ")
|
|
buffer.drawText(math.floor(self.x + self.width / 2 - unicode.len(self.text) / 2), math.floor(self.y + self.height / 2), textColor, self.text)
|
|
end
|
|
|
|
local function newAvatar(x, y, width, height, id, name)
|
|
local self = GUI.object(x, y, width, height)
|
|
|
|
local shortcut = ""
|
|
for part in name:gmatch("[^%s]+") do
|
|
shortcut = shortcut .. unicode.upper(unicode.sub(part, 1, 1))
|
|
end
|
|
|
|
self.text = #shortcut > 0 and shortcut or unicode.upper(unicode.sub(name, 1, 1))
|
|
self.draw = avatarDraw
|
|
self.color = config.avatarColors[id]
|
|
if not self.color then
|
|
config.avatarColors[id] = color.HSBToInteger(math.random(0, 360), math.random(100) / 100, 1)
|
|
saveConfig()
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
local function repositoryGUI(repositoryName, branches)
|
|
progressIndicator.active = true
|
|
|
|
local reason
|
|
if not branches then
|
|
branches, reason = request("repos/" .. user.login .. "/" .. repositoryName .. "/branches")
|
|
if not branches then
|
|
GUI.alert(reason)
|
|
|
|
progressIndicator.active = false
|
|
mainContainer:drawOnScreen()
|
|
return
|
|
end
|
|
end
|
|
|
|
contentContainer:removeChildren()
|
|
|
|
local path = ""
|
|
|
|
local branchesComboBox = contentContainer:addChild(GUI.comboBox(1, 2, 14, 1, 0xE1E1E1, 0x2D2D2D, 0xC3C3C3, 0x787878))
|
|
|
|
local pathLayout = contentContainer:addChild(GUI.layout(branchesComboBox.localX + branchesComboBox.width + 2, 2, contentContainer.width, 10, 1, 1))
|
|
pathLayout:setAlignment(1, 1, GUI.ALIGNMENT_HORIZONTAL_LEFT, GUI.ALIGNMENT_VERTICAL_TOP)
|
|
pathLayout:setDirection(1, 1, GUI.DIRECTION_HORIZONTAL)
|
|
pathLayout:setSpacing(1, 1, 0)
|
|
|
|
for i = 1, #branches do
|
|
branchesComboBox:addItem(branches[i].name)
|
|
end
|
|
|
|
local previewContainer = contentContainer:addChild(GUI.container(1, 4, contentContainer.width - 2, contentContainer.height - 3))
|
|
|
|
local fillList, fillPath, fillCode
|
|
|
|
fillList = function()
|
|
progressIndicator.active = true
|
|
|
|
local result, reason = request("repos/" .. user.login .. "/" .. repositoryName .. "/contents/" .. path .. "?ref=" .. branchesComboBox:getItem(branchesComboBox.selectedItem).text)
|
|
if result then
|
|
if result.type == "file" then
|
|
local lines = {}
|
|
for line in base64.decode(result.content:gsub("\r\n", "\n")):gmatch("[^\n]+") do
|
|
line = line:gsub("\t", " ")
|
|
table.insert(lines, line)
|
|
end
|
|
|
|
local codeView = previewContainer:addChild(GUI.codeView(1, 1, previewContainer.width, previewContainer.height, 1, 1, 1, {}, {}, GUI.LUA_SYNTAX_PATTERNS, GUI.LUA_SYNTAX_COLOR_SCHEME, fs.extension(result.name) == ".lua", lines))
|
|
|
|
else
|
|
-- Sort files alphabetically
|
|
local files = {}
|
|
local i = 1
|
|
while i <= #result do
|
|
if result[i].type ~= "dir" then
|
|
table.insert(files, result[i])
|
|
table.remove(result, i)
|
|
else
|
|
i = i + 1
|
|
end
|
|
end
|
|
|
|
table.sort(files, function(a, b) return unicode.lower(a.name) < unicode.lower(b.name) end)
|
|
|
|
i = 1
|
|
while i <= #files do
|
|
table.insert(result, files[i])
|
|
table.remove(files, i)
|
|
end
|
|
|
|
-- Fill files container
|
|
local function hyperlinkDraw(self)
|
|
buffer.drawRectangle(self.x, self.y, self.width, self.height, self.pressed and self.pressedColor or self.backgroundColor, self.textColor, " ")
|
|
buffer.drawText(self.x + 2, math.floor(self.y + self.height / 2), self.textColor, (self.node.type == "dir" and "■ " or "□ ") .. self.node.name)
|
|
end
|
|
|
|
local function hyperlinkEventHandler(mainContainer, self, e1)
|
|
if e1 == "touch" then
|
|
self.pressed = true
|
|
mainContainer:drawOnScreen()
|
|
|
|
-- if self.node.type == "dir" then
|
|
path = path .. web.encode(self.node.name) .. "/"
|
|
fillPath()
|
|
fillList()
|
|
-- else
|
|
|
|
-- end
|
|
end
|
|
end
|
|
|
|
local function newHyperlink(x, y, width, height, backgroundColor, pressedColor, textColor, node)
|
|
local object = GUI.object(x, y, width, height)
|
|
|
|
object.pressed = false
|
|
object.backgroundColor = backgroundColor
|
|
object.pressedColor = pressedColor
|
|
object.textColor = textColor
|
|
object.draw = hyperlinkDraw
|
|
object.eventHandler = hyperlinkEventHandler
|
|
object.node = node
|
|
|
|
return object
|
|
end
|
|
|
|
previewContainer:removeChildren()
|
|
|
|
local y, step = 1, false
|
|
for i = 1, #result do
|
|
local hyperlink = previewContainer:addChild(newHyperlink(1, y, previewContainer.width, 3, step and 0xD2D2D2 or 0xE1E1E1, 0xC3C3C3, result[i].type == "dir" and 0x3C3C3C or 0x787878, result[i]))
|
|
|
|
y, step = y + hyperlink.height, not step
|
|
end
|
|
end
|
|
else
|
|
GUI.alert(reason)
|
|
end
|
|
|
|
progressIndicator.active = false
|
|
mainContainer:drawOnScreen()
|
|
end
|
|
|
|
fillPath = function()
|
|
pathLayout:removeChildren()
|
|
|
|
pathLayout:addChild(GUI.button(1, 1, unicode.len(repositoryName), 1, nil, 0x9949BF, nil, 0x332480, repositoryName)).onTouch = function()
|
|
path = ""
|
|
fillPath()
|
|
fillList()
|
|
end
|
|
|
|
pathLayout:addChild(GUI.text(1, 1, 0xC3C3C3, "/"))
|
|
|
|
local segments = fs.segments(path)
|
|
for i = 1, #segments - 1 do
|
|
pathLayout:addChild(GUI.button(1, 1, unicode.len(segments[i]), 1, nil, 0x9949BF, nil, 0x332480, segments[i])).onTouch = function()
|
|
path = table.concat(segments, "/", 1, i) .. "/"
|
|
fillPath()
|
|
fillList()
|
|
end
|
|
pathLayout:addChild(GUI.text(1, 1, 0xC3C3C3, "/"))
|
|
end
|
|
|
|
if #segments > 0 then
|
|
pathLayout:addChild(GUI.text(1, 1, 0x787878, segments[#segments]))
|
|
end
|
|
end
|
|
|
|
branchesComboBox.onItemSelected = function()
|
|
|
|
end
|
|
|
|
fillPath()
|
|
fillList()
|
|
end
|
|
|
|
local function repositoryDraw(self)
|
|
buffer.drawRectangle(self.x, self.y, self.width, self.height, self.pressed and 0xE1E1E1 or 0xFFFFFF, 0x969696, " ")
|
|
buffer.drawText(self.x + 2, self.y + 1, 0x004980, self.name)
|
|
buffer.drawText(self.x + 2, self.y + 3, 0x969696, self.lines[1])
|
|
buffer.drawText(self.x + 2, self.y + 4, 0x969696, self.lines[2])
|
|
buffer.drawText(self.x + 2, self.y + 6, 0x3C3C3C, self.stats)
|
|
end
|
|
|
|
local function repositoryEventHandler(mainContainer, self, e1)
|
|
if e1 == "touch" then
|
|
self.pressed = true
|
|
mainContainer:drawOnScreen()
|
|
|
|
repositoryGUI(self.name)
|
|
end
|
|
end
|
|
|
|
local function newRepository(x, y, width, repository)
|
|
local self = GUI.object(x, y, width, 8)
|
|
|
|
self.pressed = false
|
|
self.name = repository.name
|
|
|
|
self.lines = string.wrap(repository.description or "No description provided", width - 4)
|
|
if self.lines[2] then
|
|
self.lines[2] = string.limit(table.concat(self.lines, " ", 2), width - 4)
|
|
end
|
|
|
|
self.stats = (repository.language and "• " .. repository.language .. " " or "") .. "* " .. repository.stargazers_count .. " # " .. repository.forks_count
|
|
|
|
self.draw = repositoryDraw
|
|
self.eventHandler = repositoryEventHandler
|
|
|
|
return self
|
|
end
|
|
|
|
local function repositoriesGUI(forks, page)
|
|
local startX, startY = 1, 2
|
|
local width = 40
|
|
local perHeight = math.floor((contentContainer.height - 3) / 9)
|
|
local perWidth = math.floor((contentContainer.width - startX) / width)
|
|
local perPage = perWidth * perHeight
|
|
|
|
progressIndicator.active = true
|
|
|
|
local result, reason = request("users/" .. user.login .. "/repos?page=" .. page .. "&per_page=" .. perPage .. "&sort=updated")
|
|
if result then
|
|
contentContainer:removeChildren()
|
|
|
|
local x, y, maxHeightPerLine = startX, startY, 0
|
|
for i = 1, math.min(#result, perPage) do
|
|
-- if forks and result[i].fork or not forks and not result[i].fork then
|
|
local repository = contentContainer:addChild(newRepository(x, y, width, result[i]))
|
|
maxHeightPerLine = math.max(maxHeightPerLine, repository.height)
|
|
|
|
x = x + repository.width + 2
|
|
if x > contentContainer.width - 2 then
|
|
x, y = startX, y + maxHeightPerLine + 1
|
|
maxHeightPerLine = 0
|
|
end
|
|
-- end
|
|
end
|
|
else
|
|
GUI.alert(reason)
|
|
end
|
|
|
|
progressIndicator.active = false
|
|
mainContainer:drawOnScreen()
|
|
end
|
|
|
|
local function loginGUI()
|
|
local loginContainer = window:addChild(GUI.container(1, 1, window.width, window.height))
|
|
window.actionButtons:moveToFront()
|
|
|
|
loginContainer:addChild(GUI.panel(1, 1, loginContainer.width, loginContainer.height, 0xF0F0F0))
|
|
|
|
local layout = loginContainer:addChild(GUI.layout(1, 1, loginContainer.width, loginContainer.height, 1, 1))
|
|
|
|
local try, again
|
|
|
|
try = function()
|
|
progressIndicator.active = true
|
|
layout:removeChildren()
|
|
|
|
layout:addChild(GUI.text(1, 1, 0x969696, "Logging in..."))
|
|
mainContainer:drawOnScreen()
|
|
|
|
local result, reason = request("users/" .. config.user)
|
|
if result and result.id then
|
|
loginContainer:remove()
|
|
|
|
user = result
|
|
addUserShit()
|
|
repositoriesGUI(false, 1)
|
|
else
|
|
GUI.alert("Incorrect login or password")
|
|
user = nil
|
|
config.authorization = nil
|
|
saveConfig()
|
|
|
|
again()
|
|
end
|
|
|
|
progressIndicator.active = false
|
|
mainContainer:drawOnScreen()
|
|
end
|
|
|
|
again = function()
|
|
layout:removeChildren()
|
|
|
|
local userInput = layout:addChild(GUI.input(1, 1, 26, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, config.user or "", "Username"))
|
|
local passwordInput = layout:addChild(GUI.input(1, 1, 26, 3, 0xE1E1E1, 0x787878, 0xB4B4B4, 0xE1E1E1, 0x2D2D2D, "", "Password", false, "*"))
|
|
local submitButton = layout:addChild(GUI.button(1, 1, 26, 3, 0xC3C3C3, 0xFFFFFF, 0x969696, 0xFFFFFF, "Login"))
|
|
|
|
submitButton.onTouch = function()
|
|
config.user = userInput.text
|
|
config.authorization = base64.encode(userInput.text .. ":" .. passwordInput.text)
|
|
saveConfig()
|
|
|
|
try()
|
|
end
|
|
|
|
mainContainer:drawOnScreen()
|
|
end
|
|
|
|
if config.authorization then
|
|
try()
|
|
else
|
|
again()
|
|
end
|
|
end
|
|
|
|
addUserShit = function()
|
|
userContainer:removeChildren()
|
|
|
|
local y = 1
|
|
|
|
local avatar = userContainer:addChild(newAvatar(1, y, userContainer.width, userContainer.width / 2 - 1, user.id, user.name or user.login))
|
|
y = y + avatar.height + 1
|
|
|
|
userContainer:addChild(GUI.text(1, y, 0x2D2D2D, user.login))
|
|
y = y + 2
|
|
|
|
if user.name then
|
|
userContainer:addChild(GUI.text(1, y - 1, 0x969696, user.name))
|
|
y = y + 1
|
|
end
|
|
|
|
if user.bio then
|
|
local lines = string.wrap(user.bio, userContainer.width)
|
|
local textBox = userContainer:addChild(GUI.textBox(1, y, userContainer.width, #lines, nil, 0x969696, lines, 1))
|
|
textBox.eventHandler = nil
|
|
y = y + #lines + 1
|
|
end
|
|
|
|
userContainer:addChild(GUI.roundedButton(2, y, userContainer.width - 2, 1, 0xB4B4B4, 0xFFFFFF, 0x969696, 0xFFFFFF, "Logout")).onTouch = function()
|
|
config.authorization = nil
|
|
saveConfig()
|
|
|
|
loginGUI()
|
|
end
|
|
end
|
|
|
|
local function calculateSizes()
|
|
userContainer.height = window.height - 4
|
|
contentContainer.width, contentContainer.height = window.width - userContainer.width - 4, window.height - 3
|
|
progressIndicator.localX = window.width - progressIndicator.width
|
|
|
|
titlePanel.width = window.width
|
|
window.tabBar.width = window.width - progressIndicator.width - window.actionButtons.width - searchInput.width - 8
|
|
window.tabBar.localX = searchInput.localX + searchInput.width + 2
|
|
end
|
|
|
|
calculateSizes()
|
|
loginGUI()
|