Started work on administrative functions
This commit is contained in:
parent
a13366c413
commit
332acf65c8
@ -24,6 +24,7 @@ self_update = "0.40.0"
|
|||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
reqwest = { version = "0.12.5", features = ["stream"] }
|
reqwest = { version = "0.12.5", features = ["stream"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
|
ssh2 = "0.9.4"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
# This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!!
|
||||||
|
@ -1,13 +1,75 @@
|
|||||||
static USERNAME: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
|
use std::io::Cursor;
|
||||||
static PASSWORD: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::sftp;
|
||||||
|
use crate::modpack;
|
||||||
|
use ssh2::Sftp;
|
||||||
|
use ssh2::Session;
|
||||||
|
|
||||||
|
//static USERNAME: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
|
||||||
|
//static PASSWORD: parking_lot::Mutex<String> = parking_lot::const_mutex(String::new());
|
||||||
|
static SESSION: tauri::async_runtime::Mutex<Option<Session>> = tauri::async_runtime::Mutex::const_new(None);
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn login(username: String, password: String, window: tauri::Window) {
|
pub async fn login(username: String, password: String, window: tauri::Window) {
|
||||||
//if(test_cred(username.as_str(), password.as_str())){
|
let res = sftp::connect(username, password);
|
||||||
|
if(res.is_ok()){
|
||||||
//*USERNAME.lock() = username;
|
//*USERNAME.lock() = username;
|
||||||
//*PASSWORD.lock() = password;
|
//*PASSWORD.lock() = password;
|
||||||
//window.emit("Login_Success", {});
|
*SESSION.lock().await = Some(res.unwrap());
|
||||||
//}else{
|
window.emit("Login_Success", {});
|
||||||
|
}else{
|
||||||
window.emit("Login_Failed", {});
|
window.emit("Login_Failed", {});
|
||||||
//}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn drop_session(){
|
||||||
|
let ref mut session = *SESSION.lock().await;
|
||||||
|
if let Some(session) = session {
|
||||||
|
session.disconnect(None, "disconnecting", None);
|
||||||
|
}
|
||||||
|
*session = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_modpacks(modpacks: Vec<modpack::ModpackEntry>) -> Result<(), String>{
|
||||||
|
let data = serde_json::to_string(&modpacks).or(Err("Unable to serialize json"))?;
|
||||||
|
let reader = Cursor::new(data.as_bytes());
|
||||||
|
let ref mut session = *SESSION.lock().await;
|
||||||
|
if let Some(session) = session {
|
||||||
|
sftp::uplaod(None, session.clone(), PathBuf::from("/ftp/modpacks.json"), reader, format!("modpacks.json"), data.as_bytes().len()).await;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn shift_up(id: String){
|
||||||
|
let mut modpacks = modpack::get_modpacks().await;
|
||||||
|
let mut index = 0;
|
||||||
|
for pack in modpacks.as_slice() {
|
||||||
|
if(pack.id == id){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
if index != 0 {
|
||||||
|
modpacks.swap(index, index-1);
|
||||||
|
}
|
||||||
|
update_modpacks(modpacks).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn shift_down(id: String){
|
||||||
|
let mut modpacks = modpack::get_modpacks().await;
|
||||||
|
let mut index = 0;
|
||||||
|
for pack in modpacks.as_slice() {
|
||||||
|
if(pack.id == id){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
if index != modpacks.len()-1 {
|
||||||
|
modpacks.swap(index, index+1);
|
||||||
|
}
|
||||||
|
update_modpacks(modpacks).await;
|
||||||
}
|
}
|
@ -7,10 +7,10 @@ use serde::Serialize;
|
|||||||
|
|
||||||
#[derive(Clone, Serialize)]
|
#[derive(Clone, Serialize)]
|
||||||
pub struct DownloadStatus {
|
pub struct DownloadStatus {
|
||||||
downloaded: usize,
|
pub downloaded: usize,
|
||||||
total: usize,
|
pub total: usize,
|
||||||
time_elapsed: usize,
|
pub time_elapsed: usize,
|
||||||
download_name: String
|
pub download_name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ mod util;
|
|||||||
mod modpack;
|
mod modpack;
|
||||||
mod admin;
|
mod admin;
|
||||||
mod https;
|
mod https;
|
||||||
|
mod sftp;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
struct ModpackEntry{
|
struct ModpackEntry{
|
||||||
@ -35,7 +36,7 @@ fn main() {
|
|||||||
//modpack::get_modpacks();
|
//modpack::get_modpacks();
|
||||||
//prism::install_prism();
|
//prism::install_prism();
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.invoke_handler(tauri::generate_handler![greet, modpack::get_modpacks, modpack::launch_modpack, prism::launch_prism, prism::install_prism, admin::login])
|
.invoke_handler(tauri::generate_handler![greet, modpack::get_modpacks, modpack::launch_modpack, modpack::get_versions, prism::launch_prism, prism::install_prism, admin::login, admin::drop_session, admin::shift_up, admin::shift_down])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,11 @@ use crate::https;
|
|||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct ModpackEntry{
|
pub struct ModpackEntry{
|
||||||
name: String,
|
name: String,
|
||||||
id: String
|
pub id: String,
|
||||||
|
last_updated: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Serialize, Deserialize, Clone)]
|
||||||
pub struct VersionEntry{
|
pub struct VersionEntry{
|
||||||
version: String,
|
version: String,
|
||||||
file: String,
|
file: String,
|
||||||
@ -147,6 +148,7 @@ pub async fn get_modpacks() -> Vec<ModpackEntry> {
|
|||||||
buf.rewind();
|
buf.rewind();
|
||||||
let res = serde_json::from_reader(buf);
|
let res = serde_json::from_reader(buf);
|
||||||
if !res.is_ok() {
|
if !res.is_ok() {
|
||||||
|
println!("Result not ok!");
|
||||||
let paths = fs::read_dir(get_local_data_directory().join("prism").join("instances")).unwrap();
|
let paths = fs::read_dir(get_local_data_directory().join("prism").join("instances")).unwrap();
|
||||||
for path in paths {
|
for path in paths {
|
||||||
let path = path.unwrap();
|
let path = path.unwrap();
|
||||||
@ -158,27 +160,28 @@ pub async fn get_modpacks() -> Vec<ModpackEntry> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
modpacks.push(ModpackEntry{name: name.clone(), id: name})
|
modpacks.push(ModpackEntry{name: name.clone(), id: name, last_updated: format!("")})
|
||||||
}
|
}
|
||||||
return modpacks.clone()
|
return modpacks.clone()
|
||||||
}
|
}
|
||||||
let v: Value = res.unwrap();
|
let modpacks: Vec<ModpackEntry> = res.unwrap();
|
||||||
println!("{}", v[0]["name"]);
|
//println!("{}", v[0].name);
|
||||||
for pack in v.as_array().unwrap() {
|
//for pack in v.as_array().unwrap() {
|
||||||
modpacks.push(ModpackEntry{name: pack["name"].as_str().unwrap().to_string(), id: pack["id"].as_str().unwrap().to_string()});
|
//modpacks.push(ModpackEntry{name: pack["name"].as_str().unwrap().to_string(), id: pack["id"].as_str().unwrap().to_string(), last_updated: pack["last-updated"].as_str().unwrap().to_string()});
|
||||||
}
|
//}
|
||||||
//}
|
//}
|
||||||
return modpacks.clone();
|
return modpacks.clone();
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_versions(id: String) -> Result<Vec<VersionEntry>,Box<dyn std::error::Error>> {
|
#[tauri::command]
|
||||||
|
pub async fn get_versions(id: String) -> Result<Vec<VersionEntry>,String> {
|
||||||
let mut versions: Vec<VersionEntry> = Vec::new();
|
let mut versions: Vec<VersionEntry> = Vec::new();
|
||||||
let mut buf = Cursor::new(vec![]);
|
let mut buf = Cursor::new(vec![]);
|
||||||
//ftp::ftp_retr(None, PathBuf::new().join(id).join("versions.json"), &mut buf, |_, _, _| return);
|
//ftp::ftp_retr(None, PathBuf::new().join(id).join("versions.json"), &mut buf, |_, _, _| return);
|
||||||
https::download(None, format!("https://gitea.piwalker.net/fclauncher/{}/versions.json", id.clone()), &mut buf, format!("{}/versions.json", id.clone())).await;
|
https::download(None, format!("https://gitea.piwalker.net/fclauncher/{}/versions.json", id.clone()), &mut buf, format!("{}/versions.json", id.clone())).await;
|
||||||
buf.rewind();
|
buf.rewind();
|
||||||
let v: Value = serde_json::from_reader(buf)?;
|
let v: Value = serde_json::from_reader(buf).or(Err(format!("Unable to parse json")))?;
|
||||||
for version in v.as_array().unwrap() {
|
for version in v.as_array().unwrap() {
|
||||||
versions.push(VersionEntry{version: version["Version"].as_str().unwrap().to_string(), file: version["File"].as_str().unwrap().to_string(), date: version["Date"].as_str().unwrap().to_string()});
|
versions.push(VersionEntry{version: version["Version"].as_str().unwrap().to_string(), file: version["File"].as_str().unwrap().to_string(), date: version["Date"].as_str().unwrap().to_string()});
|
||||||
}
|
}
|
||||||
@ -201,3 +204,8 @@ fn get_java_version(mc_version: &str) -> u8{
|
|||||||
}
|
}
|
||||||
return java;
|
return java;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//pub fn create_json(modpacks: Vec<ModpackEntry>) -> Result<serde_json::Value, String> {
|
||||||
|
|
||||||
|
//}
|
43
FCLauncher/src-tauri/src/sftp.rs
Normal file
43
FCLauncher/src-tauri/src/sftp.rs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
use std::io::prelude::*;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use futures_util::io::BufReader;
|
||||||
|
use futures_util::io::Cursor;
|
||||||
|
use ssh2::OpenFlags;
|
||||||
|
use ssh2::Session;
|
||||||
|
use ssh2::Sftp;
|
||||||
|
|
||||||
|
use crate::https;
|
||||||
|
|
||||||
|
pub fn connect(username: String, password: String) -> Result<Session, String> {
|
||||||
|
let tcp = TcpStream::connect("gitea.piwalker.net:22").or(Err(format!("Unable to connect to host")))?;
|
||||||
|
let mut sess = Session::new().or(Err(format!("Unable to creat stream")))?;
|
||||||
|
sess.set_tcp_stream(tcp);
|
||||||
|
sess.handshake().unwrap();
|
||||||
|
sess.userauth_password(username.as_str(), password.as_str()).or(Err(format!("Invalid username or password")))?;
|
||||||
|
Ok(sess)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn uplaod(window: Option<tauri::Window>, sess: Session, path: PathBuf, mut reader: impl Read, upload_name: String, total_size: usize) -> Result<(), String>{
|
||||||
|
let sftp = sess.sftp().or(Err("unable to open sftp session"))?;
|
||||||
|
let mut file = sftp.create(path.as_path()).or(Err("Unable to open file"))?;
|
||||||
|
let mut uploaded = 0;
|
||||||
|
while true {
|
||||||
|
let mut buf = vec![0u8; 1024];
|
||||||
|
let res = reader.read(&mut buf).unwrap();
|
||||||
|
if res <= 0 {
|
||||||
|
if let Some(window) = window.clone() {
|
||||||
|
window.emit("download_finished", true).or(Err(format!("Unable to signal window!")))?;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
file.write_all(buf.split_at(res).0).unwrap();
|
||||||
|
uploaded += res;
|
||||||
|
println!("Uploading {} {}MB / {}MB", upload_name, uploaded/(1024*1024), total_size/(1024*1024));
|
||||||
|
if let Some(window) = window.clone() {
|
||||||
|
window.emit("download_progress", https::DownloadStatus{downloaded: uploaded as usize, total: total_size as usize, time_elapsed: 0, download_name: upload_name.clone()}).or(Err(format!("Unable to signal window")))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -23,8 +23,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="container" data-bs-theme="dark">
|
<div class="container-horizontal" data-bs-theme="dark">
|
||||||
<h1>Administration</h1>
|
<div id="modpacks" >
|
||||||
|
</div>
|
||||||
|
<div class="vertical-buttons" >
|
||||||
|
<button id="up" class="square-button"></button>
|
||||||
|
<button id="down" class="square-button"></button>
|
||||||
|
<button id="remove" class="square-button"></button>
|
||||||
|
</div>
|
||||||
|
<div class="vertical" >
|
||||||
|
<input placeholder="Version" id="pack_version" />
|
||||||
|
<input placeholder="File" id="file_path" />
|
||||||
|
<button id="browse">Browse</button>
|
||||||
|
<button id="Update">Update Pack</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="horizontal-input">
|
||||||
|
<input id="pack_name" placeholder="name" />
|
||||||
|
<button id="add" class="square-button"></button>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -4,6 +4,8 @@ const { ask } = window.__TAURI__.dialog;
|
|||||||
const downBar = document.querySelector(".progressFinished");
|
const downBar = document.querySelector(".progressFinished");
|
||||||
//import { listen } from '@tauri-apps/api';
|
//import { listen } from '@tauri-apps/api';
|
||||||
|
|
||||||
|
var selected_pack = "";
|
||||||
|
|
||||||
const download_progress = listen("download_progress", (progress) => {
|
const download_progress = listen("download_progress", (progress) => {
|
||||||
console.log("Downloading");
|
console.log("Downloading");
|
||||||
//console.log("Downloaded "+progress.payload.downloaded/(1024*1024) +"MB / " + progress.payload.total/(1024*1024) + "MB");
|
//console.log("Downloaded "+progress.payload.downloaded/(1024*1024) +"MB / " + progress.payload.total/(1024*1024) + "MB");
|
||||||
@ -26,11 +28,54 @@ const download_finished = listen("download_finished", (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
window.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.getElementById("up").addEventListener("click", up);
|
||||||
|
document.getElementById("down").addEventListener("click", down);
|
||||||
document.getElementById("back").addEventListener("click", back);
|
document.getElementById("back").addEventListener("click", back);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.onload = async function() {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
function up(){
|
||||||
|
invoke("shift_up", { id: selected_pack }).then(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
function down(){
|
||||||
|
invoke("shift_down", { id: selected_pack }).then(refresh);
|
||||||
|
}
|
||||||
|
|
||||||
function back(){
|
function back(){
|
||||||
|
invoke("drop_session");
|
||||||
window.location.href = "index.html";
|
window.location.href = "index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(){
|
||||||
|
invoke("get_modpacks").then(addModpacks);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addModpacks(modpacks) {
|
||||||
|
var modpacks_list = document.getElementById("modpacks");
|
||||||
|
while (modpacks_list.firstChild) {
|
||||||
|
modpacks_list.removeChild(modpacks_list.lastChild);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < modpacks.length; i++){
|
||||||
|
var div = document.createElement("div");
|
||||||
|
div.textContent = modpacks[i].name;
|
||||||
|
div.className = "modpack";
|
||||||
|
div.id = modpacks[i].id;
|
||||||
|
if(modpacks[i].id == selected_pack){
|
||||||
|
div.classList.add("modpack-selected");
|
||||||
|
}
|
||||||
|
div.addEventListener("click", function() { modpackClick(modpacks[i].id) });
|
||||||
|
modpacks_list.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function modpackClick(id){
|
||||||
|
var old = selected_pack;
|
||||||
|
document.getElementById(id).classList.add("modpack-selected");
|
||||||
|
selected_pack = id;
|
||||||
|
document.getElementById(old).classList.remove("modpack-selected");
|
||||||
}
|
}
|
BIN
FCLauncher/src/assets/add.png
Normal file
BIN
FCLauncher/src/assets/add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
11
FCLauncher/src/assets/add.svg
Normal file
11
FCLauncher/src/assets/add.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" height="800px" width="800px" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 512 512">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M256,11C120.9,11,11,120.9,11,256s109.9,245,245,245s245-109.9,245-245S391.1,11,256,11z M256,460.2 c-112.6,0-204.2-91.6-204.2-204.2S143.4,51.8,256,51.8S460.2,143.4,460.2,256S368.6,460.2,256,460.2z"/>
|
||||||
|
<path d="m357.6,235.6h-81.2v-81.2c0-11.3-9.1-20.4-20.4-20.4-11.3,0-20.4,9.1-20.4,20.4v81.2h-81.2c-11.3,0-20.4,9.1-20.4,20.4s9.1,20.4 20.4,20.4h81.2v81.2c0,11.3 9.1,20.4 20.4,20.4 11.3,0 20.4-9.1 20.4-20.4v-81.2h81.2c11.3,0 20.4-9.1 20.4-20.4s-9.1-20.4-20.4-20.4z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 943 B |
BIN
FCLauncher/src/assets/down.png
Normal file
BIN
FCLauncher/src/assets/down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
BIN
FCLauncher/src/assets/remove.png
Normal file
BIN
FCLauncher/src/assets/remove.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
FCLauncher/src/assets/up.png
Normal file
BIN
FCLauncher/src/assets/up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
@ -14,6 +14,7 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function back(){
|
function back(){
|
||||||
|
invoke("drop_session");
|
||||||
window.location.href = "index.html";
|
window.location.href = "index.html";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +69,8 @@ window.onload = async function() {
|
|||||||
function addModpacks(modpacks) {
|
function addModpacks(modpacks) {
|
||||||
|
|
||||||
var dropdown = document.getElementById("Modpacks");
|
var dropdown = document.getElementById("Modpacks");
|
||||||
modpacks.sort((a, b) => a.name.localeCompare(b.name));
|
//modpacks.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
modpacks.reverse();
|
//modpacks.reverse();
|
||||||
for (let i = 0; i < modpacks.length; i++){
|
for (let i = 0; i < modpacks.length; i++){
|
||||||
var opt = document.createElement("option");
|
var opt = document.createElement("option");
|
||||||
opt.text = modpacks[i].name;
|
opt.text = modpacks[i].name;
|
||||||
|
@ -27,6 +27,39 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vertical {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-horizontal {
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
align-items: left;
|
||||||
|
justify-content: left;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-input {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
align-items: left;
|
||||||
|
justify-content: left;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
@ -273,4 +306,71 @@ button {
|
|||||||
background-color: red;
|
background-color: red;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modpacks{
|
||||||
|
width: 30%;
|
||||||
|
height: 13em;
|
||||||
|
background-color: #666565;
|
||||||
|
overflow: auto;
|
||||||
|
margin-left: 10px;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modpack {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modpack:hover {
|
||||||
|
background-color: lightgray;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modpack-selected {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vertical-buttons {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
margin-top: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 15em;
|
||||||
|
gap: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square-button {
|
||||||
|
width: 2em;
|
||||||
|
height: 2.5em;
|
||||||
|
top: 5px;
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#up{
|
||||||
|
background-image: url('assets/up.png');
|
||||||
|
background-size:cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#down{
|
||||||
|
background-image: url('assets/down.png');
|
||||||
|
background-size:cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add{
|
||||||
|
background-image: url('assets/add.png');
|
||||||
|
background-size:cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#remove{
|
||||||
|
background-image: url('assets/remove.png');
|
||||||
|
background-size:cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pack_name{
|
||||||
|
width: 13em;
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user