diff --git a/rlbot_gui/bot_management/downloader.py b/rlbot_gui/bot_management/downloader.py index 00a471c..ededbbd 100644 --- a/rlbot_gui/bot_management/downloader.py +++ b/rlbot_gui/bot_management/downloader.py @@ -3,7 +3,10 @@ import urllib import zipfile from pathlib import Path -from shutil import rmtree +import shutil + +from distutils.dir_util import copy_tree + def download_and_extract_zip(download_url: str, local_folder_path: Path, clobber: bool = False): @@ -17,7 +20,7 @@ def download_and_extract_zip(download_url: str, local_folder_path: Path, clobber urllib.request.urlretrieve(download_url, downloadedZip) if clobber and os.path.exists(local_folder_path): - rmtree(local_folder_path) + shutil.rmtree(local_folder_path) with zipfile.ZipFile(downloadedZip, 'r') as zip_ref: zip_ref.extractall(local_folder_path) @@ -43,3 +46,19 @@ def download_gitlfs(repo_url: str, checkout_folder: Path, branch_name: str): file.close() print('Done downloading git repo.') + +def download_botfs(repo_url: str, checkout_folder: Path, branch_name: str, bot_path: str, bot_dir_name: str): + print('Starting download of a git repo... this might take a while.') + + shutil.rmtree(checkout_folder, ignore_errors=True) + + # Download the most of the files eg. https://github.com/RLBot/RLBotPack/archive/master.zip + download_and_extract_zip( + download_url=repo_url + '/archive/' + branch_name + '.zip', + local_folder_path=checkout_folder, clobber=True) + + repo_extraction_name = '/' + repo_url.split('/')[-1] + '-' + branch_name + '/' + + shutil.copytree(checkout_folder + repo_extraction_name + '/' + bot_dir_name, bot_path + '/' + bot_dir_name) + + print('Done downloading git repo.') diff --git a/rlbot_gui/gui.py b/rlbot_gui/gui.py index 20bb5c2..56fcbf3 100644 --- a/rlbot_gui/gui.py +++ b/rlbot_gui/gui.py @@ -1,4 +1,5 @@ import os +import shutil import eel from PyQt5.QtCore import QSettings @@ -15,15 +16,17 @@ from rlbot_gui.bot_management.bot_creation import bootstrap_python_bot from rlbot_gui.bot_management.downloader import download_gitlfs +from rlbot_gui.bot_management.downloader import download_botfs from rlbot_gui.match_runner.match_runner import hot_reload_bots, shut_down, start_match_helper, do_infinite_loop_content +import getpass DEFAULT_BOT_FOLDER = 'default_bot_folder' -BOTPACK_FOLDER = 'RLBotPackDeletable' -OLD_BOTPACK_FOLDER = 'RLBotPack' CREATED_BOTS_FOLDER = 'MyBots' +HUB_FOLDER = 'repos' BOT_FOLDER_SETTINGS_KEY = 'bot_folder_settings' settings = QSettings('rlbotgui', 'preferences') + bot_folder_settings = settings.value(BOT_FOLDER_SETTINGS_KEY, type=dict) if not bot_folder_settings: @@ -147,6 +150,10 @@ def scan_for_bots(): for bot in bots: bot_hash[bot['path']] = bot + bots = get_bots_from_directory("C:/Users/"+getpass.getuser()+"/AppData/Local/RLBotGUI/repos") + for bot in bots: + bot_hash[bot['path']] = bot + return list(bot_hash.values()) @@ -154,10 +161,12 @@ def get_bots_from_directory(bot_directory): return [ { 'name': bundle.name, + 'safe': 1 if bundle.config_path.split('\\')[7] != 'unferifiedCommunity' and bundle.config_path.split('\\')[7] != 'localRepo' else 0, 'type': 'rlbot', 'skill': 1, 'image': 'imgs/rlbot.png', 'path': bundle.config_path, + 'display_path': bundle.config_path.split('\\')[7], 'info': read_info(bundle) } for bundle in scan_directory_for_bot_configs(bot_directory)] @@ -208,24 +217,66 @@ def install_package(package_string): @eel.expose -def download_bot_pack(): - # The bot pack in now hosted at https://github.com/RLBot/RLBotPack - download_gitlfs( - repo_url="https://github.com/RLBot/RLBotPack", - checkout_folder=BOTPACK_FOLDER, - branch_name='master') +def download_bot(repo_path, repo, bot_dir): + print(repo) + branch = "" + if "tree" in repo: + branch = repo.split('/')[-1] + repo = repo.split('/tree')[0] + else: + branch = "master" - # Configure the folder settings. - bot_folder_settings['folders'][os.path.abspath(BOTPACK_FOLDER)] = {'visible': True} + download_botfs( + repo_url=repo, + checkout_folder='download', + branch_name=branch, + bot_path=os.path.abspath(HUB_FOLDER) + '/' + repo_path, + bot_dir_name=bot_dir) + return 0; - if os.path.abspath(OLD_BOTPACK_FOLDER) in bot_folder_settings['folders']: - # Toggle off the old one since it's been replaced. - bot_folder_settings['folders'][os.path.abspath(OLD_BOTPACK_FOLDER)] = {'visible': False} - settings.setValue(BOT_FOLDER_SETTINGS_KEY, bot_folder_settings) - settings.sync() +@eel.expose +def delete_bot(repo_path, name): + shutil.rmtree(os.path.abspath(HUB_FOLDER + "/" + repo_path + '/' + name)) + return 0; + + +@eel.expose +def is_bot_installed(repo_path, bot_name): + if os.path.exists(HUB_FOLDER + "/" + repo_path + '/' + bot_name): + return True + else: + return False +@eel.expose +def get_bot_packaging(repo_path, bot_name): + bot_directory = HUB_FOLDER + "/" + repo_path + if os.path.exists(bot_directory+'/'+bot_name): + file = open(bot_directory+'/'+bot_name+'/botpackage.json',"r") + filestr = file.read() + file.close() + return filestr + else: + return False + + +@eel.expose +def read_local_repofile(): + if os.path.exists('localRepo.json'): + file = open('localRepo.json',"r") + filestr = file.read() + file.close() + return filestr + else: + return False + +@eel.expose +def write_local_repofile(jsonstr): + file = open('localRepo.json',"w") + file.write(jsonstr) + file.close() + @eel.expose def show_bot_in_explorer(bot_cfg_path): import subprocess @@ -261,7 +312,7 @@ def begin_python_bot(bot_name): def on_websocket_close(page, sockets): global should_quit - eel.sleep(3.0) # We might have just refreshed. Give the websocket a moment to reconnect. + eel.sleep(10.0) # We might have just refreshed. Give the websocket a moment to reconnect. if not len(eel._websockets): # At this point we think the browser window has been closed. should_quit = True @@ -296,7 +347,7 @@ def launch_eel(use_chrome): # installed to pip locally using this technique https://stackoverflow.com/a/49684835 # The suppress_error=True avoids the error "'options' argument deprecated in v1.0.0", we need to keep the # options argument since a lot of our user base has an older version of eel. - eel.start('main.html', size=(1000, 800), block=False, callback=on_websocket_close, options=options, + eel.start('main.html', size=(1000, 830), block=False, callback=on_websocket_close, options=options, disable_cache=True, mode=browser_mode, port=port, suppress_error=True) @@ -314,4 +365,4 @@ def start(): while not should_quit: do_infinite_loop_content() - eel.sleep(1.0) + eel.sleep(1.0) \ No newline at end of file diff --git a/rlbot_gui/gui/botmanager.html b/rlbot_gui/gui/botmanager.html new file mode 100644 index 0000000..4396f32 --- /dev/null +++ b/rlbot_gui/gui/botmanager.html @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + Hub + + + +
+ + + +
+ +
+
+
{{repo.name}} + Version {{repo.onlineVersion}} + Version {{repo.localVersion}}, an update to version Version {{repo.onlineVersion}} is available +
+
+
+
+
+
+

+ {{mode.name}} +

+
+
+

+ {{category.name}} +

+
+
+

+ {{repo.description}} +

+
+
+

+ {{repo.repoName}} +

+
+
+ + +
+
+
+
+ + Add bot to local repo + + + + + + + + + + + + + + Begin + Close + + +
+ + + + + + + + + diff --git a/rlbot_gui/gui/css/botmanager.css b/rlbot_gui/gui/css/botmanager.css new file mode 100644 index 0000000..1ecb8b5 --- /dev/null +++ b/rlbot_gui/gui/css/botmanager.css @@ -0,0 +1,50 @@ +@media screen and (orientation: landscape) { + html { + overflow-y: hidden; + } + aside { + width: 20vw; + float: left; + display: grid; + grid-template-rows: max-content auto auto; + } + aside > div { + display: initial; + } + aside, #main{ + height: calc(100vh - 64px); + } + #main { + float: right; + overflow-y: auto; + width: 80vw; + } + .botinfo-short{ + width: 10vw; + } + .botinfo-long{ + width: 25vw; + } +} + +.md-title > small { + font-size: small; +} + +.md-layout-item > p > .gamemode:not(:last-of-type)::after, .md-layout-item > p > .category:not(:last-of-type)::after{ + content: ', '; +} + +.md-layout{ + width: 100%; +} + +.md-card{ + display: grid; + overflow-y: hidden; +} + +.md-card-content{ + height: 100%; + overflow-y: auto; +} \ No newline at end of file diff --git a/rlbot_gui/gui/css/style.css b/rlbot_gui/gui/css/style.css index e872e72..c5d8b00 100644 --- a/rlbot_gui/gui/css/style.css +++ b/rlbot_gui/gui/css/style.css @@ -9,8 +9,9 @@ --blue-color: #0054a6; } -body { - height: 100%; +html, body { + height: 100vh; + overflow: hidden; } #app { @@ -201,4 +202,23 @@ body { color: #717171; font-size: 10pt; font-weight: normal; +} + +.menuActive:not(:hover) { + text-decoration: underline solid white !important; +} + +#hub{ + position: fixed; + top: 64px; + z-index: 999; + left: 100vw; + width: 100vw; + height: 100vh !important; + transition-property: left; + transition-duration: 1s; +} + +#menuRLBot{ + text-decoration: underline solid white; } \ No newline at end of file diff --git a/rlbot_gui/gui/js/botmanager.js b/rlbot_gui/gui/js/botmanager.js new file mode 100644 index 0000000..5cfd39b --- /dev/null +++ b/rlbot_gui/gui/js/botmanager.js @@ -0,0 +1,276 @@ +Vue.use(VueMaterial.default); + +const repolists = ['https://raw.githubusercontent.com/ard1998/RLBot-repos/master/trusted.json', + 'https://raw.githubusercontent.com/ard1998/RLBot-repos/master/unverifiedCommunity.json']; +const newsLink = 'https://raw.githubusercontent.com/ard1998/RLBot-repos/master/news.json'; +var init = true + +const app = new Vue({ + el: '#app', + data: { + repos: [], + newsItems: [], + showNewRepoDialog: false, + showProgressSpinner: false, + init: true, + filterChangeCount: 0, + highestCardID: -1 + }, + created: function () { + if (init) { + downloadRepos(); + getNews(); + init = false; + } + }, + methods: { + downloadBot: async function (repo, update=false) { + if (!repo.safe && !update) { + if (!confirm('This download is from an unferified source and may contain unsafe code. Do you really want to download this')) { + return false; + } + } + await eel.download_bot(repo.repoName, repo.url, repo.name)(async function() { + var botpackage = await eel.get_bot_packaging(repo.repoName, repo.name)() + + repo.is_installed = true; + repo.localVersion = JSON.parse(botpackage).version + + app.highestCardID += 1; + repo.ID = app.highestCardID; + + repo.push(); + }) + }, + deleteBot: function (repo, update=false) { + eel.delete_bot(repo.repoName, repo.name)(function() { + if (!update) { + repo.is_installed = false; + + app.highestCardID += 1; + repo.ID = app.highestCardID; + + if (repo.repoName === 'localRepo') { + repo.display = false; + repo.deleted = true; + app.removeBotFromLocalRepofile(repo.url); + } + + app.repo.push(); + } + }) + }, + updateBot: function(repo){ + delete_bot(repo, true) + download_bot(repo, true) + }, + readRepofile: async function(){ + var repostr = await eel.read_local_repofile()(); + if (repostr === false) { + var repostr = '{"note": "Note when enabling the repo", "repos":[]}' + } + var obj = JSON.parse(repostr); + return obj; + }, + addBotToLocalRepofile: async function(url, folder){ + //todo directly downloading the bot after adding + + var json = await app.readRepofile() + var len = json.repos.length + + json.repos[len] = {}; + json.repos[len].url = url; + json.repos[len].name = folder; + var jsonstr = JSON.stringify(json); + var repo = await downloadBotCard(json.repos[len], 'localRepo'); + + if(repo.error){ + alert('Error fetching ' + repo.url + '/' + repo.name + ': Branch or folder does not exist'); + } + else{ + await eel.write_local_repofile(jsonstr)(); + await app.downloadBot(repo, true) + app.showNewRepoDialog = false; + } + }, + removeBotFromLocalRepofile: async function(url){ + var json = await app.readRepofile() + var len = json.repos.length -1; + var found = false; + + do{ + if (json.repos[len].url === url) { + delete json.repos[len]; + found = true; + } + --len; + } + while (len >=0 || !found) + + var jsonstr = JSON.stringify(json); + jsonstr = jsonstr.replace("null", ""); + eel.write_local_repofile(jsonstr); + } + } +}); + +function getNews(){ + fetch(newsLink) + .then(function(response) { + return response.json(); + }) + .then(async function(myJson) { + console.log(myJson) + for (var i = 0; i < myJson.length; i++) { + app.newsItems.push(myJson[i]) + } + }); +} + +async function downloadRepos() { + for (var i = 0; i < repolists.length; i++) { + await fetch(repolists[i]) + .then(function(response) { + return response.json(); + }) + .then(async function(myJson) { + await addPackageData(i, myJson); + }); + } + json = await app.readRepofile(); + await addPackageData(repolists.length, json); +} + +async function addPackageData(repoListIndex, json) { + repoName = '' + + if (repoListIndex= 0; i--) { + var repo = app.repos[i]; + + //fill gamemode filters + var gamemodeFilters = []; + for (var j = document.getElementsByName('gamemode').length - 1; j >= 0; j--) { + if (document.getElementsByName('gamemode')[j].checked) { + arrLen = gamemodeFilters.length; + gamemodeFilters[arrLen] = document.getElementsByName('gamemode')[j].value.toLowerCase(); + } + } + + var gamemodeNotFoundCount = 0; + if (gamemodeFilters.length != 0) { + //if any active gamemode gamemodeFilters, apply them + var gamemodeNotFoundCount = gamemodeFilters.length; + for (var gamemodeFilterIndex = repo.gamemodes.length - 1; gamemodeFilterIndex >= 0; gamemodeFilterIndex--) { + if (gamemodeFilters.includes(repo.gamemodes[gamemodeFilterIndex].name.toLowerCase())) { + --gamemodeNotFoundCount; + } + } + } + + //fill repo filters + var repoFilterPass = false; + var repoFilter = ''; + for (var j = document.getElementsByName('source').length - 1; j >= 0; j--) { + if (document.getElementsByName('source')[j].checked) { + repoFilter = document.getElementsByName('source')[j].value.toLowerCase(); + } + } + if (repo.repoName.toLowerCase() === repoFilter || repoFilter === 'all') { + repoFilterPass = true; + } + + //display or hide cards + if (!repo.deleted && repoFilterPass && gamemodeNotFoundCount === 0 && repo.name.toLowerCase().indexOf(document.getElementById('searchBotName').value.toLowerCase())!==-1) { + app.repos[i].display=true; + } + else{ + app.repos[i].display=false; + } + + app.repos.push() + } +} \ No newline at end of file diff --git a/rlbot_gui/gui/js/main.js b/rlbot_gui/gui/js/main.js index 5b3f03e..8818523 100644 --- a/rlbot_gui/gui/js/main.js +++ b/rlbot_gui/gui/js/main.js @@ -6,10 +6,10 @@ function PythonPrint(message) { Vue.use(VueMaterial.default); const STARTING_BOT_POOL = [ - {'name': 'Human', 'type': 'human', 'image': 'imgs/human.png'}, - {'name': 'Psyonix Allstar', 'type': 'psyonix', 'skill': 1, 'image': 'imgs/psyonix.png'}, - {'name': 'Psyonix Pro', 'type': 'psyonix', 'skill': 0.5, 'image': 'imgs/psyonix.png'}, - {'name': 'Psyonix Rookie', 'type': 'psyonix', 'skill': 0, 'image': 'imgs/psyonix.png'} + {'name': 'Human', 'type': 'human', 'image': 'imgs/human.png', 'safe': true}, + {'name': 'Psyonix Allstar', 'type': 'psyonix', 'skill': 1, 'image': 'imgs/psyonix.png', 'safe': true}, + {'name': 'Psyonix Pro', 'type': 'psyonix', 'skill': 0.5, 'image': 'imgs/psyonix.png', 'safe': true}, + {'name': 'Psyonix Rookie', 'type': 'psyonix', 'skill': 0, 'image': 'imgs/psyonix.png', 'safe': true} ]; const app = new Vue({ @@ -104,9 +104,14 @@ const app = new Vue({ this.showProgressSpinner = true; eel.install_package(this.packageString)(onInstallationComplete); }, - downloadBotPack: function() { - this.showProgressSpinner = true; - eel.download_bot_pack()(botPackDownloaded); + downloadBot: function(repo, branch) { + eel.download_bot(repo, branch)(downloadBotComplete); + }, + deleteBot: function(name) { + eel.delete_bot(name)(deleteBotComplete); + }, + isBotInstalled: function (name) { + return eel.isBotInstalled(name) }, showBotInExplorer: function (botPath) { eel.show_bot_in_explorer(botPath); @@ -212,6 +217,14 @@ function onInstallationComplete(result) { app.showProgressSpinner = false; } +function downloadBotComplete(){ + console.log('test') +} + +function deleteBotComplete(){ + console.log('test') +} + Vue.component('mutator-field', { props: ['label', 'options', 'value'], data: function() { @@ -234,4 +247,18 @@ Vue.component('mutator-field', { ` } -); \ No newline at end of file +); + +function showHub(){ + document.getElementById('menuHub').style.textDecoration = "underline solid white"; + document.getElementById('menuRLBot').style.textDecoration = "none"; + document.getElementById('hub').style.left = '0vw'; +} + +function hideHub(){ + eel.get_folder_settings()(folderSettingsReceived); + eel.scan_for_bots()(botsReceived); + document.getElementById('menuHub').style.textDecoration = "none"; + document.getElementById('menuRLBot').style.textDecoration = "underline solid white"; + document.getElementById('hub').style.left = '100vw'; +} \ No newline at end of file diff --git a/rlbot_gui/gui/main.html b/rlbot_gui/gui/main.html index b29f89a..a87ea8a 100644 --- a/rlbot_gui/gui/main.html +++ b/rlbot_gui/gui/main.html @@ -22,7 +22,8 @@
-

RLBot

+ +
@@ -69,10 +70,6 @@

RLBot

- - Download Bot Pack - cloud_download - Start Your Own Bot! create @@ -97,7 +94,8 @@

RLBot

+ +