Merge pull request #56 from modrinth/mod-management
Profile mod management
42
.github/workflows/cli-build.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: CLI Build + Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_cli
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: install dependencies (ubuntu only)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: Get build cache
|
||||
id: cache-build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ../target/**
|
||||
key: ${{ runner.os }}-theseus
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
name: Build program
|
||||
with:
|
||||
command: build
|
||||
args: --bin theseus_cli
|
||||
- name: Run Lint
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --bin theseus_cli
|
||||
34
.github/workflows/gui-build.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: GUI Build + Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_gui
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Get yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable --immutable-cache --check-cache
|
||||
- name: Run Lint
|
||||
run: npm run lint
|
||||
- name: Build
|
||||
run: npm run build
|
||||
59
.github/workflows/tauri-build.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
name: 'Tauri GUI Build'
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
jobs:
|
||||
test-tauri:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_gui
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
- name: Get build cache
|
||||
id: cache-build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ../target/**
|
||||
key: ${{ runner.os }}-theseus
|
||||
- name: Get yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: install frontend dependencies
|
||||
run: yarn install --immutable --immutable-cache --check-cache
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run Lint
|
||||
if: matrix.platform == 'ubuntu-20.04'
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --bin theseus_cli
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "theseus_gui/locales"]
|
||||
path = theseus_gui/locales
|
||||
url = git@github.com:modrinth/translations.git
|
||||
13
COPYING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copying
|
||||
|
||||
The source code of the knossos repository is licensed under the GNU Affero General Public License, Version 3 only, which is provided in the file [LICENSE](./LICENSE). However, some files listed below are licensed under a different license.
|
||||
|
||||
## Modrinth logo
|
||||
|
||||
Any files depicting the Modrinth branding, including the wrench-in-labyrinth logo, the landing image, and variations thereof, are licensed as follows:
|
||||
|
||||
> All rights reserved. © 2020-2023 Rinth, Inc.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
- theseus_gui/src-tauri/icons
|
||||
1465
Cargo.lock
generated
103
flake.lock
generated
@@ -1,103 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1655706580,
|
||||
"narHash": "sha256-7DshIT1Ya5W9NAW7UdnYCHsGmXfOXJZCEHbbB/cCX7g=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "d895003d8e03ac2fc8ffe2aa898299cbef1a7048",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1655042882,
|
||||
"narHash": "sha256-9BX8Fuez5YJlN7cdPO63InoyBy7dm3VlJkkmTt6fS1A=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "cddffb5aa211f50c4b8750adbec0bbbdfb26bb9f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1655624069,
|
||||
"narHash": "sha256-7g1zwTdp35GMTERnSzZMWJ7PG3QdDE8VOX3WsnOkAtM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0d68d7c857fe301d49cdcd56130e0beea4ecd5aa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1655654433,
|
||||
"narHash": "sha256-auHQ0XPCiaTPSn+R3Yu4J7oZ5Zq/FS5/Da1ivvdYb/Y=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "427061da19723f2206fe4dcb175c9c43b9a6193d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"locked": {
|
||||
"lastModified": 1653893745,
|
||||
"narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
72
flake.nix
@@ -1,72 +0,0 @@
|
||||
{
|
||||
description = "The official Modrinth launcher";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
naersk = {
|
||||
url = "github:nix-community/naersk";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = inputs@{self, ...}:
|
||||
inputs.utils.lib.eachDefaultSystem (system: let
|
||||
pkgs = import inputs.nixpkgs { inherit system; };
|
||||
fenix = inputs.fenix.packages.${system};
|
||||
utils = inputs.utils.lib;
|
||||
|
||||
toolchain = with fenix;
|
||||
combine [
|
||||
minimal.rustc minimal.cargo
|
||||
];
|
||||
|
||||
naersk = inputs.naersk.lib.${system}.override {
|
||||
rustc = toolchain;
|
||||
cargo = toolchain;
|
||||
};
|
||||
|
||||
deps = with pkgs; {
|
||||
global = [
|
||||
openssl pkg-config gcc
|
||||
];
|
||||
gui = [
|
||||
gtk4 gdk-pixbuf atk webkitgtk dbus
|
||||
];
|
||||
shell = [
|
||||
(with fenix; combine [toolchain default.clippy complete.rust-src rust-analyzer])
|
||||
git
|
||||
jdk17 jdk8
|
||||
];
|
||||
};
|
||||
in {
|
||||
packages = {
|
||||
theseus-cli = naersk.buildPackage {
|
||||
pname = "theseus_cli";
|
||||
src = ./.;
|
||||
buildInputs = deps.global;
|
||||
cargoBuildOptions = x: x ++ ["-p" "theseus_cli"];
|
||||
};
|
||||
};
|
||||
|
||||
apps = {
|
||||
cli = utils.mkApp {
|
||||
drv = self.packages.${system}.theseus-cli;
|
||||
};
|
||||
cli-dev = utils.mkApp {
|
||||
drv = self.packages.${system}.theseus-cli.overrideAttrs (old: old // {
|
||||
release = false;
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with deps;
|
||||
global ++ gui ++ shell;
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -11,7 +11,9 @@ bytes = "1"
|
||||
bincode = { version = "2.0.0-rc.1", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
sha1 = { version = "0.6.0", features = ["std"]}
|
||||
toml = "0.7.3"
|
||||
sha1 = { version = "0.6.1", features = ["std"]}
|
||||
sha2 = "0.9.9"
|
||||
sled = { version = "0.34.7", features = ["compression"] }
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||
@@ -29,7 +31,7 @@ tracing = "0.1"
|
||||
tracing-error = "0.2"
|
||||
|
||||
|
||||
async-tungstenite = { version = "0.17", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||
async-tungstenite = { version = "0.20.0", features = ["tokio-runtime", "tokio-native-tls"] }
|
||||
futures = "0.3"
|
||||
once_cell = "1.9.0"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn has_user(user: uuid::Uuid) -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let users = state.users.read().await;
|
||||
|
||||
Ok(users.contains(user)?)
|
||||
users.contains(user)
|
||||
}
|
||||
|
||||
/// Get a copy of the list of all user credentials
|
||||
|
||||
@@ -68,12 +68,7 @@ pub async fn is_managed(profile: &Path) -> crate::Result<bool> {
|
||||
pub async fn is_loaded(profile: &Path) -> crate::Result<bool> {
|
||||
let state = State::get().await?;
|
||||
let profiles = state.profiles.read().await;
|
||||
Ok(profiles
|
||||
.0
|
||||
.get(profile)
|
||||
.map(Option::as_ref)
|
||||
.flatten()
|
||||
.is_some())
|
||||
Ok(profiles.0.get(profile).and_then(Option::as_ref).is_some())
|
||||
}
|
||||
|
||||
/// Edit a profile using a given asynchronous closure
|
||||
@@ -138,8 +133,8 @@ pub async fn run(
|
||||
})?;
|
||||
let version_info = d::minecraft::fetch_version_info(version).await?;
|
||||
|
||||
let ref pre_launch_hooks =
|
||||
profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
||||
let pre_launch_hooks =
|
||||
&profile.hooks.as_ref().unwrap_or(&settings.hooks).pre_launch;
|
||||
for hook in pre_launch_hooks.iter() {
|
||||
// TODO: hook parameters
|
||||
let mut cmd = hook.split(' ');
|
||||
@@ -190,7 +185,7 @@ pub async fn run(
|
||||
.as_error());
|
||||
}
|
||||
|
||||
let ref java_args = profile
|
||||
let java_args = profile
|
||||
.java
|
||||
.as_ref()
|
||||
.and_then(|it| it.extra_arguments.as_ref())
|
||||
@@ -201,18 +196,18 @@ pub async fn run(
|
||||
.as_ref()
|
||||
.map_or(&settings.hooks.wrapper, |it| &it.wrapper);
|
||||
|
||||
let ref memory = profile.memory.unwrap_or(settings.memory);
|
||||
let ref resolution = profile.resolution.unwrap_or(settings.game_resolution);
|
||||
let memory = profile.memory.unwrap_or(settings.memory);
|
||||
let resolution = profile.resolution.unwrap_or(settings.game_resolution);
|
||||
|
||||
crate::launcher::launch_minecraft(
|
||||
&profile.metadata.game_version,
|
||||
&profile.metadata.loader_version,
|
||||
&profile.path,
|
||||
&java_install,
|
||||
&java_args,
|
||||
&wrapper,
|
||||
memory,
|
||||
resolution,
|
||||
java_install,
|
||||
java_args,
|
||||
wrapper,
|
||||
&memory,
|
||||
&resolution,
|
||||
credentials,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -17,6 +17,8 @@ pub static REQWEST_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub const MODRINTH_API_URL: &str = "https://api.modrinth.com/v2/";
|
||||
|
||||
pub fn sled_config() -> sled::Config {
|
||||
sled::Config::default().use_compression(true)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub fn get_class_paths_jar<T: AsRef<str>>(
|
||||
pub fn get_lib_path(libraries_path: &Path, lib: &str) -> crate::Result<String> {
|
||||
let mut path = libraries_path.to_path_buf();
|
||||
|
||||
path.push(get_path_from_artifact(lib.as_ref())?);
|
||||
path.push(get_path_from_artifact(lib)?);
|
||||
|
||||
let path = &path.canonicalize().map_err(|_| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
@@ -164,8 +164,7 @@ fn parse_jvm_argument(
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.replace("${classpath_separator}", classpath_separator())
|
||||
.replace("${launcher_name}", "theseus")
|
||||
@@ -219,7 +218,6 @@ pub fn get_minecraft_arguments(
|
||||
resolution,
|
||||
)?
|
||||
.split(' ')
|
||||
.into_iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect())
|
||||
} else {
|
||||
@@ -260,8 +258,7 @@ fn parse_minecraft_argument(
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_owned(),
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.replace(
|
||||
"${assets_root}",
|
||||
@@ -274,8 +271,7 @@ fn parse_minecraft_argument(
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_owned(),
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.replace(
|
||||
"${game_assets}",
|
||||
@@ -288,8 +284,7 @@ fn parse_minecraft_argument(
|
||||
))
|
||||
.as_error()
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_owned(),
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.replace("${version_type}", version_type.as_str())
|
||||
.replace("${resolution_width}", &resolution.0.to_string())
|
||||
@@ -366,7 +361,7 @@ pub fn get_processor_arguments<T: AsRef<str>>(
|
||||
pub async fn get_processor_main_class(
|
||||
path: String,
|
||||
) -> crate::Result<Option<String>> {
|
||||
Ok(tokio::task::spawn_blocking(move || {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let zipfile = std::fs::File::open(&path)?;
|
||||
let mut archive = zip::ZipArchive::new(zipfile).map_err(|_| {
|
||||
crate::ErrorKind::LauncherError(format!(
|
||||
@@ -400,5 +395,5 @@ pub async fn get_processor_main_class(
|
||||
Ok::<Option<String>, crate::Error>(None)
|
||||
})
|
||||
.await
|
||||
.unwrap()?)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ use async_tungstenite as ws;
|
||||
use bincode::{Decode, Encode};
|
||||
use chrono::{prelude::*, Duration};
|
||||
use futures::prelude::*;
|
||||
use once_cell::sync::*;
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
pub const HYDRA_URL: Lazy<Url> =
|
||||
Lazy::new(|| Url::parse("https://hydra.modrinth.com").unwrap());
|
||||
lazy_static! {
|
||||
static ref HYDRA_URL: Url =
|
||||
Url::parse("https://hydra.modrinth.com").unwrap();
|
||||
}
|
||||
|
||||
// Socket messages
|
||||
#[derive(Deserialize)]
|
||||
@@ -65,7 +67,7 @@ pub struct HydraAuthFlow<S: AsyncRead + AsyncWrite + Unpin> {
|
||||
impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||
pub async fn new() -> crate::Result<Self> {
|
||||
let sock_url = wrap_ref_builder!(
|
||||
it = HYDRA_URL =>
|
||||
it = HYDRA_URL.clone() =>
|
||||
{ it.set_scheme("wss").ok() }
|
||||
);
|
||||
let (socket, _) = ws::tokio::connect_async(sock_url.clone()).await?;
|
||||
|
||||
@@ -75,7 +75,7 @@ pub async fn download_client(
|
||||
st: &State,
|
||||
version_info: &GameVersionInfo,
|
||||
) -> crate::Result<()> {
|
||||
let ref version = version_info.id;
|
||||
let version = &version_info.id;
|
||||
log::debug!("Locating client for version {version}");
|
||||
let client_download = version_info
|
||||
.downloads
|
||||
@@ -143,7 +143,7 @@ pub async fn download_assets(
|
||||
stream::iter(index.objects.iter())
|
||||
.map(Ok::<(&String, &Asset), crate::Error>)
|
||||
.try_for_each_concurrent(None, |(name, asset)| async move {
|
||||
let ref hash = asset.hash;
|
||||
let hash = &asset.hash;
|
||||
let resource_path = st.directories.object_dir(hash);
|
||||
let url = format!(
|
||||
"https://resources.download.minecraft.net/{sub_hash}/{hash}",
|
||||
@@ -158,7 +158,7 @@ pub async fn download_assets(
|
||||
let resource = fetch_cell
|
||||
.get_or_try_init(|| fetch(&url, Some(hash), &permit))
|
||||
.await?;
|
||||
write(&resource_path, &resource, &permit).await?;
|
||||
write(&resource_path, resource, &permit).await?;
|
||||
log::info!("Fetched asset with hash {hash}");
|
||||
}
|
||||
Ok::<_, crate::Error>(())
|
||||
@@ -172,7 +172,7 @@ pub async fn download_assets(
|
||||
let resource_path = st.directories.legacy_assets_dir().join(
|
||||
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
|
||||
);
|
||||
write(&resource_path, &resource, &permit).await?;
|
||||
write(&resource_path, resource, &permit).await?;
|
||||
log::info!("Fetched legacy asset with hash {hash}");
|
||||
}
|
||||
Ok::<_, crate::Error>(())
|
||||
|
||||
@@ -44,6 +44,7 @@ macro_rules! processor_rules {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[tracing::instrument(skip_all, fields(path = ?instance_path))]
|
||||
pub async fn launch_minecraft(
|
||||
game_version: &str,
|
||||
@@ -75,7 +76,7 @@ pub async fn launch_minecraft(
|
||||
|
||||
let mut version_info = download::download_version_info(
|
||||
&state,
|
||||
&version,
|
||||
version,
|
||||
loader_version.as_ref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -122,6 +122,12 @@ impl DirectoryInfo {
|
||||
self.config_dir.join("settings.json")
|
||||
}
|
||||
|
||||
/// Get the cache directory for Theseus
|
||||
#[inline]
|
||||
pub fn caches_dir(&self) -> PathBuf {
|
||||
self.config_dir.join("caches")
|
||||
}
|
||||
|
||||
/// Get path from environment variable
|
||||
#[inline]
|
||||
fn env_path(name: &str) -> Option<PathBuf> {
|
||||
|
||||
@@ -57,7 +57,7 @@ impl Metadata {
|
||||
|
||||
if let Some(ref meta_bin) = db.get(METADATA_DB_FIELD)? {
|
||||
match bincode::decode_from_slice::<Self, _>(
|
||||
&meta_bin,
|
||||
meta_bin,
|
||||
*BINCODE_CONFIG,
|
||||
) {
|
||||
Ok((meta, _)) => metadata = Some(meta),
|
||||
|
||||
@@ -16,6 +16,9 @@ pub use self::profiles::*;
|
||||
mod settings;
|
||||
pub use self::settings::*;
|
||||
|
||||
mod projects;
|
||||
pub use self::projects::*;
|
||||
|
||||
mod users;
|
||||
pub use self::users::*;
|
||||
|
||||
@@ -62,7 +65,7 @@ impl State {
|
||||
// Launcher data
|
||||
let (metadata, profiles) = tokio::try_join! {
|
||||
Metadata::init(&database),
|
||||
Profiles::init(&database),
|
||||
Profiles::init(&database, &directories),
|
||||
}?;
|
||||
let users = Users::init(&database)?;
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use super::settings::{Hooks, MemorySettings, WindowSize};
|
||||
use crate::config::BINCODE_CONFIG;
|
||||
use crate::data::DirectoryInfo;
|
||||
use crate::state::projects::Project;
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use futures::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -16,7 +18,7 @@ pub(crate) struct Profiles(pub HashMap<PathBuf, Option<Profile>>);
|
||||
|
||||
// TODO: possibly add defaults to some of these values
|
||||
pub const CURRENT_FORMAT_VERSION: u32 = 1;
|
||||
pub const SUPPORTED_ICON_FORMATS: &[&'static str] = &[
|
||||
pub const SUPPORTED_ICON_FORMATS: &[&str] = &[
|
||||
"bmp", "gif", "jpeg", "jpg", "jpe", "png", "svg", "svgz", "webp", "rgb",
|
||||
"mp4",
|
||||
];
|
||||
@@ -27,6 +29,7 @@ pub struct Profile {
|
||||
#[serde(skip)]
|
||||
pub path: PathBuf,
|
||||
pub metadata: ProfileMetadata,
|
||||
pub projects: HashMap<PathBuf, Project>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub java: Option<JavaSettings>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -51,26 +54,23 @@ pub struct ProfileMetadata {
|
||||
}
|
||||
|
||||
// TODO: Quilt?
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize)]
|
||||
#[derive(
|
||||
Debug, Eq, PartialEq, Clone, Copy, Deserialize, Serialize, Default,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ModLoader {
|
||||
#[default]
|
||||
Vanilla,
|
||||
Forge,
|
||||
Fabric,
|
||||
}
|
||||
|
||||
impl Default for ModLoader {
|
||||
fn default() -> Self {
|
||||
ModLoader::Vanilla
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ModLoader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
&Self::Vanilla => "Vanilla",
|
||||
&Self::Forge => "Forge",
|
||||
&Self::Fabric => "Fabric",
|
||||
f.write_str(match *self {
|
||||
Self::Vanilla => "Vanilla",
|
||||
Self::Forge => "Forge",
|
||||
Self::Fabric => "Fabric",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,7 @@ impl Profile {
|
||||
loader_version: None,
|
||||
format_version: CURRENT_FORMAT_VERSION,
|
||||
},
|
||||
projects: HashMap::new(),
|
||||
java: None,
|
||||
memory: None,
|
||||
resolution: None,
|
||||
@@ -200,7 +201,10 @@ impl Profile {
|
||||
|
||||
impl Profiles {
|
||||
#[tracing::instrument(skip(db))]
|
||||
pub async fn init(db: &sled::Db) -> crate::Result<Self> {
|
||||
pub async fn init(
|
||||
db: &sled::Db,
|
||||
dirs: &DirectoryInfo,
|
||||
) -> crate::Result<Self> {
|
||||
let profile_db = db.get(PROFILE_SUBTREE)?.map_or(
|
||||
Ok(Default::default()),
|
||||
|bytes| {
|
||||
@@ -212,7 +216,7 @@ impl Profiles {
|
||||
},
|
||||
)?;
|
||||
|
||||
let profiles = stream::iter(profile_db.iter())
|
||||
let mut profiles = stream::iter(profile_db.iter())
|
||||
.then(|it| async move {
|
||||
let path = PathBuf::from(it);
|
||||
let prof = match Self::read_profile_from_dir(&path).await {
|
||||
@@ -227,6 +231,37 @@ impl Profiles {
|
||||
.collect::<HashMap<PathBuf, Option<Profile>>>()
|
||||
.await;
|
||||
|
||||
// project path, parent profile path
|
||||
let mut files: HashMap<PathBuf, PathBuf> = HashMap::new();
|
||||
{
|
||||
for (profile_path, _profile_opt) in profiles.iter() {
|
||||
let mut read_paths = |path: &str| {
|
||||
for path in std::fs::read_dir(profile_path.join(path))? {
|
||||
files.insert(path?.path(), profile_path.clone());
|
||||
}
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
};
|
||||
read_paths("mods")?;
|
||||
read_paths("shaders")?;
|
||||
read_paths("resourcepacks")?;
|
||||
read_paths("datapacks")?;
|
||||
}
|
||||
}
|
||||
let inferred = super::projects::infer_data_from_files(
|
||||
files.keys().cloned().collect(),
|
||||
dirs.caches_dir(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (key, value) in inferred {
|
||||
if let Some(profile_path) = files.get(&key) {
|
||||
if let Some(Some(profile)) = profiles.get_mut(profile_path) {
|
||||
profile.projects.insert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self(profiles))
|
||||
}
|
||||
|
||||
@@ -296,63 +331,3 @@ impl Profiles {
|
||||
Ok(profile)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn profile_test() -> Result<(), serde_json::Error> {
|
||||
let profile = Profile {
|
||||
path: PathBuf::new(),
|
||||
metadata: ProfileMetadata {
|
||||
name: String::from("Example Pack"),
|
||||
icon: None,
|
||||
game_version: String::from("1.18.2"),
|
||||
loader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
format_version: CURRENT_FORMAT_VERSION,
|
||||
},
|
||||
java: Some(JavaSettings {
|
||||
install: Some(PathBuf::from("/usr/bin/java")),
|
||||
extra_arguments: Some(Vec::new()),
|
||||
}),
|
||||
memory: Some(MemorySettings {
|
||||
minimum: None,
|
||||
maximum: 8192,
|
||||
}),
|
||||
resolution: Some(WindowSize(1920, 1080)),
|
||||
hooks: Some(Hooks {
|
||||
pre_launch: HashSet::new(),
|
||||
wrapper: None,
|
||||
post_exit: HashSet::new(),
|
||||
}),
|
||||
};
|
||||
let json = serde_json::json!({
|
||||
"metadata": {
|
||||
"name": "Example Pack",
|
||||
"game_version": "1.18.2",
|
||||
"format_version": 1u32,
|
||||
"loader": "vanilla",
|
||||
},
|
||||
"java": {
|
||||
"extra_arguments": [],
|
||||
"install": "/usr/bin/java",
|
||||
},
|
||||
"memory": {
|
||||
"maximum": 8192u32,
|
||||
},
|
||||
"resolution": (1920u16, 1080u16),
|
||||
"hooks": {},
|
||||
});
|
||||
|
||||
assert_eq!(serde_json::to_value(profile.clone())?, json.clone());
|
||||
assert_str_eq!(
|
||||
format!("{:?}", serde_json::from_value::<Profile>(json)?),
|
||||
format!("{:?}", profile),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
428
theseus/src/state/projects.rs
Normal file
@@ -0,0 +1,428 @@
|
||||
//! Project management + inference
|
||||
|
||||
use crate::config::{MODRINTH_API_URL, REQWEST_CLIENT};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sha2::Digest;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use zip::ZipArchive;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Project {
|
||||
pub sha512: String,
|
||||
pub disabled: bool,
|
||||
pub metadata: ProjectMetadata,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct ModrinthProject {
|
||||
pub id: String,
|
||||
pub slug: Option<String>,
|
||||
pub project_type: String,
|
||||
pub team: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub body: String,
|
||||
|
||||
pub published: DateTime<Utc>,
|
||||
pub updated: DateTime<Utc>,
|
||||
|
||||
pub client_side: String,
|
||||
pub server_side: String,
|
||||
|
||||
pub downloads: u32,
|
||||
pub followers: u32,
|
||||
|
||||
pub categories: Vec<String>,
|
||||
pub additional_categories: Vec<String>,
|
||||
pub game_versions: Vec<String>,
|
||||
pub loaders: Vec<String>,
|
||||
|
||||
pub versions: Vec<String>,
|
||||
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ProjectMetadata {
|
||||
Modrinth(Box<ModrinthProject>),
|
||||
Inferred {
|
||||
title: Option<String>,
|
||||
description: Option<String>,
|
||||
authors: Vec<String>,
|
||||
version: Option<String>,
|
||||
icon: Option<PathBuf>,
|
||||
},
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub async fn infer_data_from_files(
|
||||
paths: Vec<PathBuf>,
|
||||
cache_dir: PathBuf,
|
||||
) -> crate::Result<HashMap<PathBuf, Project>> {
|
||||
let mut file_path_hashes = HashMap::new();
|
||||
|
||||
// TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory
|
||||
for path in paths.clone() {
|
||||
let mut file = tokio::fs::File::open(path.clone()).await?;
|
||||
|
||||
let mut buffer = Vec::new();
|
||||
file.read_to_end(&mut buffer).await?;
|
||||
|
||||
let hash = format!("{:x}", sha2::Sha512::digest(&buffer));
|
||||
file_path_hashes.insert(hash, path.clone());
|
||||
}
|
||||
|
||||
// TODO: add disabled mods
|
||||
// TODO: add retrying
|
||||
#[derive(Deserialize)]
|
||||
pub struct ModrinthVersion {
|
||||
pub project_id: String,
|
||||
}
|
||||
let files: HashMap<String, ModrinthVersion> = REQWEST_CLIENT
|
||||
.post(format!("{}version_files", MODRINTH_API_URL))
|
||||
.json(&json!({
|
||||
"hashes": file_path_hashes.keys().collect::<Vec<_>>(),
|
||||
"algorithm": "sha512",
|
||||
}))
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
let projects: Vec<ModrinthProject> = REQWEST_CLIENT
|
||||
.get(format!(
|
||||
"{}projects?ids={}",
|
||||
MODRINTH_API_URL,
|
||||
serde_json::to_string(
|
||||
&files
|
||||
.values()
|
||||
.map(|x| x.project_id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
)?
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
let mut return_projects = HashMap::new();
|
||||
let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new();
|
||||
|
||||
for (hash, path) in file_path_hashes {
|
||||
if let Some(file) = files.get(&hash) {
|
||||
if let Some(project) =
|
||||
projects.iter().find(|x| file.project_id == x.id)
|
||||
{
|
||||
return_projects.insert(
|
||||
path,
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Modrinth(Box::new(
|
||||
project.clone(),
|
||||
)),
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
further_analyze_projects.push((hash, path));
|
||||
}
|
||||
|
||||
for (hash, path) in further_analyze_projects {
|
||||
let file = File::open(path.clone())?;
|
||||
|
||||
// TODO: get rid of below unwrap
|
||||
let mut zip = ZipArchive::new(file).unwrap();
|
||||
|
||||
let read_icon_from_file =
|
||||
|icon_path: Option<String>| -> crate::Result<Option<PathBuf>> {
|
||||
if let Some(icon_path) = icon_path {
|
||||
// we have to repoen the zip twice here :(
|
||||
let zip_file = File::open(path.clone())?;
|
||||
if let Ok(mut zip) = ZipArchive::new(zip_file) {
|
||||
if let Ok(mut file) = zip.by_name(&icon_path) {
|
||||
let mut bytes = Vec::new();
|
||||
if file.read_to_end(&mut bytes).is_ok() {
|
||||
let extension = Path::new(&icon_path)
|
||||
.extension()
|
||||
.and_then(OsStr::to_str);
|
||||
let hash = sha1::Sha1::from(&bytes).hexdigest();
|
||||
let path = cache_dir.join("icons").join(
|
||||
if let Some(ext) = extension {
|
||||
format!("{hash}.{ext}")
|
||||
} else {
|
||||
hash
|
||||
},
|
||||
);
|
||||
|
||||
if !path.exists() {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
let mut file = File::create(path.clone())?;
|
||||
file.write_all(&bytes)?;
|
||||
}
|
||||
|
||||
return Ok(Some(path));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
};
|
||||
|
||||
if let Ok(mut file) = zip.by_name("META-INF/mods.toml") {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ForgeModInfo {
|
||||
pub mods: Vec<ForgeMod>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ForgeMod {
|
||||
mod_id: String,
|
||||
version: Option<String>,
|
||||
display_name: Option<String>,
|
||||
description: Option<String>,
|
||||
logo_file: Option<String>,
|
||||
authors: Option<String>,
|
||||
}
|
||||
|
||||
let mut file_str = String::new();
|
||||
if file.read_to_string(&mut file_str).is_ok() {
|
||||
if let Ok(pack) =
|
||||
serde_json::from_str::<ForgeModInfo>(&file_str)
|
||||
{
|
||||
if let Some(pack) = pack.mods.first() {
|
||||
let icon = read_icon_from_file(pack.logo_file.clone())?;
|
||||
|
||||
return_projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Inferred {
|
||||
title: Some(
|
||||
pack.display_name
|
||||
.clone()
|
||||
.unwrap_or(pack.mod_id.clone()),
|
||||
),
|
||||
description: pack.description.clone(),
|
||||
authors: pack
|
||||
.authors
|
||||
.clone()
|
||||
.map(|x| vec![x])
|
||||
.unwrap_or_default(),
|
||||
version: pack.version.clone(),
|
||||
icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut file) = zip.by_name("mcmod.info") {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ForgeMod {
|
||||
modid: String,
|
||||
name: String,
|
||||
description: Option<String>,
|
||||
version: Option<String>,
|
||||
author_list: Option<Vec<String>>,
|
||||
logo_file: Option<String>,
|
||||
}
|
||||
|
||||
let mut file_str = String::new();
|
||||
if file.read_to_string(&mut file_str).is_ok() {
|
||||
if let Ok(pack) = serde_json::from_str::<ForgeMod>(&file_str) {
|
||||
let icon = read_icon_from_file(pack.logo_file)?;
|
||||
|
||||
return_projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Inferred {
|
||||
title: Some(if pack.name.is_empty() {
|
||||
pack.modid
|
||||
} else {
|
||||
pack.name
|
||||
}),
|
||||
description: pack.description,
|
||||
authors: pack.author_list.unwrap_or_default(),
|
||||
version: pack.version,
|
||||
icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut file) = zip.by_name("fabric.mod.json") {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum FabricAuthor {
|
||||
String(String),
|
||||
Object { name: String },
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FabricMod {
|
||||
id: String,
|
||||
version: String,
|
||||
name: Option<String>,
|
||||
description: Option<String>,
|
||||
authors: Vec<FabricAuthor>,
|
||||
icon: Option<String>,
|
||||
}
|
||||
|
||||
let mut file_str = String::new();
|
||||
if file.read_to_string(&mut file_str).is_ok() {
|
||||
if let Ok(pack) = serde_json::from_str::<FabricMod>(&file_str) {
|
||||
let icon = read_icon_from_file(pack.icon)?;
|
||||
|
||||
return_projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Inferred {
|
||||
title: Some(pack.name.unwrap_or(pack.id)),
|
||||
description: pack.description,
|
||||
authors: pack
|
||||
.authors
|
||||
.into_iter()
|
||||
.map(|x| match x {
|
||||
FabricAuthor::String(name) => name,
|
||||
FabricAuthor::Object { name } => name,
|
||||
})
|
||||
.collect(),
|
||||
version: Some(pack.version),
|
||||
icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut file) = zip.by_name("quilt.mod.json") {
|
||||
#[derive(Deserialize)]
|
||||
struct QuiltMetadata {
|
||||
pub name: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub contributors: Option<HashMap<String, String>>,
|
||||
pub icon: Option<String>,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct QuiltMod {
|
||||
id: String,
|
||||
version: String,
|
||||
metadata: Option<QuiltMetadata>,
|
||||
}
|
||||
|
||||
let mut file_str = String::new();
|
||||
if file.read_to_string(&mut file_str).is_ok() {
|
||||
if let Ok(pack) = serde_json::from_str::<QuiltMod>(&file_str) {
|
||||
let icon = read_icon_from_file(
|
||||
pack.metadata.as_ref().and_then(|x| x.icon.clone()),
|
||||
)?;
|
||||
|
||||
return_projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Inferred {
|
||||
title: Some(
|
||||
pack.metadata
|
||||
.as_ref()
|
||||
.and_then(|x| x.name.clone())
|
||||
.unwrap_or(pack.id),
|
||||
),
|
||||
description: pack
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|x| x.description.clone()),
|
||||
authors: pack
|
||||
.metadata
|
||||
.map(|x| {
|
||||
x.contributors
|
||||
.unwrap_or_default()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
version: Some(pack.version),
|
||||
icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(mut file) = zip.by_name("pack.mcmeta") {
|
||||
#[derive(Deserialize)]
|
||||
struct Pack {
|
||||
description: Option<String>,
|
||||
}
|
||||
|
||||
let mut file_str = String::new();
|
||||
if file.read_to_string(&mut file_str).is_ok() {
|
||||
if let Ok(pack) = serde_json::from_str::<Pack>(&file_str) {
|
||||
let icon =
|
||||
read_icon_from_file(Some("pack.png".to_string()))?;
|
||||
|
||||
return_projects.insert(
|
||||
path.clone(),
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Inferred {
|
||||
title: None,
|
||||
description: pack.description,
|
||||
authors: Vec::new(),
|
||||
version: None,
|
||||
icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return_projects.insert(
|
||||
path,
|
||||
Project {
|
||||
sha512: hash,
|
||||
disabled: false,
|
||||
metadata: ProjectMetadata::Unknown,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Ok(return_projects)
|
||||
}
|
||||
@@ -103,7 +103,7 @@ impl Default for WindowSize {
|
||||
}
|
||||
|
||||
/// Game initialization hooks
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Hooks {
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
@@ -113,13 +113,3 @@ pub struct Hooks {
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub post_exit: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Default for Hooks {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pre_launch: HashSet::<String>::new(),
|
||||
wrapper: None,
|
||||
post_exit: HashSet::<String>::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ pub async fn fetch_mirrors(
|
||||
let _permits = sem.acquire_many(permits).await.unwrap();
|
||||
let sem = Arc::new(Semaphore::new(permits.try_into().unwrap()));
|
||||
|
||||
future::select_ok(urls.into_iter().map(|url| {
|
||||
future::select_ok(urls.iter().map(|url| {
|
||||
let sha1 = sha1.map(String::from);
|
||||
let url = String::from(*url);
|
||||
let sem = Arc::clone(&sem);
|
||||
|
||||
@@ -185,11 +185,11 @@ pub fn get_all_jre_path() -> Result<HashSet<JavaVersion>, JREError> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[allow(dead_code)]
|
||||
const JAVA_BIN: &'static str = "java.exe";
|
||||
const JAVA_BIN: &str = "java.exe";
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[allow(dead_code)]
|
||||
const JAVA_BIN: &'static str = "java";
|
||||
const JAVA_BIN: &str = "java";
|
||||
|
||||
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
||||
// If no such path exists, or no such valid java at this path exists, returns None
|
||||
|
||||
@@ -31,11 +31,7 @@ pub fn os_rule(rule: &OsRule) -> bool {
|
||||
let mut rule_match = true;
|
||||
|
||||
if let Some(ref arch) = rule.arch {
|
||||
rule_match &= match arch.as_str() {
|
||||
"x86" => cfg!(any(target_arch = "x86", target_arch = "x86_64")),
|
||||
"arm" => cfg!(target_arch = "arm"),
|
||||
_ => true,
|
||||
};
|
||||
rule_match &= !matches!(arch.as_str(), "x86" | "arm");
|
||||
}
|
||||
|
||||
if let Some(name) = &rule.name {
|
||||
|
||||
@@ -202,7 +202,7 @@ impl ProfileInit {
|
||||
let filter = |it: &LoaderVersion| match version.as_str() {
|
||||
"latest" => true,
|
||||
"stable" => it.stable,
|
||||
id => it.id == String::from(id),
|
||||
id => it.id == *id,
|
||||
};
|
||||
|
||||
let loader_data = match loader {
|
||||
@@ -211,7 +211,7 @@ impl ProfileInit {
|
||||
_ => eyre::bail!("Could not get manifest for loader {loader}. This is a bug in the CLI!"),
|
||||
};
|
||||
|
||||
let ref loaders = loader_data.game_versions
|
||||
let loaders = &loader_data.game_versions
|
||||
.iter()
|
||||
.find(|it| it.id == game_version)
|
||||
.ok_or_else(|| eyre::eyre!("Modloader {loader} unsupported for Minecraft version {game_version}"))?
|
||||
|
||||
13
theseus_gui/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -4,29 +4,18 @@
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:vue/vue3-recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"prettier"
|
||||
],
|
||||
"extends": ["eslint:recommended", "plugin:vue/vue3-recommended", "prettier"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"vue",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["vue"],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"vue/no-v-html": "off",
|
||||
"comma-dangle": [
|
||||
"error",
|
||||
"only-multiline"
|
||||
],
|
||||
"comma-dangle": ["error", "only-multiline"],
|
||||
"vue/comment-directive": "off",
|
||||
"vue/multi-word-component-names": "off",
|
||||
"import/no-named-as-default": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,4 @@
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
}
|
||||
|
||||
6
theseus_gui/.vscode/extensions.json
vendored
@@ -1,7 +1,3 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"Vue.volar",
|
||||
"tauri-apps.tauri-vscode",
|
||||
"rust-lang.rust-analyzer"
|
||||
]
|
||||
"recommendations": ["Vue.volar", "tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"lint:js": "eslint --ext .js,.vue,.ts,.jsx,.tsx,.html,.vue .",
|
||||
"lint": "npm run lint:js && prettier --check .",
|
||||
"fix": "eslint --fix --ext .js,.vue,.ts,.jsx,.tsx,.html,.vue ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
@@ -25,7 +28,7 @@
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"prettier": "^2.8.4",
|
||||
"prettier": "^2.8.7",
|
||||
"sass": "^1.58.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-eslint": "^1.8.1"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 974 B After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 903 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 76 KiB |
@@ -27,9 +27,13 @@
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/favicon.ico"
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "com.tauri.dev",
|
||||
"identifier": "com.modrinth.theseus",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": null,
|
||||
@@ -65,4 +69,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,10 @@ import {
|
||||
ClientIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
Button,
|
||||
Avatar,
|
||||
} from 'omorphia'
|
||||
import { useTheming, useInstances } from '@/store/state'
|
||||
import { toggleTheme } from '@/helpers/theme'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -39,7 +37,9 @@ watch(theme, (newState) => {
|
||||
<RouterLink to="/" class="button-base nav-button"><ClientIcon /></RouterLink>
|
||||
<RouterLink to="/browse" class="button-base nav-button"> <SearchIcon /></RouterLink>
|
||||
<RouterLink to="/library" class="button-base nav-button"> <BookIcon /></RouterLink>
|
||||
<button color="primary" class="button-base primary nav-button" icon-only><PlusIcon /></button>
|
||||
<button color="primary" class="button-base primary nav-button" icon-only>
|
||||
<PlusIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings pages-list">
|
||||
@@ -194,7 +194,7 @@ watch(theme, (newState) => {
|
||||
.nav-button {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
padding: .75rem;
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--radius-md);
|
||||
|
||||
svg {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { default as PlayIcon } from './play.svg'
|
||||
export { default as OpenFolderIcon } from './folder-open.svg'
|
||||
export { default as BrowseIcon } from './folder-search.svg'
|
||||
export { default as BrowseIcon } from './folder-search.svg'
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
<script setup>
|
||||
import {ChevronLeftIcon, ChevronRightIcon} from 'omorphia'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from 'omorphia'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import News from '@/components/ui/News.vue'
|
||||
import {onMounted, onUnmounted, ref} from 'vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
instances: Array,
|
||||
news: Array,
|
||||
label: String,
|
||||
instances: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
news: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
canPaginate: Boolean,
|
||||
})
|
||||
const allowPagination = ref(false)
|
||||
@@ -16,9 +28,6 @@ const newsRow = ref(null)
|
||||
// Remove after state is populated with real data
|
||||
const shouldRenderNormalInstances = props.instances && props.instances?.length !== 0
|
||||
const shouldRenderNews = props.news && props.news?.length !== 0
|
||||
onUnmounted(() => {
|
||||
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
||||
})
|
||||
const handleLeftPage = () => {
|
||||
if (shouldRenderNormalInstances) modsRow.value.scrollLeft -= 170
|
||||
else if (shouldRenderNews) newsRow.value.scrollLeft -= 170
|
||||
@@ -32,10 +41,10 @@ const handleRightPage = () => {
|
||||
<div class="row">
|
||||
<div class="header">
|
||||
<p>{{ props.label }}</p>
|
||||
<hr>
|
||||
<hr />
|
||||
<div v-if="allowPagination" class="pagination">
|
||||
<ChevronLeftIcon @click="handleLeftPage"/>
|
||||
<ChevronRightIcon @click="handleRightPage"/>
|
||||
<ChevronLeftIcon @click="handleLeftPage" />
|
||||
<ChevronRightIcon @click="handleRightPage" />
|
||||
</div>
|
||||
</div>
|
||||
<section ref="modsRow" class="instances">
|
||||
@@ -129,4 +138,4 @@ const handleRightPage = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
<script setup>
|
||||
import {ChevronLeftIcon, ChevronRightIcon} from 'omorphia'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from 'omorphia'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import News from '@/components/ui/News.vue'
|
||||
import {onMounted, onUnmounted, ref} from 'vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
instances: Array,
|
||||
news: Array,
|
||||
label: String,
|
||||
instances: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
news: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
canPaginate: Boolean,
|
||||
})
|
||||
const allowPagination = ref(false)
|
||||
@@ -48,23 +61,23 @@ const handleRightPage = () => {
|
||||
<div class="row">
|
||||
<div class="header">
|
||||
<p>{{ props.label }}</p>
|
||||
<hr aria-hidden="true"/>
|
||||
<hr aria-hidden="true" />
|
||||
<div v-if="allowPagination" class="pagination">
|
||||
<ChevronLeftIcon @click="handleLeftPage"/>
|
||||
<ChevronRightIcon @click="handleRightPage"/>
|
||||
<ChevronLeftIcon @click="handleLeftPage" />
|
||||
<ChevronRightIcon @click="handleRightPage" />
|
||||
</div>
|
||||
</div>
|
||||
<section ref="modsRow" class="instances" v-if="shouldRenderNormalInstances">
|
||||
<section v-if="shouldRenderNormalInstances" ref="modsRow" class="instances">
|
||||
<Instance
|
||||
v-for="instance in props.instances"
|
||||
:key="instance.id"
|
||||
display="card"
|
||||
:instance="instance"
|
||||
class="row-instance"
|
||||
v-for="instance in props.instances"
|
||||
:key="instance.id"
|
||||
display="card"
|
||||
:instance="instance"
|
||||
class="row-instance"
|
||||
/>
|
||||
</section>
|
||||
<section ref="newsRow" class="news" v-else-if="shouldRenderNews">
|
||||
<News v-for="news in props.news" :key="news.id" :news="news"/>
|
||||
<section v-else-if="shouldRenderNews" ref="newsRow" class="news">
|
||||
<News v-for="actualNews in props.news" :key="actualNews.id" :news="actualNews" />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -171,4 +184,4 @@ const handleRightPage = () => {
|
||||
min-width: 12rem;
|
||||
max-width: 12rem;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -3,17 +3,32 @@ import { RouterLink } from 'vue-router'
|
||||
import { Card } from 'omorphia'
|
||||
import { PlayIcon } from '@/assets/icons'
|
||||
const props = defineProps({
|
||||
display: String,
|
||||
instance: Object,
|
||||
display: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
instance: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RouterLink v-if="display === 'list'" class="instance-list-item" :to="`/instance/${props.instance.id}`">{{
|
||||
props.instance.name
|
||||
}}</RouterLink>
|
||||
<Card class="instance-card-item" v-else-if="display === 'card'" @click="this.$router.push(`/instance/${props.instance.id}`)">
|
||||
<RouterLink
|
||||
v-if="display === 'list'"
|
||||
class="instance-list-item"
|
||||
:to="`/instance/${props.instance.id}`"
|
||||
>{{ props.instance.name }}</RouterLink
|
||||
>
|
||||
<Card
|
||||
v-else-if="display === 'card'"
|
||||
class="instance-card-item"
|
||||
@click="$router.push(`/instance/${props.instance.id}`)"
|
||||
>
|
||||
<img :src="props.instance.img" alt="Trending mod card" />
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.name }}</p>
|
||||
@@ -113,4 +128,4 @@ const props = defineProps({
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<script setup>
|
||||
import { Card, ChevronRightIcon } from 'omorphia'
|
||||
const props = defineProps({
|
||||
news: Object,
|
||||
news: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -83,4 +88,4 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -14,9 +14,9 @@ const popularInstances = instances.instances.filter((i) => i.downloads > 50 || i
|
||||
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<RowDisplay label="Jump back in" :instances="recentInstances" :canPaginate="false" />
|
||||
<RowDisplay label="Popular packs" :instances="popularInstances" :canPaginate="true" />
|
||||
<RowDisplay label="News & updates" :news="news.news" :canPaginate="true" />
|
||||
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
||||
<RowDisplay label="Popular packs" :instances="popularInstances" :can-paginate="true" />
|
||||
<RowDisplay label="News & updates" :news="news.news" :can-paginate="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<div class="instance-container">
|
||||
<div class="side-cards">
|
||||
<Card class="instance-card">
|
||||
<Avatar size="lg" :src="getInstance(instances).img"/>
|
||||
<Avatar size="lg" :src="getInstance(instances).img" />
|
||||
<div class="instance-info">
|
||||
<h2 class="name">{{getInstance(instances).name}}</h2>
|
||||
<h2 class="name">{{ getInstance(instances).name }}</h2>
|
||||
Fabric {{ getInstance(instances).version }}
|
||||
</div>
|
||||
<span class="button-group">
|
||||
@@ -18,40 +18,40 @@
|
||||
</span>
|
||||
</Card>
|
||||
<div class="pages-list">
|
||||
<RouterLink :to="`/instance/${this.$route.params.id}/`" class="btn">
|
||||
<BoxIcon/>
|
||||
<RouterLink :to="`/instance/${$route.params.id}/`" class="btn">
|
||||
<BoxIcon />
|
||||
Mods
|
||||
</RouterLink>
|
||||
<RouterLink :to="`/instance/${this.$route.params.id}/options`" class="btn">
|
||||
<SettingsIcon/>
|
||||
<RouterLink :to="`/instance/${$route.params.id}/options`" class="btn">
|
||||
<SettingsIcon />
|
||||
Options
|
||||
</RouterLink>
|
||||
<RouterLink :to="`/instance/${this.$route.params.id}/logs`" class="btn">
|
||||
<FileIcon/>
|
||||
<RouterLink :to="`/instance/${$route.params.id}/logs`" class="btn">
|
||||
<FileIcon />
|
||||
Logs
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Promotion />
|
||||
<router-view/>
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {BoxIcon, SettingsIcon, FileIcon, Button, Avatar, Card, Promotion} from 'omorphia'
|
||||
import {PlayIcon, OpenFolderIcon} from "@/assets/icons";
|
||||
import {useInstances} from '@/store/state'
|
||||
import { BoxIcon, SettingsIcon, FileIcon, Button, Avatar, Card, Promotion } from 'omorphia'
|
||||
import { PlayIcon, OpenFolderIcon } from '@/assets/icons'
|
||||
import { useInstances } from '@/store/state'
|
||||
|
||||
const instances = useInstances();
|
||||
instances.fetchInstances();
|
||||
const instances = useInstances()
|
||||
instances.fetchInstances()
|
||||
</script>
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
getInstance(instances) {
|
||||
return instances.instances.find((i) => i.id === parseInt(this.$route.params.id));
|
||||
}
|
||||
return instances.instances.find((i) => i.id === parseInt(this.$route.params.id))
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -266,4 +266,4 @@ Button {
|
||||
height: 1px;
|
||||
margin: var(--gap-xl) 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -4,21 +4,21 @@
|
||||
<DropdownSelect :options="['logs/latest.log']" />
|
||||
<div class="button-group">
|
||||
<Button>
|
||||
<ClipboardCopyIcon/>
|
||||
<ClipboardCopyIcon />
|
||||
Copy
|
||||
</Button>
|
||||
<Button color="primary">
|
||||
<SendIcon/>
|
||||
<SendIcon />
|
||||
Share
|
||||
</Button>
|
||||
<Button color="danger">
|
||||
<TrashIcon/>
|
||||
<TrashIcon />
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-text">
|
||||
<div v-for="line in fileContents.value.split('\n')"> {{ line }} </div>
|
||||
<div v-for="(line, index) in fileContents.value.split('\n')" :key="index">{{ line }}</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -27,66 +27,66 @@
|
||||
import { Card, Button, TrashIcon, SendIcon, ClipboardCopyIcon, DropdownSelect } from 'omorphia'
|
||||
</script>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fileContents: {
|
||||
value: "'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial left the game\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping server\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving players\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving worlds\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n" +
|
||||
"[22:13:06] [Render thread/INFO]: Stopping worker threads\n" +
|
||||
"[22:13:07] [Render thread/INFO]: Stopping!\n" +
|
||||
"[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n" +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial left the game\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping server\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving players\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving worlds\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n" +
|
||||
"[22:13:06] [Render thread/INFO]: Stopping worker threads\n" +
|
||||
"[22:13:07] [Render thread/INFO]: Stopping!\n" +
|
||||
"[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n" +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n" +
|
||||
"[22:13:02] [Server thread/INFO]: venashial left the game\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Stopping server\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving players\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving worlds\n" +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n" +
|
||||
"[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n" +
|
||||
"[22:13:06] [Render thread/INFO]: Stopping worker threads\n" +
|
||||
"[22:13:07] [Render thread/INFO]: Stopping!\n" +
|
||||
"[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence..."
|
||||
}
|
||||
};
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
fileContents: {
|
||||
value:
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n' +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...\n' +
|
||||
"'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:02] [Server thread/INFO]: venashial lost connection: Disconnected\n' +
|
||||
'[22:13:02] [Server thread/INFO]: venashial left the game\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping singleplayer server as player logged out\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Stopping server\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving players\n' +
|
||||
'[22:13:02] [Server thread/INFO]: Saving worlds\n' +
|
||||
"[22:13:02] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:overworld\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_nether\n" +
|
||||
"[22:13:05] [Server thread/INFO]: Saving chunks for level 'ServerLevel[New World]'/minecraft:the_end\n" +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (New World): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM-1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage (DIM1): All chunks are saved\n' +
|
||||
'[22:13:05] [Server thread/INFO]: ThreadedAnvilChunkStorage: All dimensions are saved\n' +
|
||||
'[22:13:06] [Render thread/INFO]: Stopping worker threads\n' +
|
||||
'[22:13:07] [Render thread/INFO]: Stopping!\n' +
|
||||
'[22:13:07] [CraftPresence-ShutDown-Handler/INFO]: Shutting down CraftPresence...',
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -118,4 +118,4 @@ import { Card, Button, TrashIcon, SendIcon, ClipboardCopyIcon, DropdownSelect }
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -2,63 +2,59 @@
|
||||
<Card class="mod-card">
|
||||
<div class="card-row">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search Mods"
|
||||
v-model="searchFilter"
|
||||
/>
|
||||
<SearchIcon />
|
||||
<input v-model="searchFilter" type="text" placeholder="Search Mods" />
|
||||
</div>
|
||||
<span class="manage">
|
||||
<span class="text-combo">
|
||||
Sort By
|
||||
<DropdownSelect :options="['Name', 'Version', 'Author']" v-model="sortFilter" default-value="Name" class="dropdown"/>
|
||||
<DropdownSelect
|
||||
v-model="sortFilter"
|
||||
:options="['Name', 'Version', 'Author']"
|
||||
default-value="Name"
|
||||
class="dropdown"
|
||||
/>
|
||||
</span>
|
||||
<Button color="primary">
|
||||
<PlusIcon />
|
||||
Add Mods
|
||||
</Button>
|
||||
<PlusIcon />
|
||||
Add Mods
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<div class="table-row table-head">
|
||||
<div class="table-cell table-text">
|
||||
<Button color="success" iconOnly>
|
||||
<Button color="success" icon-only>
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="table-cell table-text name-cell"> Name </div>
|
||||
<div class="table-cell table-text"> Version </div>
|
||||
<div class="table-cell table-text"> Author </div>
|
||||
<div class="table-cell table-text"> Actions </div>
|
||||
<div class="table-cell table-text name-cell">Name</div>
|
||||
<div class="table-cell table-text">Version</div>
|
||||
<div class="table-cell table-text">Author</div>
|
||||
<div class="table-cell table-text">Actions</div>
|
||||
</div>
|
||||
<div class="table-row" v-for="mod in search" :key="mod.name">
|
||||
<div v-for="mod in search" :key="mod.name" class="table-row">
|
||||
<div class="table-cell table-text">
|
||||
<Button v-if="mod.outdated" iconOnly>
|
||||
<Button v-if="mod.outdated" icon-only>
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
<Button v-else disabled iconOnly>
|
||||
<CheckCircleIcon/>
|
||||
<Button v-else disabled icon-only>
|
||||
<CheckCircleIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="table-cell table-text name-cell">
|
||||
<span class="mod-text">
|
||||
<Avatar :src="mod.icon"/>
|
||||
<Avatar :src="mod.icon" />
|
||||
{{ mod.name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="table-cell table-text"> {{ mod.version }} </div>
|
||||
<div class="table-cell table-text"> {{ mod.author }} </div>
|
||||
<div class="table-cell table-text">{{ mod.version }}</div>
|
||||
<div class="table-cell table-text">{{ mod.author }}</div>
|
||||
<div class="table-cell table-text manage">
|
||||
<Button iconOnly>
|
||||
<Button icon-only>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="switch stylized-toggle"
|
||||
id="switch-1"
|
||||
checked
|
||||
/>
|
||||
<input id="switch-1" type="checkbox" class="switch stylized-toggle" checked />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,55 +63,66 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Mods",
|
||||
name: 'Mods',
|
||||
data() {
|
||||
return {
|
||||
searchFilter: "",
|
||||
sortFilter: "",
|
||||
searchFilter: '',
|
||||
sortFilter: '',
|
||||
mods: [
|
||||
{
|
||||
name: "Fabric API",
|
||||
icon: "https://cdn.modrinth.com/data/P7dR8mSH/icon.png",
|
||||
version: "0.76.0+1.19.4",
|
||||
author: "modmuss50",
|
||||
description: "Lightweight and modular API providing common hooks and intercompatibility measures utilized by mods using the Fabric toolchain.",
|
||||
outdated: true
|
||||
name: 'Fabric API',
|
||||
icon: 'https://cdn.modrinth.com/data/P7dR8mSH/icon.png',
|
||||
version: '0.76.0+1.19.4',
|
||||
author: 'modmuss50',
|
||||
description:
|
||||
'Lightweight and modular API providing common hooks and intercompatibility measures utilized by mods using the Fabric toolchain.',
|
||||
outdated: true,
|
||||
},
|
||||
{
|
||||
name: "Spirit",
|
||||
icon: "https://cdn.modrinth.com/data/b1LdOZlE/465598dc5d89f67fb8f8de6def21240fa35e3a54.png",
|
||||
version: "2.2.4",
|
||||
author: "CodexAdrian",
|
||||
description: "Create your own configurable mob spawner!",
|
||||
outdated: true
|
||||
name: 'Spirit',
|
||||
icon: 'https://cdn.modrinth.com/data/b1LdOZlE/465598dc5d89f67fb8f8de6def21240fa35e3a54.png',
|
||||
version: '2.2.4',
|
||||
author: 'CodexAdrian',
|
||||
description: 'Create your own configurable mob spawner!',
|
||||
outdated: true,
|
||||
},
|
||||
{
|
||||
name: "Botarium",
|
||||
icon: "https://cdn.modrinth.com/data/2u6LRnMa/98b286b0d541ad4f9409e0af3df82ad09403f179.gif",
|
||||
version: "2.0.5",
|
||||
author: "CodexAdrian",
|
||||
description: "A crossplatform API for devs that makes transfer and storage of items, fluids and energy easier, as well as some other helpful things",
|
||||
outdated: true
|
||||
name: 'Botarium',
|
||||
icon: 'https://cdn.modrinth.com/data/2u6LRnMa/98b286b0d541ad4f9409e0af3df82ad09403f179.gif',
|
||||
version: '2.0.5',
|
||||
author: 'CodexAdrian',
|
||||
description:
|
||||
'A crossplatform API for devs that makes transfer and storage of items, fluids and energy easier, as well as some other helpful things',
|
||||
outdated: true,
|
||||
},
|
||||
{
|
||||
name: "Tempad",
|
||||
icon: "https://cdn.modrinth.com/data/gKNwt7xu/icon.gif",
|
||||
version: "2.2.4",
|
||||
author: "CodexAdrian",
|
||||
description: "Create a portal to anywhere from anywhere",
|
||||
outdated: false
|
||||
name: 'Tempad',
|
||||
icon: 'https://cdn.modrinth.com/data/gKNwt7xu/icon.gif',
|
||||
version: '2.2.4',
|
||||
author: 'CodexAdrian',
|
||||
description: 'Create a portal to anywhere from anywhere',
|
||||
outdated: false,
|
||||
},
|
||||
{
|
||||
name: "Sodium",
|
||||
icon: "https://cdn.modrinth.com/data/AANobbMI/icon.png",
|
||||
version: "0.4.10",
|
||||
author: "jellysquid3",
|
||||
description: "Modern rendering engine and client-side optimization mod for Minecraft",
|
||||
outdated: false
|
||||
}
|
||||
]
|
||||
name: 'Sodium',
|
||||
icon: 'https://cdn.modrinth.com/data/AANobbMI/icon.png',
|
||||
version: '0.4.10',
|
||||
author: 'jellysquid3',
|
||||
description: 'Modern rendering engine and client-side optimization mod for Minecraft',
|
||||
outdated: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
search() {
|
||||
const filtered = this.mods.filter((mod) => {
|
||||
return mod.name.toLowerCase().includes(this.searchFilter.toLowerCase())
|
||||
})
|
||||
|
||||
return this.updateSort(filtered, this.sortFilter)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateSort(projects, sort) {
|
||||
switch (sort) {
|
||||
@@ -148,23 +155,24 @@ export default {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
search() {
|
||||
const filtered = this.mods.filter((mod) => {
|
||||
return mod.name.toLowerCase().includes(this.searchFilter.toLowerCase())
|
||||
})
|
||||
|
||||
return this.updateSort(filtered, this.sortFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script setup>
|
||||
import { Avatar, Button, TrashIcon, PlusIcon, Card, CheckCircleIcon, SearchIcon, UpdatedIcon, DropdownSelect } from 'omorphia'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
Card,
|
||||
CheckCircleIcon,
|
||||
SearchIcon,
|
||||
UpdatedIcon,
|
||||
DropdownSelect,
|
||||
} from 'omorphia'
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -244,4 +252,4 @@ import { Avatar, Button, TrashIcon, PlusIcon, Card, CheckCircleIcon, SearchIcon,
|
||||
.dropdown {
|
||||
width: 7rem !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,172 +1,132 @@
|
||||
<template>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title"> Java </h2>
|
||||
<h2 class="settings-title">Java</h2>
|
||||
<div class="settings-group">
|
||||
<h3>Installation</h3>
|
||||
<input
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
|
||||
ref="javaPath"
|
||||
v-model="javaPath"
|
||||
ref="javaPath"
|
||||
v-model="javaPath"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home"
|
||||
/>
|
||||
<span class="installation-buttons">
|
||||
<Button @click="saveJavaPath">
|
||||
<SearchIcon/>
|
||||
Auto Detect
|
||||
</Button>
|
||||
<Button @click="saveJavaPath">
|
||||
<BrowseIcon/>
|
||||
Browse
|
||||
</Button>
|
||||
<Button @click="saveJavaPath">
|
||||
<PlayIcon/>
|
||||
Test
|
||||
</Button>
|
||||
</span>
|
||||
<Button @click="saveJavaPath">
|
||||
<SearchIcon />
|
||||
Auto Detect
|
||||
</Button>
|
||||
<Button @click="saveJavaPath">
|
||||
<BrowseIcon />
|
||||
Browse
|
||||
</Button>
|
||||
<Button @click="saveJavaPath">
|
||||
<PlayIcon />
|
||||
Test
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<hr class="card-divider">
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Arguments</h3>
|
||||
<input
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
ref="javaArgs"
|
||||
v-model="javaArgs"
|
||||
/>
|
||||
<input ref="javaArgs" v-model="javaArgs" type="text" class="input installation-input" />
|
||||
</div>
|
||||
<hr class="card-divider">
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Minimum Memory
|
||||
<Slider
|
||||
v-model="javaMemory"
|
||||
:min="1024"
|
||||
:max="8192"
|
||||
:step="1024"
|
||||
/>
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum Memory
|
||||
<Slider
|
||||
v-model="javaMemory"
|
||||
:min="1024"
|
||||
:max="8192"
|
||||
:step="1024"
|
||||
/>
|
||||
</span>
|
||||
Minimum Memory
|
||||
<Slider v-model="javaMemory" :min="1024" :max="8192" :step="1024" />
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum Memory
|
||||
<Slider v-model="javaMemory" :min="1024" :max="8192" :step="1024" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title"> Window </h2>
|
||||
<h2 class="settings-title">Window</h2>
|
||||
<div class="settings-group">
|
||||
<div class="settings-group">
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Width
|
||||
<Slider
|
||||
v-model="javaMemory"
|
||||
:min="1024"
|
||||
:max="8192"
|
||||
:step="1024"
|
||||
/>
|
||||
<Slider v-model="javaMemory" :min="1024" :max="8192" :step="1024" />
|
||||
</span>
|
||||
<span class="slider">
|
||||
Height
|
||||
<Slider
|
||||
v-model="javaMemory"
|
||||
:min="1024"
|
||||
:max="8192"
|
||||
:step="1024"
|
||||
/>
|
||||
<Slider v-model="javaMemory" :min="1024" :max="8192" :step="1024" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Start in Fullscreen
|
||||
<input
|
||||
type="checkbox"
|
||||
id="fullscreen"
|
||||
name="fullscreen"
|
||||
v-model="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
id="fullscreen"
|
||||
v-model="fullscreen"
|
||||
type="checkbox"
|
||||
name="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="card-divider">
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Console</h3>
|
||||
<div class="toggle-setting">
|
||||
Show console while game is running
|
||||
<input
|
||||
type="checkbox"
|
||||
id="fullscreen"
|
||||
name="fullscreen"
|
||||
v-model="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
id="fullscreen"
|
||||
v-model="fullscreen"
|
||||
type="checkbox"
|
||||
name="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Close console when game quits
|
||||
<input
|
||||
type="checkbox"
|
||||
id="fullscreen"
|
||||
name="fullscreen"
|
||||
v-model="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
id="fullscreen"
|
||||
v-model="fullscreen"
|
||||
type="checkbox"
|
||||
name="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Show console when game crashes
|
||||
<input
|
||||
type="checkbox"
|
||||
id="fullscreen"
|
||||
name="fullscreen"
|
||||
v-model="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
id="fullscreen"
|
||||
v-model="fullscreen"
|
||||
type="checkbox"
|
||||
name="fullscreen"
|
||||
class="switch stylized-toggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title"> Commands </h2>
|
||||
<h2 class="settings-title">Commands</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Pre Launch
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
ref="javaArgs"
|
||||
v-model="javaArgs"
|
||||
/>
|
||||
<input ref="javaArgs" v-model="javaArgs" type="text" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Wrapper
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
ref="javaArgs"
|
||||
v-model="javaArgs"
|
||||
/>
|
||||
<input ref="javaArgs" v-model="javaArgs" type="text" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Post Launch
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
ref="javaArgs"
|
||||
v-model="javaArgs"
|
||||
/>
|
||||
<input ref="javaArgs" v-model="javaArgs" type="text" class="input" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {Card, Button, SearchIcon, Slider } from 'omorphia'
|
||||
import {BrowseIcon, PlayIcon} from "@/assets/icons";
|
||||
import { Card, Button, SearchIcon, Slider } from 'omorphia'
|
||||
import { BrowseIcon, PlayIcon } from '@/assets/icons'
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -177,7 +137,7 @@ import {BrowseIcon, PlayIcon} from "@/assets/icons";
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
color: var(--color-contrast)
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
@@ -195,9 +155,8 @@ import {BrowseIcon, PlayIcon} from "@/assets/icons";
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
}
|
||||
|
||||
.sliders {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Index from './Index.vue'
|
||||
import Mods from "./Mods.vue";
|
||||
import Options from "./Options.vue";
|
||||
import Logs from "./Logs.vue";
|
||||
import Mods from './Mods.vue'
|
||||
import Options from './Options.vue'
|
||||
import Logs from './Logs.vue'
|
||||
|
||||
export { Index, Mods, Options, Logs }
|
||||
export { Index, Mods, Options, Logs }
|
||||
|
||||
@@ -45,9 +45,9 @@ export default new createRouter({
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'Mods',
|
||||
component: Instance.Mods,
|
||||
path: '',
|
||||
name: 'Mods',
|
||||
component: Instance.Mods,
|
||||
},
|
||||
{
|
||||
path: 'options',
|
||||
@@ -58,9 +58,9 @@ export default new createRouter({
|
||||
path: 'logs',
|
||||
name: 'Logs',
|
||||
component: Instance.Logs,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
linkActiveClass: 'router-link-active',
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
|
||||
@@ -2,7 +2,8 @@ import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import alias from '@rollup/plugin-alias'
|
||||
import { resolve } from 'path'
|
||||
import svgLoader from "vite-svg-loader";
|
||||
import eslint from 'vite-plugin-eslint'
|
||||
import svgLoader from 'vite-svg-loader'
|
||||
|
||||
const projectRootDir = resolve(__dirname)
|
||||
|
||||
@@ -18,6 +19,7 @@ export default defineConfig({
|
||||
},
|
||||
],
|
||||
}),
|
||||
eslint(),
|
||||
svgLoader({
|
||||
svgoConfig: {
|
||||
plugins: [
|
||||
|
||||
@@ -1315,7 +1315,7 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
prettier@^2.8.4:
|
||||
prettier@^2.8.7:
|
||||
version "2.8.7"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
|
||||
integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==
|
||||
|
||||