336 lines
7.1 KiB
Lua
336 lines
7.1 KiB
Lua
local plugin = PluginManager():CreatePlugin()
|
|
local initiated = false
|
|
|
|
local HttpService = game:GetService "HttpService"
|
|
HttpService.HttpEnabled = true
|
|
|
|
local function initiate()
|
|
if initiated then
|
|
return
|
|
end
|
|
initiated = true
|
|
|
|
print "Initiating network server for Mercury Sync."
|
|
print "Hosting servers is not possible after opening Mercury Sync! Please restart Studio to host servers again."
|
|
|
|
game:GetService("NetworkServer"):Start()
|
|
end
|
|
|
|
local toolbar = plugin:CreateToolbar "Mercury Sync"
|
|
|
|
local buttons = {
|
|
toolbar:CreateButton(
|
|
"", -- The text next to the icon. Leave this blank if the icon is sufficient.
|
|
"Sync!", -- hover text
|
|
"icon.png" -- The icon file's name. Make sure you change it to your own icon file's name!
|
|
),
|
|
}
|
|
|
|
local Fusion = LoadLibrary "RbxFusion"
|
|
|
|
local New = Fusion.New
|
|
local Children = Fusion.Children
|
|
local Value = Fusion.Value
|
|
local Spring = Fusion.Spring
|
|
-- local Tween = Fusion.Tween
|
|
-- local TweenInfo = Fusion.TweenInfo
|
|
local Observer = Fusion.Observer
|
|
local peek = Fusion.peek
|
|
|
|
local g
|
|
local notifications = {}
|
|
|
|
local WIDTH = 250
|
|
|
|
-- the gui is removed when there are no notifications,
|
|
-- to prevent remaining in StarterGui
|
|
local function gui()
|
|
if not g then
|
|
g = New "ScreenGui" {
|
|
Name = "Mercury Sync",
|
|
Parent = game.StarterGui,
|
|
|
|
[Children] = New "Frame" {
|
|
Name = "Notifications",
|
|
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
BackgroundTransparency = 1,
|
|
Position = UDim2.new(0, 0, 0, 0),
|
|
Size = UDim2.new(0, WIDTH, 1, 0),
|
|
},
|
|
}
|
|
|
|
local conn, destroyed
|
|
conn = g.Notifications.DescendantRemoving:connect(function()
|
|
wait(1)
|
|
if not destroyed and #g.Notifications:GetChildren() == 0 then
|
|
g:Destroy()
|
|
g = nil
|
|
notifications = {}
|
|
destroyed = true
|
|
conn:disconnect()
|
|
end
|
|
end)
|
|
end
|
|
|
|
return g
|
|
end
|
|
|
|
local idCount = 0
|
|
local nCount = 0
|
|
|
|
local function notify(text, willUpdate)
|
|
nCount = nCount + 1
|
|
idCount = idCount + 1
|
|
local id = idCount
|
|
|
|
local position = Value(UDim2.new(0, -WIDTH, 0, 60 * nCount - 50))
|
|
local transparency = Value(0)
|
|
local textValue = Value(text)
|
|
local textChanged
|
|
local arrowRotation = Value(0)
|
|
local done = Value(not willUpdate)
|
|
local background = Value(Color3.new())
|
|
local backgroundSpring = Spring(background, 4)
|
|
local start = tick()
|
|
|
|
local disconn = function() end
|
|
|
|
if willUpdate then
|
|
textChanged = Observer(textValue)
|
|
disconn = textChanged:onChange(function()
|
|
if tick() - start > 0.5 then -- don't change color if it's just appearing
|
|
backgroundSpring:setPosition(Color3.new(0.4, 0.4, 0.4))
|
|
end
|
|
end)
|
|
end
|
|
|
|
local t = New "Frame" {
|
|
Name = "Notification",
|
|
Parent = gui().Notifications,
|
|
BackgroundColor3 = backgroundSpring,
|
|
BackgroundTransparency = Spring(transparency, 15),
|
|
BorderSizePixel = 0,
|
|
Position = Spring(position, 15),
|
|
Size = UDim2.new(1, 0, 0, 50),
|
|
|
|
[Children] = {
|
|
New "ImageLabel" {
|
|
Name = "InnerIcon",
|
|
Image = "rbxasset://../../../Plugins/TestPlugin/innerIcon.png",
|
|
BackgroundTransparency = 1,
|
|
Position = UDim2.new(0, 5, 0, 5),
|
|
Size = UDim2.new(0, 40, 0, 40),
|
|
},
|
|
New "ImageLabel" {
|
|
Name = "OuterIcon",
|
|
Image = "rbxasset://../../../Plugins/TestPlugin/outerIcon.png",
|
|
BackgroundTransparency = 1,
|
|
Position = UDim2.new(0, 5, 0, 5),
|
|
Rotation = Spring(arrowRotation),
|
|
Size = UDim2.new(0, 40, 0, 40),
|
|
},
|
|
New "TextLabel" {
|
|
Position = UDim2.new(0, 50, 0, 0),
|
|
Size = UDim2.new(1, -60, 1, 0),
|
|
BackgroundTransparency = 1,
|
|
Text = textValue,
|
|
TextWrapped = true,
|
|
TextColor3 = Color3.new(1, 1, 1),
|
|
Font = Enum.Font.SourceSans,
|
|
FontSize = Enum.FontSize.Size18,
|
|
TextXAlignment = Enum.TextXAlignment.Center,
|
|
TextYAlignment = Enum.TextYAlignment.Center,
|
|
},
|
|
},
|
|
}
|
|
|
|
Spawn(function()
|
|
local tbl = {
|
|
obj = t,
|
|
pos = position,
|
|
}
|
|
|
|
notifications[id] = tbl
|
|
|
|
position:set(peek(position) + UDim2.new(0, WIDTH, 0, 0))
|
|
transparency:set(0.5)
|
|
|
|
repeat
|
|
wait(1)
|
|
until peek(done)
|
|
|
|
wait(3)
|
|
|
|
position:set(UDim2.new(0, 0, 0, -60))
|
|
transparency:set(1)
|
|
|
|
notifications[id] = nil
|
|
nCount = nCount - 1
|
|
|
|
for _, v in pairs(notifications) do
|
|
if peek(v.pos).Y.Offset > peek(position).Y.Offset then
|
|
v.pos:set(peek(v.pos) - UDim2.new(0, 0, 0, 60))
|
|
end
|
|
end
|
|
|
|
wait(1)
|
|
disconn()
|
|
t:Destroy()
|
|
end)
|
|
|
|
if willUpdate then
|
|
return {
|
|
text = textValue,
|
|
arrowRotation = arrowRotation,
|
|
done = done,
|
|
}
|
|
end
|
|
return
|
|
end
|
|
|
|
local function makeScript(s) -- because no continue
|
|
local path = s.path -- { "ServerScriptService", "script" }
|
|
local content = s.content
|
|
local filetype = s.type
|
|
|
|
local obj = game
|
|
local ok, err = pcall(function()
|
|
for i = 1, #path - 1 do
|
|
obj = obj:FindFirstChild(path[i])
|
|
if not obj then
|
|
local folder = Instance.new "Model"
|
|
folder.Name = path[i]
|
|
folder.Parent = obj
|
|
obj = folder
|
|
end
|
|
end
|
|
end)
|
|
if not ok then
|
|
notify(
|
|
"Failed to sync "
|
|
.. table.concat(path, ".")
|
|
.. "! Is the path correct?"
|
|
)
|
|
print("Failed to sync:", err)
|
|
return
|
|
end
|
|
|
|
local name = path[#path]
|
|
local existingObj = obj:FindFirstChild(name)
|
|
if existingObj then
|
|
if existingObj:IsA "Script" then
|
|
local ok2, err2 = pcall(function()
|
|
existingObj.Source = content
|
|
end)
|
|
if not ok2 then
|
|
notify(
|
|
"Failed to sync "
|
|
.. table.concat(path, ".")
|
|
.. " to an existing file!"
|
|
)
|
|
print("Failed to sync to existing:", err2)
|
|
return
|
|
end
|
|
else
|
|
notify(
|
|
"Object already exists at path "
|
|
.. table.concat(path, ".")
|
|
.. "! Please remove it and try again."
|
|
)
|
|
return
|
|
end
|
|
else
|
|
local createScript
|
|
|
|
if filetype == "server" then
|
|
createScript = Instance.new "Script"
|
|
elseif filetype == "client" then
|
|
createScript = Instance.new "LocalScript"
|
|
else
|
|
return
|
|
end
|
|
|
|
print("Name", name)
|
|
print("Content", content, type(content))
|
|
|
|
if type(content) == "table" then
|
|
for i, v in pairs(content) do
|
|
print(i, v)
|
|
end
|
|
end
|
|
|
|
local ok2, err2 = pcall(function()
|
|
createScript.Name = name
|
|
createScript.Source = content
|
|
createScript.Parent = obj
|
|
end)
|
|
if not ok2 then
|
|
notify(
|
|
"Failed to write "
|
|
.. table.concat(path, ".")
|
|
.. "! Is the content valid?"
|
|
)
|
|
print("Failed to write:", err2)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local debounce
|
|
|
|
buttons[1].Click:connect(function()
|
|
if debounce then
|
|
return
|
|
end
|
|
debounce = true
|
|
initiate()
|
|
|
|
local n = notify("Syncing...", true)
|
|
|
|
Spawn(function()
|
|
while not peek(n.done) do
|
|
n.arrowRotation:set(peek(n.arrowRotation) + 180)
|
|
wait(0.9)
|
|
end
|
|
end)
|
|
|
|
Spawn(function()
|
|
local ok, res = ypcall(function()
|
|
return HttpService:GetAsync(
|
|
"http://localhost:2013/sync?" .. tick() * 10000
|
|
-- nocache parameter doesn't work
|
|
)
|
|
end)
|
|
|
|
local function finish()
|
|
n.done:set(true)
|
|
wait(0.05)
|
|
debounce = false
|
|
end
|
|
|
|
if not ok then
|
|
n.text:set "Failed to sync! Is Mercury Sync Server running?"
|
|
finish()
|
|
return
|
|
end
|
|
|
|
n.text:set "Decoding..."
|
|
local json = HttpService:JSONDecode(res) -- { files }
|
|
|
|
if not json.files or json.files == "null" then
|
|
n.text:set "No files to sync!"
|
|
finish()
|
|
return
|
|
end
|
|
n.text:set "Applying..."
|
|
|
|
for _, v in pairs(json.files) do -- { path, content, type }
|
|
makeScript(v)
|
|
end
|
|
|
|
n.text:set "Successfully synchronised!"
|
|
|
|
finish()
|
|
end)
|
|
end)
|