Compare commits
7 Commits
v0.8.9
...
shared-pro
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e147cc9db | ||
|
|
d2834ce720 | ||
|
|
f9beea8ef2 | ||
|
|
1018d05e36 | ||
|
|
719aded698 | ||
|
|
6b99f82cea | ||
|
|
bdde054036 |
@@ -1,3 +1,3 @@
|
||||
# Windows has stack overflows when calling from Tauri, so we increase compiler size
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
59
.github/ISSUE_TEMPLATE/1-app-bug.yml
vendored
59
.github/ISSUE_TEMPLATE/1-app-bug.yml
vendored
@@ -1,59 +0,0 @@
|
||||
name: 🎮 Modrinth App bug
|
||||
description: Report an issue in the Modrinth Launcher.
|
||||
labels: [bug, app]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- label: I have ensured my Modrinth App installation is up to date
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: What version of the Modrinth App are you using?
|
||||
description: Find this in ⚙️ Settings (bottom right) -> About -> App version.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: oses
|
||||
attributes:
|
||||
label: What operating systems are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Windows
|
||||
- MacOS
|
||||
- Linux
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
52
.github/ISSUE_TEMPLATE/2-web-bug.yml
vendored
52
.github/ISSUE_TEMPLATE/2-web-bug.yml
vendored
@@ -1,52 +0,0 @@
|
||||
name: 🌐 Website bug (modrinth.com)
|
||||
description: Report an issue on the Modrinth website.
|
||||
labels: [bug, frontend]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers are you seeing the problem on?
|
||||
multiple: true
|
||||
options:
|
||||
- Chrome (including Arc, Brave, Opera, Vivaldi)
|
||||
- Microsoft Edge
|
||||
- Firefox
|
||||
- Safari
|
||||
- Other (please specify)
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,19 +1,11 @@
|
||||
name: 🛠️ API issue (api.modrinth.com)
|
||||
description: Report an issue regarding the Modrinth API.
|
||||
labels: [bug, api]
|
||||
name: Bug report
|
||||
description: Create a report to help us improve Modrinth
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate problems
|
||||
required: true
|
||||
- label: I have tried resolving the issue using the [support portal](https://support.modrinth.com)
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is. Include screenshots if applicable.
|
||||
description: A clear and concise description of what the bug is.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
@@ -33,9 +25,13 @@ body:
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: System information
|
||||
description: Add any information about what OS you are on (like Windows or Mac), and what version of the app you are using.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
description: Add any other context about the problem here. This might include logs, screenshots, etc. The more the merrier!
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
20
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,14 +1,16 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 🫶 Support Portal
|
||||
about: Get support using through our portal.
|
||||
url: https://support.modrinth.com
|
||||
- name: 💬 Chat
|
||||
about: Join our Discord server to chat about Modrinth.
|
||||
- name: Discord
|
||||
about: Ask questions on our Discord Server.
|
||||
url: https://discord.modrinth.com
|
||||
- name: 🛣️ Roadmap
|
||||
- name: Roadmap
|
||||
about: View our Roadmap. Please do not open issues for items on our roadmap.
|
||||
url: https://roadmap.modrinth.com
|
||||
- name: 📚 Documentation
|
||||
about: Useful documentation about Modrinth's API
|
||||
url: https://docs.modrinth.com
|
||||
- name: Discussions (Feature requests)
|
||||
about: |
|
||||
Please use Issues for reporting bugs and suggesting enhancements to existing features.
|
||||
Suggestions for new features should be made as a Discussion.
|
||||
url: https://github.com/orgs/modrinth/discussions/categories/feature-requests
|
||||
- name: Documentation
|
||||
about: Useful documentation about Modrinth, its API, and how you can contribute.
|
||||
url: https://docs.modrinth.com
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest an idea
|
||||
name: Enhancement
|
||||
description: Suggest an enhancement for an existing Modrinth feature
|
||||
labels: [enhancement]
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Please confirm the following.
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
|
||||
required: true
|
||||
- label: I have checked that this feature request is not on our [roadmap](https://roadmap.modrinth.com)
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: projects
|
||||
attributes:
|
||||
label: What parts of Modrinth is your feature request related too?
|
||||
multiple: true
|
||||
options:
|
||||
- App
|
||||
- Website
|
||||
- API
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your suggested feature related to a problem? Please describe.
|
||||
label: Is your suggested enhancement related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
@@ -43,4 +25,4 @@ body:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the suggested enhancement here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
BIN
.github/assets/app_cover.png
vendored
BIN
.github/assets/app_cover.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
BIN
.github/assets/monorepo_cover.png
vendored
BIN
.github/assets/monorepo_cover.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 417 KiB |
BIN
.github/assets/web_cover.png
vendored
BIN
.github/assets/web_cover.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
155
.github/workflows/app-release.yml
vendored
155
.github/workflows/app-release.yml
vendored
@@ -1,155 +0,0 @@
|
||||
name: 'Modrinth App build'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- .github/workflows/app-release.yml
|
||||
- 'apps/app/**'
|
||||
- 'apps/app-frontend/**'
|
||||
- 'packages/app-lib/**'
|
||||
- 'packages/app-macros/**'
|
||||
- 'packages/assets/**'
|
||||
- 'packages/ui/**'
|
||||
- 'packages/utils/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Rust setup (mac)
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
targets: aarch64-apple-darwin, x86_64-apple-darwin
|
||||
|
||||
- name: Rust setup
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Setup rust cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
target/**
|
||||
!target/*/release/bundle/*/*.dmg
|
||||
!target/*/release/bundle/*/*.app.tar.gz
|
||||
!target/*/release/bundle/*/*.app.tar.gz.sig
|
||||
!target/release/bundle/*/*.dmg
|
||||
!target/release/bundle/*/*.app.tar.gz
|
||||
!target/release/bundle/*/*.app.tar.gz.sig
|
||||
|
||||
!target/release/bundle/*/*.AppImage
|
||||
!target/release/bundle/*/*.AppImage.tar.gz
|
||||
!target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
!target/release/bundle/*/*.deb
|
||||
!target/release/bundle/*/*.rpm
|
||||
|
||||
!target/release/bundle/msi/*.msi
|
||||
!target/release/bundle/msi/*.msi.zip
|
||||
!target/release/bundle/msi/*.msi.zip.sig
|
||||
|
||||
!target/release/bundle/nsis/*.exe
|
||||
!target/release/bundle/nsis/*.nsis.zip
|
||||
!target/release/bundle/nsis/*.nsis.zip.sig
|
||||
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-target-
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config "tauri-release.conf.json"
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: build app
|
||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
||||
id: build_os
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: |
|
||||
target/*/release/bundle/*/*.dmg
|
||||
target/*/release/bundle/*/*.app.tar.gz
|
||||
target/*/release/bundle/*/*.app.tar.gz.sig
|
||||
target/release/bundle/*/*.dmg
|
||||
target/release/bundle/*/*.app.tar.gz
|
||||
target/release/bundle/*/*.app.tar.gz.sig
|
||||
|
||||
target/release/bundle/*/*.AppImage
|
||||
target/release/bundle/*/*.AppImage.tar.gz
|
||||
target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
target/release/bundle/*/*.deb
|
||||
target/release/bundle/*/*.rpm
|
||||
|
||||
target/release/bundle/msi/*.msi
|
||||
target/release/bundle/msi/*.msi.zip
|
||||
target/release/bundle/msi/*.msi.zip.sig
|
||||
|
||||
target/release/bundle/nsis/*.exe
|
||||
target/release/bundle/nsis/*.nsis.zip
|
||||
target/release/bundle/nsis/*.nsis.zip.sig
|
||||
70
.github/workflows/ci.yml
vendored
70
.github/workflows/ci.yml
vendored
@@ -1,70 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
merge_group:
|
||||
types: [ checks_requested ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build, Test, and Lint
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Cache turbo build setup
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .turbo
|
||||
key: ${{ runner.os }}-turbo-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-turbo-
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Setup Node.JS environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
|
||||
- name: Lint
|
||||
run: pnpm lint
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
44
.github/workflows/cli-build.yml
vendored
Normal file
44
.github/workflows/cli-build.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
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:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Rust setup
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- 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
|
||||
28
.github/workflows/frontend-pages.yml
vendored
28
.github/workflows/frontend-pages.yml
vendored
@@ -1,28 +0,0 @@
|
||||
name: Deploy frontend
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
wait:
|
||||
if: github.repository_owner == 'modrinth'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
deployments: write
|
||||
steps:
|
||||
- name: Cloudflare Pages deployment
|
||||
uses: WalshyDev/cf-pages-await@v1
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: '9ddae624c98677d68d93df6e524a6061'
|
||||
project: 'frontend'
|
||||
githubToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
commitHash: ${{ steps.push-changes.outputs.commit-hash }}
|
||||
- name: Purge cache
|
||||
if: github.ref == 'refs/heads/prod'
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer ${{ secrets.CF_API_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data '{"hosts": ["modrinth.com", "www.modrinth.com"]}' \
|
||||
https://api.cloudflare.com/client/v4/zones/e39df17b9c4ef44cbce2646346ee6d33/purge_cache
|
||||
42
.github/workflows/gui-build.yml
vendored
Normal file
42
.github/workflows/gui-build.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
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: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Run Lint
|
||||
run: pnpm lint
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
109
.github/workflows/tauri-build.yml
vendored
Normal file
109
.github/workflows/tauri-build.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: 'Tauri GUI Build'
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
jobs:
|
||||
test-tauri:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest, ubuntu-20.04, ubuntu-22.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./theseus_gui
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Rust setup (mac)
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
targets: aarch64-apple-darwin
|
||||
|
||||
- name: Rust setup
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rust cache
|
||||
uses: swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libselinux1
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os_mac
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
args: --target universal-apple-darwin
|
||||
|
||||
- name: build app
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v3
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: "${{ join(fromJSON(steps.build_os_mac.outputs.artifactPaths), '\n') }}"
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: "${{ join(fromJSON(steps.build_os.outputs.artifactPaths), '\n') }}"
|
||||
161
.gitignore
vendored
161
.gitignore
vendored
@@ -1,57 +1,114 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
dist
|
||||
tmp
|
||||
/out-tsc
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
/target
|
||||
node_modules/
|
||||
.svelte-kit/
|
||||
theseus_gui/build/
|
||||
theseus_gui/generated/
|
||||
WixTools
|
||||
.direnv/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
.pnpm-debug.log
|
||||
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
minecraft
|
||||
config
|
||||
|
||||
# frontend generated files
|
||||
apps/frontend/src/generated
|
||||
[#]*[#]
|
||||
|
||||
.turbo
|
||||
target
|
||||
generated
|
||||
.env
|
||||
# TEMPORARY: ignore my test instance and metadata
|
||||
theseus_cli/foo
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Intellij+all Patch ###
|
||||
# Ignore everything but code style settings and run configurations
|
||||
# that are supposed to be shared within teams.
|
||||
|
||||
.idea/*
|
||||
|
||||
!.idea/codeStyles
|
||||
!.idea/runConfigurations
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
theseus.iml
|
||||
|
||||
# app testing dir
|
||||
app-playground-data/*
|
||||
|
||||
13
.vscode/extensions.json
vendored
13
.vscode/extensions.json
vendored
@@ -1,3 +1,12 @@
|
||||
{
|
||||
"recommendations": ["esbenp.prettier-vscode", "Vue.volar", "rust-lang.rust-analyzer"]
|
||||
}
|
||||
"recommendations": [
|
||||
"tauri-apps.tauri-vscode",
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
"stylelint.vscode-stylelint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"pivaszbs.svelte-autoimport",
|
||||
"svelte.svelte-vscode",
|
||||
"ardenivanov.svelte-intellisense"
|
||||
]
|
||||
}
|
||||
160
.vscode/launch.json
vendored
Normal file
160
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in library 'theseus'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=theseus"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_cli'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_cli",
|
||||
"--package=theseus_cli"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_cli",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_cli'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_cli",
|
||||
"--package=theseus_cli"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_cli",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_playground'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_playground",
|
||||
"--package=theseus_playground"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_playground",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_playground'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_playground",
|
||||
"--package=theseus_playground"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_playground",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'theseus_gui'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=theseus_gui",
|
||||
"--package=theseus_gui"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_gui",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'theseus_gui'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=theseus_gui",
|
||||
"--package=theseus_gui"
|
||||
],
|
||||
"filter": {
|
||||
"name": "theseus_gui",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Tauri Development Debug",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path=./theseus_gui/src-tauri/Cargo.toml",
|
||||
"--no-default-features"
|
||||
]
|
||||
},
|
||||
"preLaunchTask": "ui:dev"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Tauri Production Debug",
|
||||
"cargo": {
|
||||
"args": ["build", "--release", "--manifest-path=.theseus_gui/src-tauri/Cargo.toml"]
|
||||
},
|
||||
"preLaunchTask": "ui:build"
|
||||
}
|
||||
]
|
||||
}
|
||||
69
.vscode/settings.json
vendored
69
.vscode/settings.json
vendored
@@ -1,13 +1,60 @@
|
||||
{
|
||||
"prettier.endOfLine": "lf",
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
"cssVariables.lookupFiles": [
|
||||
"**/*.postcss",
|
||||
"**/node_modules/omorphia/**/*.postcss"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
||||
"cssVariables.blacklistFolders": [
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/.git",
|
||||
"**/bower_components",
|
||||
"**/tmp",
|
||||
"**/dist",
|
||||
"**/tests"
|
||||
],
|
||||
"gitlens.showWelcomeOnInstall": false,
|
||||
"gitlens.showWhatsNewAfterUpgrades": false,
|
||||
"gitlens.plusFeatures.enabled": false,
|
||||
"gitlens.currentLine.enabled": false,
|
||||
"gitlens.currentLine.pullRequests.enabled": false,
|
||||
"gitlens.currentLine.scrollable": true,
|
||||
"gitlens.codeLens.enabled": false,
|
||||
"gitlens.hovers.enabled": false,
|
||||
"CSSNavigation.activeCSSFileExtensions": [
|
||||
"css",
|
||||
"postcss"
|
||||
],
|
||||
"CSSNavigation.activeHTMLFileExtensions": [
|
||||
"html",
|
||||
"svelte",
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
"CSSNavigation.excludeGlobPatterns": [
|
||||
"**/bower_components/**",
|
||||
"**/vendor/**",
|
||||
"**/coverage/**"
|
||||
],
|
||||
"CSSNavigation.alwaysIncludeGlobPatterns": [
|
||||
"./theseus_gui/node_modules/omorphia/**/*.postcss"
|
||||
],
|
||||
"html-css-class-completion.HTMLLanguages": [
|
||||
"html",
|
||||
"svelte"
|
||||
],
|
||||
"html-css-class-completion.includeGlobPattern": "**/*.{postcss,svelte}",
|
||||
"html-css-class-completion.CSSLanguages": [
|
||||
"postcss",
|
||||
],
|
||||
"svelte.enable-ts-plugin": true,
|
||||
"svelte.ask-to-enable-ts-plugin": false,
|
||||
"svelte.plugin.css.diagnostics.enable": false,
|
||||
"svelte.plugin.svelte.diagnostics.enable": false,
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./theseus/Cargo.toml"
|
||||
],
|
||||
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||
}
|
||||
32
.vscode/tasks.json
vendored
Normal file
32
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "ui:dev",
|
||||
"type": "shell",
|
||||
// `dev` keeps running in the background
|
||||
// ideally you should also configure a `problemMatcher`
|
||||
// see https://code.visualstudio.com/docs/editor/tasks#_can-a-background-task-be-used-as-a-prelaunchtask-in-launchjson
|
||||
"isBackground": true,
|
||||
// change this to your `beforeDevCommand`:
|
||||
"command": "yarn",
|
||||
"args": ["dev"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/theseus_gui"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "ui:build",
|
||||
"type": "shell",
|
||||
// change this to your `beforeBuildCommand`:
|
||||
"command": "yarn",
|
||||
"args": ["build"],
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/theseus_gui"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
14
COPYING.md
14
COPYING.md
@@ -1,13 +1,13 @@
|
||||
# Copying Guidelines
|
||||
# Copying
|
||||
|
||||
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
|
||||
The source code of the theseus repository is licensed under the GNU 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.
|
||||
|
||||
For detailed information, consult each package's COPYING.md file, if available.
|
||||
## Modrinth logo
|
||||
|
||||
## Modrinth Branding
|
||||
Any files depicting the Modrinth branding, including the wrench-in-labyrinth logo, the landing image, and variations thereof, are licensed as follows:
|
||||
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
> All rights reserved. © 2020-2023 Rinth, Inc.
|
||||
|
||||
All rights reserved. © 2020-2024 Rinth, Inc.
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
If you fork this repository, you must remove all Modrinth branding assets from your fork.
|
||||
- theseus_gui/src-tauri/icons/*
|
||||
|
||||
5386
Cargo.lock
generated
5386
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
@@ -1,11 +1,24 @@
|
||||
[workspace]
|
||||
resolver = '2'
|
||||
|
||||
members = [
|
||||
'./packages/app-lib',
|
||||
'./apps/app-playground',
|
||||
'./apps/app'
|
||||
"theseus",
|
||||
"theseus_cli",
|
||||
"theseus_playground",
|
||||
"theseus_gui/src-tauri",
|
||||
"theseus_macros"
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
debug = true
|
||||
debug-assertions = true
|
||||
overflow-checks = true
|
||||
lto = false
|
||||
panic = 'unwind'
|
||||
incremental = true
|
||||
codegen-units = 256
|
||||
rpath = false
|
||||
|
||||
# Optimize for speed and reduce size on release builds
|
||||
[profile.release]
|
||||
panic = "abort" # Strip expensive panic clean-up logic
|
||||
@@ -13,9 +26,3 @@ codegen-units = 1 # Compile crates one after another so the compiler can optimiz
|
||||
lto = true # Enables link to optimizations
|
||||
opt-level = "s" # Optimize for binary size
|
||||
strip = true # Remove debug symbols
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
wry = { git = "https://github.com/modrinth/wry", rev = "27fb16b" }
|
||||
|
||||
44
README.md
44
README.md
@@ -1,39 +1,9 @@
|
||||
# 
|
||||
# theseus
|
||||
A game launcher which can be used as a CLI, GUI, and a library for creating and playing modrinth projects
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
Theseus aims to provide three components:
|
||||
- A library (theseus)
|
||||
- A CLI (theseus-cli)
|
||||
- A GUI (theseus-gui)
|
||||
|
||||
## Modrinth Monorepo
|
||||
|
||||
Welcome to the Modrinth Monorepo, the primary codebase for the Modrinth web interface and app. It contains  lines of code and has  contributors!
|
||||
|
||||
If you're not a developer and you've stumbled upon this repository, you can access the web interface on the [Modrinth website](https://modrinth.com) and download the latest release of the app [here](https://modrinth.com/app).
|
||||
|
||||
## Development
|
||||
|
||||
This repository contains two primary packages. For detailed development information, please refer to their respective READMEs:
|
||||
|
||||
- [Web Interface](apps/frontend/README.md)
|
||||
- [Desktop App](apps/app/README.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://support.modrinth.com/en/articles/8802215-contributing-to-modrinth).
|
||||
|
||||
If you plan to fork this repository for your own purposes, please review our [copying guidelines](COPYING.md).
|
||||
|
||||
## Security
|
||||
|
||||
If you discover a security vulnerability within our codebase, please follow our [responsible disclosure guidelines](https://modrinth.com/legal/security).
|
||||
|
||||
## Support
|
||||
|
||||
If you need help with the Modrinth web interface or app, please visit our [support page](https://support.modrinth.com). For general inquiries, you can also join our [Discord server](https://discord.modrinth.com).
|
||||
|
||||
## License
|
||||
|
||||
All packages in this repository are licensed under their respective licenses. Refer to the LICENSE file in each package for more information.
|
||||
Feel free to contribute!
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
**/dist
|
||||
@@ -1,13 +0,0 @@
|
||||
# Copying
|
||||
|
||||
The source code of the theseus repository is licensed under the GNU 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
|
||||
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
|
||||
> All rights reserved. © 2020-2023 Rinth, Inc.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
- theseus_gui/src-tauri/icons/\*
|
||||
@@ -1,22 +0,0 @@
|
||||
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
|
||||
import { fixupPluginRules } from '@eslint/compat'
|
||||
import turboPlugin from 'eslint-plugin-turbo'
|
||||
|
||||
export default createConfigForNuxt().append([
|
||||
{
|
||||
name: 'turbo',
|
||||
plugins: {
|
||||
turbo: fixupPluginRules(turboPlugin),
|
||||
},
|
||||
rules: {
|
||||
'turbo/no-undeclared-env-vars': 'error',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'modrinth',
|
||||
rules: {
|
||||
'vue/html-self-closing': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
},
|
||||
])
|
||||
@@ -1,54 +0,0 @@
|
||||
{
|
||||
"name": "@modrinth/app-frontend",
|
||||
"private": true,
|
||||
"version": "0.8.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"tsc:check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@sentry/vue": "^8.27.0",
|
||||
"@tauri-apps/api": "^2.0.0-rc.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
||||
"@vintl/vintl": "^4.4.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"floating-vue": "^5.2.2",
|
||||
"ofetch": "^1.3.4",
|
||||
"pinia": "^2.1.7",
|
||||
"posthog-js": "^1.158.2",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-multiselect": "3.0.0",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.1.1",
|
||||
"@nuxt/eslint-config": "^0.5.6",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"eslint-plugin-turbo": "^2.1.1",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsconfig": "workspace:*",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.2.8",
|
||||
"vue-tsc": "^2.1.6"
|
||||
},
|
||||
"packageManager": "pnpm@9.4.0"
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -1,579 +0,0 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
|
||||
import {
|
||||
HomeIcon,
|
||||
SearchIcon,
|
||||
LibraryIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
DownloadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, Notifications } from '@modrinth/ui'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { get } from '@/helpers/settings'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import ErrorModal from '@/components/ui/ErrorModal.vue'
|
||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||
import { handleError, useNotifications } from '@/store/notifications.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { isDev, getOS, restartApp } from '@/helpers/utils.js'
|
||||
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import { install_from_file } from './helpers/pack'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
||||
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
|
||||
import { useInstall } from '@/store/install.js'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-shell'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { check } from '@tauri-apps/plugin-updater'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const urlModal = ref(null)
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const showOnboarding = ref(false)
|
||||
const nativeDecorations = ref(false)
|
||||
|
||||
const os = ref('')
|
||||
|
||||
const stateInitialized = ref(false)
|
||||
|
||||
const criticalErrorMessage = ref()
|
||||
|
||||
onMounted(async () => {
|
||||
await useCheckDisableMouseover()
|
||||
})
|
||||
|
||||
async function setupApp() {
|
||||
stateInitialized.value = true
|
||||
const {
|
||||
native_decorations,
|
||||
theme,
|
||||
telemetry,
|
||||
collapsed_navigation,
|
||||
advanced_rendering,
|
||||
onboarded,
|
||||
default_page,
|
||||
} = await get()
|
||||
|
||||
if (default_page && default_page !== 'Home') {
|
||||
await router.push({ name: default_page })
|
||||
}
|
||||
|
||||
os.value = await getOS()
|
||||
const dev = await isDev()
|
||||
const version = await getVersion()
|
||||
showOnboarding.value = !onboarded
|
||||
|
||||
nativeDecorations.value = native_decorations
|
||||
if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations)
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
|
||||
initAnalytics()
|
||||
if (!telemetry) {
|
||||
optOutAnalytics()
|
||||
}
|
||||
if (dev) debugAnalytics()
|
||||
trackEvent('Launched', { version, dev, onboarded })
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
const osType = await type()
|
||||
if (osType === 'macos') {
|
||||
document.getElementsByTagName('html')[0].classList.add('mac')
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||
}
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
}),
|
||||
)
|
||||
|
||||
useFetch(
|
||||
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
'criticalAnnouncements',
|
||||
true,
|
||||
).then((res) => {
|
||||
if (res && res.header && res.body) {
|
||||
criticalErrorMessage.value = res
|
||||
}
|
||||
})
|
||||
|
||||
get_opening_command().then(handleCommand)
|
||||
checkUpdates()
|
||||
}
|
||||
|
||||
const stateFailed = ref(false)
|
||||
initialize_state()
|
||||
.then(() => {
|
||||
setupApp().catch((err) => {
|
||||
stateFailed.value = true
|
||||
console.error(err)
|
||||
error.showError(err, null, false, 'state_init')
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
stateFailed.value = true
|
||||
console.error('Failed to initialize app', err)
|
||||
error.showError(err, null, false, 'state_init')
|
||||
})
|
||||
|
||||
const handleClose = async () => {
|
||||
await saveWindowState(StateFlags.ALL)
|
||||
await getCurrentWindow().close()
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
router.afterEach((to, from, failure) => {
|
||||
trackEvent('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
})
|
||||
const route = useRoute()
|
||||
const isOnBrowse = computed(() => route.path.startsWith('/browse'))
|
||||
|
||||
const loading = useLoading()
|
||||
loading.setEnabled(false)
|
||||
|
||||
const notifications = useNotifications()
|
||||
const notificationsWrapper = ref()
|
||||
|
||||
const error = useError()
|
||||
const errorModal = ref()
|
||||
|
||||
const install = useInstall()
|
||||
const modInstallModal = ref()
|
||||
const installConfirmModal = ref()
|
||||
const incompatibilityWarningModal = ref()
|
||||
|
||||
onMounted(() => {
|
||||
invoke('show_window')
|
||||
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
|
||||
error.setErrorModal(errorModal.value)
|
||||
|
||||
install.setIncompatibilityWarningModal(incompatibilityWarningModal)
|
||||
install.setInstallConfirmModal(installConfirmModal)
|
||||
install.setModInstallModal(modInstallModal)
|
||||
})
|
||||
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
if (
|
||||
target.href &&
|
||||
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
||||
!target.classList.contains('router-link-active') &&
|
||||
!target.href.startsWith('http://localhost') &&
|
||||
!target.href.startsWith('https://tauri.localhost') &&
|
||||
!target.href.startsWith('http://tauri.localhost')
|
||||
) {
|
||||
open(target.href)
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
|
||||
document.querySelector('body').addEventListener('auxclick', function (e) {
|
||||
// disables middle click -> new tab
|
||||
if (e.button === 1) {
|
||||
e.preventDefault()
|
||||
// instead do a left click
|
||||
const event = new MouseEvent('click', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
e.target.dispatchEvent(event)
|
||||
}
|
||||
})
|
||||
|
||||
const accounts = ref(null)
|
||||
|
||||
command_listener(handleCommand)
|
||||
async function handleCommand(e) {
|
||||
if (!e) return
|
||||
|
||||
if (e.event === 'RunMRPack') {
|
||||
// RunMRPack should directly install a local mrpack given a path
|
||||
if (e.path.endsWith('.mrpack')) {
|
||||
await install_from_file(e.path).catch(handleError)
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Other commands are URL-based (deep linking)
|
||||
urlModal.value.show(e)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAvailable = ref(false)
|
||||
async function checkUpdates() {
|
||||
const update = await check()
|
||||
console.log(update)
|
||||
updateAvailable.value = !!update
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
checkUpdates()
|
||||
},
|
||||
5 * 1000 * 60,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplashScreen v-if="!stateFailed" ref="splashScreen" data-tauri-drag-region />
|
||||
<div v-if="stateInitialized" class="app-container">
|
||||
<div class="nav-container">
|
||||
<div class="nav-section">
|
||||
<suspense>
|
||||
<AccountsCard ref="accounts" mode="small" />
|
||||
</suspense>
|
||||
<div class="pages-list">
|
||||
<RouterLink v-tooltip="'Home'" to="/" class="btn icon-only collapsed-button">
|
||||
<HomeIcon />
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-tooltip="'Browse'"
|
||||
to="/browse/modpack"
|
||||
class="btn icon-only collapsed-button"
|
||||
:class="{
|
||||
'router-link-active': isOnBrowse,
|
||||
}"
|
||||
>
|
||||
<SearchIcon />
|
||||
</RouterLink>
|
||||
<RouterLink v-tooltip="'Library'" to="/library" class="btn icon-only collapsed-button">
|
||||
<LibraryIcon />
|
||||
</RouterLink>
|
||||
<Suspense>
|
||||
<InstanceCreationModal ref="installationModal" />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings pages-list">
|
||||
<button
|
||||
v-if="updateAvailable"
|
||||
v-tooltip="'Install update'"
|
||||
class="btn btn-outline btn-primary icon-only collapsed-button"
|
||||
@click="restartApp()"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</button>
|
||||
<Button
|
||||
v-tooltip="'Create profile'"
|
||||
class="sleek-primary collapsed-button"
|
||||
icon-only
|
||||
:disabled="offline"
|
||||
@click="() => $refs.installationModal.show()"
|
||||
>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<RouterLink v-tooltip="'Settings'" to="/settings" class="btn icon-only collapsed-button">
|
||||
<SettingsIcon />
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view">
|
||||
<div v-if="criticalErrorMessage" class="critical-error-banner" data-tauri-drag-region>
|
||||
<h1>{{ criticalErrorMessage.header }}</h1>
|
||||
<div class="markdown-body" v-html="renderString(criticalErrorMessage.body ?? '')"></div>
|
||||
</div>
|
||||
<div class="appbar-row">
|
||||
<div data-tauri-drag-region class="appbar">
|
||||
<section class="navigation-controls">
|
||||
<Breadcrumbs data-tauri-drag-region />
|
||||
</section>
|
||||
<section class="mod-stats">
|
||||
<Suspense>
|
||||
<RunningAppBar />
|
||||
</Suspense>
|
||||
</section>
|
||||
</div>
|
||||
<section v-if="!nativeDecorations" class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
|
||||
<MinimizeIcon />
|
||||
</Button>
|
||||
<Button
|
||||
class="titlebar-button"
|
||||
icon-only
|
||||
@click="() => getCurrentWindow().toggleMaximize()"
|
||||
>
|
||||
<MaximizeIcon />
|
||||
</Button>
|
||||
<Button class="titlebar-button close" icon-only @click="handleClose">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
<div class="router-view">
|
||||
<ModrinthLoadingIndicator
|
||||
offset-height="var(--appbar-height)"
|
||||
offset-width="var(--sidebar-width)"
|
||||
/>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
||||
<component :is="Component"></component>
|
||||
</Suspense>
|
||||
</template>
|
||||
</RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<URLConfirmModal ref="urlModal" />
|
||||
<Notifications ref="notificationsWrapper" />
|
||||
<ErrorModal ref="errorModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
<InstallConfirmModal ref="installConfirmModal" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sleek-primary {
|
||||
background-color: var(--color-brand-highlight);
|
||||
transition: all ease-in-out 0.1s;
|
||||
}
|
||||
|
||||
.navigation-controls {
|
||||
flex-grow: 1;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.appbar-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
z-index: 20;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.titlebar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all ease-in-out 0.1s;
|
||||
background-color: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
border-radius: 0;
|
||||
height: 3.25rem;
|
||||
|
||||
&.close {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-container {
|
||||
--appbar-height: 3.25rem;
|
||||
--sidebar-width: 4.5rem;
|
||||
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
|
||||
.view {
|
||||
width: calc(100% - var(--sidebar-width));
|
||||
background-color: var(--color-raised-bg);
|
||||
|
||||
.critical-error-banner {
|
||||
margin-top: -1.25rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(203, 34, 69, 0.1);
|
||||
border-left: 2px solid var(--color-red);
|
||||
border-bottom: 2px solid var(--color-red);
|
||||
border-right: 2px solid var(--color-red);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.appbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
background: var(--color-raised-bg);
|
||||
text-align: center;
|
||||
padding: var(--gap-md);
|
||||
height: 3.25rem;
|
||||
gap: var(--gap-sm);
|
||||
//no select
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.router-view {
|
||||
width: 100%;
|
||||
height: calc(100% - 3.125rem);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--color-bg);
|
||||
border-top-left-radius: var(--radius-xl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
padding: var(--gap-md);
|
||||
}
|
||||
|
||||
.pages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
word-spacing: 3px;
|
||||
background: inherit;
|
||||
transition: all ease-in-out 0.1s;
|
||||
color: var(--color-base);
|
||||
box-shadow: none;
|
||||
|
||||
&.router-link-active {
|
||||
color: var(--color-contrast);
|
||||
background: var(--color-button-bg);
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.primary {
|
||||
color: var(--color-accent-contrast);
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
|
||||
.collapsed-button {
|
||||
height: 3rem !important;
|
||||
width: 3rem !important;
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: none;
|
||||
|
||||
svg {
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
max-width: 1.5rem !important;
|
||||
max-height: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-sm) 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.mac {
|
||||
.nav-container {
|
||||
padding-top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
|
||||
.account-card,
|
||||
.card-section {
|
||||
top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
.windows {
|
||||
.fake-appbar {
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
right: 8rem;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
right: 8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 937 KiB |
@@ -1,63 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { Button, OverflowMenu } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const handleAddContentFromFile = async () => {
|
||||
const newProject = await open({ multiple: true })
|
||||
if (!newProject) return
|
||||
|
||||
for (const project of newProject) {
|
||||
await add_project_from_path(props.instance.path, project.path ?? project).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSearchContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="joined-buttons">
|
||||
<Button color="primary" @click="handleSearchContent"><SearchIcon /> Add content </Button>
|
||||
|
||||
<OverflowMenu
|
||||
:options="[
|
||||
{
|
||||
id: 'search',
|
||||
action: handleSearchContent,
|
||||
},
|
||||
{
|
||||
id: 'from_file',
|
||||
action: handleAddContentFromFile,
|
||||
},
|
||||
]"
|
||||
class="btn btn-primary btn-dropdown-animation icon-only"
|
||||
>
|
||||
<DropdownIcon />
|
||||
<template #search>
|
||||
<SearchIcon />
|
||||
<span class="no-wrap"> Search </span>
|
||||
</template>
|
||||
<template #from_file>
|
||||
<FolderOpenIcon />
|
||||
<span class="no-wrap"> Add from file </span>
|
||||
</template>
|
||||
</OverflowMenu>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,332 +0,0 @@
|
||||
<script setup>
|
||||
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.js'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const errorModal = ref()
|
||||
const error = ref()
|
||||
const closable = ref(true)
|
||||
|
||||
const title = ref('An error occurred')
|
||||
const errorType = ref('unknown')
|
||||
const supportLink = ref('https://support.modrinth.com')
|
||||
const metadata = ref({})
|
||||
|
||||
defineExpose({
|
||||
async show(errorVal, context, canClose = true, source = null) {
|
||||
closable.value = canClose
|
||||
|
||||
if (errorVal.message && errorVal.message.includes('Minecraft authentication error:')) {
|
||||
title.value = 'Unable to sign in to Minecraft'
|
||||
errorType.value = 'minecraft_auth'
|
||||
supportLink.value =
|
||||
'https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues'
|
||||
|
||||
if (
|
||||
errorVal.message.includes('existing connection was forcibly closed') ||
|
||||
errorVal.message.includes('error sending request for url')
|
||||
) {
|
||||
metadata.value.network = true
|
||||
}
|
||||
if (errorVal.message.includes('because the target machine actively refused it')) {
|
||||
metadata.value.hostsFile = true
|
||||
}
|
||||
} else if (errorVal.message && errorVal.message.includes('User is not logged in')) {
|
||||
title.value = 'Sign in to Minecraft'
|
||||
errorType.value = 'minecraft_sign_in'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else if (errorVal.message && errorVal.message.includes('Move directory error:')) {
|
||||
title.value = 'Could not change app directory'
|
||||
errorType.value = 'directory_move'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
|
||||
if (errorVal.message.includes('directory is not writeable')) {
|
||||
metadata.value.readOnly = true
|
||||
}
|
||||
|
||||
if (errorVal.message.includes('Not enough space')) {
|
||||
metadata.value.notEnoughSpace = true
|
||||
}
|
||||
} else if (errorVal.message && errorVal.message.includes('No loader version selected for')) {
|
||||
title.value = 'No loader selected'
|
||||
errorType.value = 'no_loader_version'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
metadata.value.profilePath = context.profilePath
|
||||
} else if (source === 'state_init') {
|
||||
title.value = 'Error initializing Modrinth App'
|
||||
errorType.value = 'state_init'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
} else {
|
||||
title.value = 'An error occurred'
|
||||
errorType.value = 'unknown'
|
||||
supportLink.value = 'https://support.modrinth.com'
|
||||
metadata.value = {}
|
||||
}
|
||||
|
||||
error.value = errorVal
|
||||
errorModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
const loadingMinecraft = ref(false)
|
||||
async function loginMinecraft() {
|
||||
try {
|
||||
loadingMinecraft.value = true
|
||||
const loggedIn = await login_flow()
|
||||
|
||||
if (loggedIn) {
|
||||
await set_default_user(loggedIn.id).catch(handleError)
|
||||
}
|
||||
|
||||
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
|
||||
loadingMinecraft.value = false
|
||||
errorModal.value.hide()
|
||||
} catch (err) {
|
||||
loadingMinecraft.value = false
|
||||
handleSevereError(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelDirectoryChange() {
|
||||
try {
|
||||
await cancel_directory_change()
|
||||
window.location.reload()
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
function retryDirectoryChange() {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const loadingRepair = ref(false)
|
||||
async function repairInstance() {
|
||||
loadingRepair.value = true
|
||||
try {
|
||||
await install(metadata.value.profilePath, false)
|
||||
errorModal.value.hide()
|
||||
} catch (err) {
|
||||
handleSevereError(err)
|
||||
}
|
||||
loadingRepair.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<template v-if="errorType === 'minecraft_auth'">
|
||||
<template v-if="metadata.network">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
It looks like there were issues with the Modrinth App connecting to Microsoft's
|
||||
servers. This is often the result of a poor connection, so we recommend trying again
|
||||
to see if it works. If issues continue to persist, follow the steps in
|
||||
<a
|
||||
href="https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues#h_e71a5f805f"
|
||||
>
|
||||
our support article
|
||||
</a>
|
||||
to troubleshoot.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="metadata.hostsFile">
|
||||
<h3>Network issues</h3>
|
||||
<p>
|
||||
The Modrinth App tried to connect to Microsoft / Xbox / Minecraft services, but the
|
||||
remote server rejected the connection. This may indicate that these services are
|
||||
blocked by the hosts file. Please visit
|
||||
<a
|
||||
href="https://support.modrinth.com/en/articles/9038231-minecraft-sign-in-issues#h_d694a29256"
|
||||
>
|
||||
our support article
|
||||
</a>
|
||||
for steps on how to fix the issue.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h3>Try another Microsoft account</h3>
|
||||
<p>
|
||||
Double check you've signed in with the right account. You may own Minecraft on a
|
||||
different Microsoft account.
|
||||
</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Try another account
|
||||
</button>
|
||||
</div>
|
||||
<h3>Using PC Game Pass, coming from Bedrock, or just bought the game?</h3>
|
||||
<p>
|
||||
Try signing in with the
|
||||
<a href="https://www.minecraft.net/en-us/download">official Minecraft Launcher</a>
|
||||
first. Once you're done, come back here and sign in!
|
||||
</p>
|
||||
</template>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Try signing in again
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="errorType === 'directory_move'">
|
||||
<template v-if="metadata.readOnly">
|
||||
<h3>Change directory permissions</h3>
|
||||
<p>
|
||||
It looks like the Modrinth App is unable to write to the directory you selected.
|
||||
Please adjust the permissions of the directory and try again or cancel the directory
|
||||
change.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else-if="metadata.notEnoughSpace">
|
||||
<h3>Not enough space</h3>
|
||||
<p>
|
||||
It looks like there is not enough space on the disk containing the dirctory you
|
||||
selected Please free up some space and try again or cancel the directory change.
|
||||
</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>
|
||||
The Modrinth App is unable to migrate to the new directory you selected. Please
|
||||
contact support for help or cancel the directory change.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div class="cta-button">
|
||||
<button class="btn" @click="retryDirectoryChange">
|
||||
<UpdatedIcon /> Retry directory change
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="cancelDirectoryChange">
|
||||
<XIcon /> Cancel directory change
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else-if="errorType === 'minecraft_sign_in'">
|
||||
<p>
|
||||
To play this instance, you must sign in through Microsoft below. If you don't have a
|
||||
Minecraft account, you can purchase the game on the
|
||||
<a href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
|
||||
>Minecraft website</a
|
||||
>.
|
||||
</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingMinecraft" @click="loginMinecraft">
|
||||
<LogInIcon /> Sign in to Minecraft
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="errorType === 'state_init'">
|
||||
<p>
|
||||
Modrinth App failed to load correctly. This may be because of a corrupted file, or
|
||||
because the app is missing crucial files.
|
||||
</p>
|
||||
<p>You may be able to fix it through one of the following ways:</p>
|
||||
<ul>
|
||||
<li>Ensuring you are connected to the internet, then try restarting the app.</li>
|
||||
<li>Redownloading the app.</li>
|
||||
</ul>
|
||||
</template>
|
||||
<template v-else-if="errorType === 'no_loader_version'">
|
||||
<p>The Modrinth App failed to find the loader version for this instance.</p>
|
||||
<p>To resolve this, you need to repair the instance. Click the button below to do so.</p>
|
||||
<div class="cta-button">
|
||||
<button class="btn btn-primary" :disabled="loadingRepair" @click="repairInstance">
|
||||
<HammerIcon /> Repair instance
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ error.message ?? error }}
|
||||
</template>
|
||||
<template
|
||||
v-if="
|
||||
errorType === 'directory_move' ||
|
||||
errorType === 'minecraft_auth' ||
|
||||
errorType === 'state_init' ||
|
||||
errorType === 'no_loader_version'
|
||||
"
|
||||
>
|
||||
<hr />
|
||||
<p>
|
||||
If nothing is working and you need help, visit
|
||||
<a :href="supportLink">our support page</a>
|
||||
and start a chat using the widget in the bottom right and we will be more than happy to
|
||||
assist! Make sure to provide the following debug information to the agent:
|
||||
</p>
|
||||
<details>
|
||||
<summary>Debug information</summary>
|
||||
{{ error.message ?? error }}
|
||||
</details>
|
||||
</template>
|
||||
</div>
|
||||
<div class="input-group push-right">
|
||||
<a :href="supportLink" class="btn" @click="errorModal.hide()"><ChatIcon /> Get support</a>
|
||||
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.light-mode {
|
||||
--color-orange-bg: rgba(255, 163, 71, 0.2);
|
||||
}
|
||||
|
||||
.dark-mode,
|
||||
.oled-mode {
|
||||
--color-orange-bg: rgba(224, 131, 37, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cta-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.warning-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: var(--gap-lg);
|
||||
background-color: var(--color-orange-bg);
|
||||
border: 2px solid var(--color-orange);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-banner__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 700;
|
||||
|
||||
svg {
|
||||
color: var(--color-orange);
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-md);
|
||||
padding: var(--gap-lg);
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,259 +0,0 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
|
||||
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const playing = ref(false)
|
||||
|
||||
const modLoading = computed(() => props.instance.install_stage !== 'installed')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const seeInstance = async () => {
|
||||
await router.push(`/instance/${encodeURIComponent(props.instance.path)}/`)
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningProcesses = await get_by_profile_path(props.instance.path).catch(handleError)
|
||||
|
||||
playing.value = runningProcesses.length > 0
|
||||
}
|
||||
|
||||
const play = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
modLoading.value = true
|
||||
await run(props.instance.path).catch((err) =>
|
||||
handleSevereError(err, { profilePath: props.instance.path }),
|
||||
)
|
||||
modLoading.value = false
|
||||
|
||||
trackEvent('InstancePlay', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const stop = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
await kill(props.instance.path).catch(handleError)
|
||||
|
||||
trackEvent('InstanceStop', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const openFolder = async () => {
|
||||
await showProfileInFolder(props.instance.path)
|
||||
}
|
||||
|
||||
const addContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
play,
|
||||
stop,
|
||||
seeInstance,
|
||||
openFolder,
|
||||
addContent,
|
||||
instance: props.instance,
|
||||
})
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
if (e.event === 'finished' && e.profile_path_id === props.instance.path) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="instance">
|
||||
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
|
||||
<Avatar
|
||||
size="lg"
|
||||
:src="props.instance.icon_path ? convertFileSrc(props.instance.icon_path) : null"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.name }}</p>
|
||||
<p
|
||||
v-if="
|
||||
props.instance.install_stage === 'installing' ||
|
||||
props.instance.install_stage === 'not_installed' ||
|
||||
props.instance.install_stage === 'pack_installing'
|
||||
"
|
||||
class="description"
|
||||
>
|
||||
Installing...
|
||||
</p>
|
||||
<p v-else class="description">
|
||||
{{ props.instance.loader }}
|
||||
{{ props.instance.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
v-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="(e) => play(e, 'InstanceCard')">
|
||||
<PlayIcon />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.loading-indicator {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
|
||||
svg {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.instance {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: calc(var(--gap-md) + 4.25rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
z-index: 1;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: calc(var(--gap-md) * 2);
|
||||
bottom: 3.25rem;
|
||||
opacity: 0;
|
||||
transition:
|
||||
0.2s ease-in-out bottom,
|
||||
0.2s ease-in-out opacity,
|
||||
0.1s ease-in-out filter !important;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-floating);
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&.install {
|
||||
background: var(--color-brand);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.stop {
|
||||
background: var(--color-red);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.loading-cta {
|
||||
background: hsl(220, 11%, 10%) !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: var(--gap-md);
|
||||
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||
margin-bottom: 0;
|
||||
|
||||
.mod-image {
|
||||
--size: 100%;
|
||||
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
aspect-ratio: 1 / 1 !important;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 110%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-base);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
font-size: 0.775rem;
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,126 +0,0 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { init_ads_window, open_ads_link, record_ads_click } from '@/helpers/ads.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
const showAd = ref(true)
|
||||
|
||||
defineExpose({
|
||||
scroll() {
|
||||
updateAdPosition()
|
||||
},
|
||||
})
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
if (creds && creds.user_id) {
|
||||
const user = await get_user(creds.user_id).catch(handleError)
|
||||
|
||||
const MIDAS_BITFLAG = 1 << 0
|
||||
if (user && (user.badges & MIDAS_BITFLAG) === MIDAS_BITFLAG) {
|
||||
showAd.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const adsWrapper = ref(null)
|
||||
let resizeObserver
|
||||
let scrollHandler
|
||||
let intersectionObserver
|
||||
let mutationObserver
|
||||
onMounted(() => {
|
||||
if (showAd.value) {
|
||||
updateAdPosition(true)
|
||||
|
||||
resizeObserver = new ResizeObserver(() => updateAdPosition())
|
||||
resizeObserver.observe(adsWrapper.value)
|
||||
|
||||
intersectionObserver = new IntersectionObserver(() => updateAdPosition())
|
||||
intersectionObserver.observe(adsWrapper.value)
|
||||
|
||||
mutationObserver = new MutationObserver(() => updateAdPosition())
|
||||
mutationObserver.observe(adsWrapper.value, { attributes: true, childList: true, subtree: true })
|
||||
|
||||
// Add scroll event listener
|
||||
scrollHandler = () => {
|
||||
requestAnimationFrame(() => updateAdPosition())
|
||||
}
|
||||
window.addEventListener('scroll', scrollHandler, { passive: true })
|
||||
}
|
||||
})
|
||||
|
||||
function updateAdPosition(overrideShown = false) {
|
||||
if (adsWrapper.value) {
|
||||
const rect = adsWrapper.value.getBoundingClientRect()
|
||||
|
||||
let y = rect.top + window.scrollY
|
||||
let height = rect.bottom - rect.top
|
||||
|
||||
// Prevent ad from overlaying the app bar
|
||||
if (y <= 52) {
|
||||
y = 52
|
||||
height = rect.bottom - 52
|
||||
|
||||
if (height < 0) {
|
||||
height = 0
|
||||
y = -1000
|
||||
}
|
||||
}
|
||||
|
||||
init_ads_window(rect.left + window.scrollX, y, rect.right - rect.left, height, overrideShown)
|
||||
}
|
||||
}
|
||||
|
||||
async function openPlusLink() {
|
||||
await record_ads_click()
|
||||
await open_ads_link('https://modrinth.com/plus', 'https://modrinth.com')
|
||||
}
|
||||
|
||||
const unlisten = await listen('ads-scroll', (event) => {
|
||||
if (adsWrapper.value) {
|
||||
adsWrapper.value.parentNode.scrollTop += event.payload.scroll
|
||||
updateAdPosition()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
if (intersectionObserver) {
|
||||
intersectionObserver.disconnect()
|
||||
}
|
||||
if (mutationObserver) {
|
||||
mutationObserver.disconnect()
|
||||
}
|
||||
if (scrollHandler) {
|
||||
window.removeEventListener('scroll', scrollHandler)
|
||||
}
|
||||
|
||||
unlisten()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="showAd"
|
||||
ref="adsWrapper"
|
||||
class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised cursor-pointer"
|
||||
>
|
||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
||||
<button
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline bg-transparent border-none text-left cursor-pointer outline-none"
|
||||
@click="openPlusLink"
|
||||
>
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
<span class="font-bold">Modrinth+</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
File diff suppressed because one or more lines are too long
@@ -1,69 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ConfirmModal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
defineProps({
|
||||
confirmationText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hasToType: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'No title defined',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: 'No description defined',
|
||||
required: true,
|
||||
},
|
||||
proceedLabel: {
|
||||
type: String,
|
||||
default: 'Proceed',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['proceed'])
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
hide_ads_window()
|
||||
modal.value.show()
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
}
|
||||
|
||||
function proceed() {
|
||||
emit('proceed')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmModal
|
||||
ref="modal"
|
||||
:confirmation-text="confirmationText"
|
||||
:has-to-type="hasToType"
|
||||
:title="title"
|
||||
:description="description"
|
||||
:proceed-label="proceedLabel"
|
||||
:on-hide="onModalHide"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="proceed"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,49 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Modal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const props = defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
hide_ads_window()
|
||||
modal.value.show()
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
props.onHide()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
|
||||
<slot />
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -1,61 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ShareModal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: 'Share',
|
||||
},
|
||||
shareTitle: {
|
||||
type: String,
|
||||
default: 'Modrinth',
|
||||
},
|
||||
shareText: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
openInNewTab: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: (passedContent) => {
|
||||
hide_ads_window()
|
||||
modal.value.show(passedContent)
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ShareModal
|
||||
ref="modal"
|
||||
:header="header"
|
||||
:share-title="shareTitle"
|
||||
:share-text="shareText"
|
||||
:link="link"
|
||||
:open-in-new-tab="openInNewTab"
|
||||
:on-hide="onModalHide"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,368 +0,0 @@
|
||||
<script setup>
|
||||
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
||||
import { Button, Card, Checkbox } from '@modrinth/ui'
|
||||
import {
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
MicrosoftIcon,
|
||||
GoogleIcon,
|
||||
SteamIcon,
|
||||
GitLabIcon,
|
||||
} from '@/assets/external'
|
||||
import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.js'
|
||||
import { handleError, useNotifications } from '@/store/state.js'
|
||||
import { ref } from 'vue'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
callback: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref()
|
||||
const turnstileToken = ref()
|
||||
const widgetId = ref()
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
modal.value.show()
|
||||
|
||||
if (window.turnstile === null || !window.turnstile) {
|
||||
const script = document.createElement('script')
|
||||
script.src =
|
||||
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback'
|
||||
script.async = true
|
||||
script.defer = true
|
||||
document.head.appendChild(script)
|
||||
|
||||
window.onloadTurnstileCallback = loadWidget
|
||||
} else {
|
||||
loadWidget()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function loadWidget() {
|
||||
widgetId.value = window.turnstile.render('#turnstile-container', {
|
||||
sitekey: '0x4AAAAAAAW3guHM6Eunbgwu',
|
||||
callback: (token) => (turnstileToken.value = token),
|
||||
expiredCallback: () => (turnstileToken.value = null),
|
||||
})
|
||||
}
|
||||
|
||||
function removeWidget() {
|
||||
if (widgetId.value) {
|
||||
window.turnstile.remove(widgetId.value)
|
||||
widgetId.value = null
|
||||
turnstileToken.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const loggingIn = ref(true)
|
||||
const twoFactorFlow = ref(null)
|
||||
const twoFactorCode = ref('')
|
||||
|
||||
const email = ref('')
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const subscribe = ref(true)
|
||||
|
||||
async function signInOauth(provider) {
|
||||
const creds = await login(provider).catch(handleSevereError)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn2fa() {
|
||||
const creds = await login_2fa(twoFactorCode.value, twoFactorFlow.value).catch(handleError)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
const creds = await login_pass(username.value, password.value, turnstileToken.value).catch(
|
||||
handleError,
|
||||
)
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function createAccount() {
|
||||
if (password.value !== confirmPassword.value) {
|
||||
const notifs = useNotifications()
|
||||
notifs.addNotification({
|
||||
title: 'An error occurred',
|
||||
text: 'Passwords do not match!',
|
||||
type: 'error',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const creds = await create_account(
|
||||
username.value,
|
||||
email.value,
|
||||
password.value,
|
||||
turnstileToken.value,
|
||||
subscribe.value,
|
||||
).catch(handleError)
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalWrapper ref="modal" :on-hide="removeWidget">
|
||||
<Card>
|
||||
<template v-if="twoFactorFlow">
|
||||
<h1>Enter two-factor code</h1>
|
||||
<p>Please enter a two-factor code to proceed.</p>
|
||||
<input v-model="twoFactorCode" maxlength="11" type="text" placeholder="Enter code..." />
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 v-if="loggingIn">Login to Modrinth</h1>
|
||||
<h1 v-else>Create an account</h1>
|
||||
<div class="button-grid">
|
||||
<Button class="discord" large @click="signInOauth('discord')">
|
||||
<DiscordIcon />
|
||||
Discord
|
||||
</Button>
|
||||
<Button class="github" large @click="signInOauth('github')">
|
||||
<GithubIcon />
|
||||
Github
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('microsoft')">
|
||||
<MicrosoftIcon />
|
||||
Microsoft
|
||||
</Button>
|
||||
<Button class="google" large @click="signInOauth('google')">
|
||||
<GoogleIcon />
|
||||
Google
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('steam')">
|
||||
<SteamIcon />
|
||||
Steam
|
||||
</Button>
|
||||
<Button class="gitlab" large @click="signInOauth('gitlab')">
|
||||
<GitLabIcon />
|
||||
GitLab
|
||||
</Button>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<hr />
|
||||
<p>Or</p>
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<MailIcon />
|
||||
<input v-model="email" type="text" placeholder="Email" />
|
||||
</div>
|
||||
<div class="iconified-input username">
|
||||
<UserIcon />
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
:placeholder="loggingIn ? 'Email or username' : 'Username'"
|
||||
/>
|
||||
</div>
|
||||
<div class="iconified-input" :class="{ username: !loggingIn }">
|
||||
<LockIcon />
|
||||
<input v-model="password" type="password" placeholder="Password" />
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<LockIcon />
|
||||
<input v-model="confirmPassword" type="password" placeholder="Confirm password" />
|
||||
</div>
|
||||
<div class="turnstile">
|
||||
<div id="turnstile-container"></div>
|
||||
<div id="turnstile-container-2"></div>
|
||||
</div>
|
||||
<Checkbox
|
||||
v-if="!loggingIn"
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
label="Subscribe to updates about Modrinth"
|
||||
/>
|
||||
<div class="link-row">
|
||||
<a v-if="loggingIn" class="button-base" @click="loggingIn = false"> Create account </a>
|
||||
<a v-else class="button-base" @click="loggingIn = true">Sign in</a>
|
||||
<a class="button-base" href="https://modrinth.com/auth/reset-password">
|
||||
Forgot password?
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" large>Close</Button>
|
||||
<Button v-if="twoFactorCode" color="primary" large @click="signIn2fa"> Login </Button>
|
||||
<Button
|
||||
v-else-if="loggingIn"
|
||||
color="primary"
|
||||
large
|
||||
:disabled="!turnstileToken"
|
||||
@click="signIn"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button v-else color="primary" large :disabled="!turnstileToken" @click="createAccount">
|
||||
Create account
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.modal-container) {
|
||||
.modal-body {
|
||||
width: auto;
|
||||
|
||||
.content {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 25rem;
|
||||
}
|
||||
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: var(--gap-md);
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discord {
|
||||
background-color: #5865f2;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.github {
|
||||
background-color: #8740f1;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: var(--color-contrast);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.google {
|
||||
background-color: #4285f4;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.gitlab {
|
||||
background-color: #fc6d26;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--color-raised-bg);
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
width: 100%;
|
||||
border-top: 2px solid var(--color-button-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
margin-bottom: var(--gap-sm);
|
||||
}
|
||||
|
||||
.link-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
a {
|
||||
color: var(--color-blue);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-md) 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.turnstile {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 2px solid var(--color-button-bg);
|
||||
height: 66px;
|
||||
margin-top: var(--gap-md);
|
||||
|
||||
iframe {
|
||||
margin: -1px;
|
||||
min-width: calc(100% + 2px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import cssContent from '@/assets/stylesheets/macFix.css?inline'
|
||||
|
||||
export async function useCheckDisableMouseover() {
|
||||
try {
|
||||
// Fetch the CSS content from the Rust backend
|
||||
let should_disable_mouseover = await invoke('plugin:utils|should_disable_mouseover')
|
||||
|
||||
if (should_disable_mouseover) {
|
||||
// Create a style element and set its content
|
||||
const styleElement = document.createElement('style')
|
||||
styleElement.innerHTML = cssContent
|
||||
|
||||
// Append the style element to the document's head
|
||||
document.head.appendChild(styleElement)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking OS version from Rust backend', error)
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export async function init_ads_window(x, y, width, height, overrideShown = false) {
|
||||
return await invoke('plugin:ads|init_ads_window', { x, y, width, height, overrideShown })
|
||||
}
|
||||
|
||||
export async function show_ads_window() {
|
||||
return await invoke('plugin:ads|show_ads_window')
|
||||
}
|
||||
|
||||
export async function hide_ads_window(reset) {
|
||||
return await invoke('plugin:ads|hide_ads_window', { reset })
|
||||
}
|
||||
|
||||
export async function record_ads_click() {
|
||||
return await invoke('plugin:ads|record_ads_click')
|
||||
}
|
||||
|
||||
export async function open_ads_link(path, origin) {
|
||||
return await invoke('plugin:ads|open_link', { path, origin })
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { posthog } from 'posthog-js'
|
||||
|
||||
export const initAnalytics = () => {
|
||||
posthog.init('phc_hm2ihMpTAoE86xIm7XzsCB8RPiTRKivViK5biiHedm', {
|
||||
persistence: 'localStorage',
|
||||
})
|
||||
}
|
||||
|
||||
export const debugAnalytics = () => {
|
||||
posthog.debug()
|
||||
}
|
||||
|
||||
export const optOutAnalytics = () => {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
|
||||
export const optInAnalytics = () => {
|
||||
posthog.opt_in_capturing()
|
||||
}
|
||||
|
||||
export const trackEvent = (eventName, properties) => {
|
||||
posthog.capture(eventName, properties)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
// Example function:
|
||||
// User goes to auth_url to complete flow, and when completed, authenticate_await_completion() returns the credentials
|
||||
// export async function authenticate() {
|
||||
// const auth_url = await authenticate_begin_flow()
|
||||
// console.log(auth_url)
|
||||
// await authenticate_await_completion()
|
||||
// }
|
||||
|
||||
/**
|
||||
* Authenticate a user with Hydra - part 1.
|
||||
* This begins the authentication flow quasi-synchronously.
|
||||
*
|
||||
* @returns {Promise<DeviceLoginSuccess>} A DeviceLoginSuccess object with two relevant fields:
|
||||
* @property {string} verification_uri - The URL to go to complete the flow.
|
||||
* @property {string} user_code - The code to enter on the verification_uri page.
|
||||
*/
|
||||
export async function login() {
|
||||
return await invoke('plugin:auth|login')
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default user
|
||||
* @return {Promise<UUID | undefined>}
|
||||
*/
|
||||
export async function get_default_user() {
|
||||
return await invoke('plugin:auth|get_default_user')
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default user
|
||||
* @param {UUID} user
|
||||
*/
|
||||
export async function set_default_user(user) {
|
||||
return await invoke('plugin:auth|set_default_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user account from the database
|
||||
* @param {UUID} user
|
||||
*/
|
||||
export async function remove_user(user) {
|
||||
return await invoke('plugin:auth|remove_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of users
|
||||
* @returns {Promise<Credential[]>}
|
||||
*/
|
||||
export async function users() {
|
||||
return await invoke('plugin:auth|get_users')
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export async function get_project(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_project', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_project_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_project_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_version(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_version', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_version_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_version_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_user(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_user', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_user_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_user_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_team(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_team', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_team_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_team_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_organization(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_organization', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_organization_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_organization_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_search_results(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_search_results', { id, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function get_search_results_many(ids, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_search_results_many', { ids, cacheBehaviour })
|
||||
}
|
||||
|
||||
export async function purge_cache_types(cacheTypes) {
|
||||
return await invoke('plugin:cache|purge_cache_types', { cacheTypes })
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
/*
|
||||
|
||||
JavaVersion {
|
||||
path: Path
|
||||
version: String
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
export async function get_java_versions() {
|
||||
return await invoke('plugin:jre|get_java_versions')
|
||||
}
|
||||
|
||||
export async function set_java_version(javaVersion) {
|
||||
return await invoke('plugin:jre|set_java_version', { javaVersion })
|
||||
}
|
||||
|
||||
// Finds all the installation of Java 7, if it exists
|
||||
// Returns [JavaVersion]
|
||||
export async function find_filtered_jres(version) {
|
||||
return await invoke('plugin:jre|jre_find_filtered_jres', { version })
|
||||
}
|
||||
|
||||
// Gets java version from a specific path by trying to run 'java -version' on it.
|
||||
// This also validates it, as it returns null if no valid java version is found at the path
|
||||
export async function get_jre(path) {
|
||||
return await invoke('plugin:jre|jre_get_jre', { path })
|
||||
}
|
||||
|
||||
// Tests JRE version by running 'java -version' on it.
|
||||
// Returns true if the version is valid, and matches given (after extraction)
|
||||
export async function test_jre(path, majorVersion, minorVersion) {
|
||||
return await invoke('plugin:jre|jre_test_jre', { path, majorVersion, minorVersion })
|
||||
}
|
||||
|
||||
// Automatically installs specified java version
|
||||
export async function auto_install_java(javaVersion) {
|
||||
return await invoke('plugin:jre|jre_auto_install_java', { javaVersion })
|
||||
}
|
||||
|
||||
// Get max memory in KiB
|
||||
export async function get_max_memory() {
|
||||
return await invoke('plugin:jre|jre_get_max_memory')
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
/// Gets the game versions from daedalus
|
||||
// Returns a VersionManifest
|
||||
export async function get_game_versions() {
|
||||
return await invoke('plugin:metadata|metadata_get_game_versions')
|
||||
}
|
||||
|
||||
// Gets the given loader versions from daedalus
|
||||
// Returns Manifest
|
||||
export async function get_loader_versions(loader) {
|
||||
return await invoke('plugin:metadata|metadata_get_loader_versions', { loader })
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export async function login(provider) {
|
||||
return await invoke('modrinth_auth_login', { provider })
|
||||
}
|
||||
|
||||
export async function login_pass(username, password, challenge) {
|
||||
return await invoke('plugin:mr-auth|login_pass', { username, password, challenge })
|
||||
}
|
||||
|
||||
export async function login_2fa(code, flow) {
|
||||
return await invoke('plugin:mr-auth|login_2fa', { code, flow })
|
||||
}
|
||||
|
||||
export async function create_account(username, email, password, challenge, signUpNewsletter) {
|
||||
return await invoke('plugin:mr-auth|create_account', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
challenge,
|
||||
signUpNewsletter,
|
||||
})
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
return await invoke('plugin:mr-auth|logout')
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
return await invoke('plugin:mr-auth|get')
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* All theseus API calls return serialized values (both return values and errors);
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
/// Gets all running process IDs with a given profile path
|
||||
/// Returns [u32]
|
||||
export async function get_by_profile_path(path) {
|
||||
return await invoke('plugin:process|process_get_by_profile_path', { path })
|
||||
}
|
||||
|
||||
/// Gets all running process IDs with a given profile path
|
||||
/// Returns [u32]
|
||||
export async function get_all() {
|
||||
return await invoke('plugin:process|process_get_all')
|
||||
}
|
||||
|
||||
/// Kills a process by UUID
|
||||
export async function kill(uuid) {
|
||||
return await invoke('plugin:process|process_kill', { uuid })
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { get_full_path, get_mod_full_path } from '@/helpers/profile'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
export async function isDev() {
|
||||
return await invoke('is_dev')
|
||||
}
|
||||
|
||||
// One of 'Windows', 'Linux', 'MacOS'
|
||||
export async function getOS() {
|
||||
return await invoke('plugin:utils|get_os')
|
||||
}
|
||||
|
||||
export async function openPath(path) {
|
||||
return await invoke('plugin:utils|open_path', { path })
|
||||
}
|
||||
|
||||
export async function highlightInFolder(path) {
|
||||
return await invoke('plugin:utils|highlight_in_folder', { path })
|
||||
}
|
||||
|
||||
export async function showLauncherLogsFolder() {
|
||||
return await invoke('plugin:utils|show_launcher_logs_folder', {})
|
||||
}
|
||||
|
||||
// Opens a profile's folder in the OS file explorer
|
||||
export async function showProfileInFolder(path) {
|
||||
const fullPath = await get_full_path(path)
|
||||
return await openPath(fullPath)
|
||||
}
|
||||
|
||||
export async function highlightModInProfile(profilePath, projectPath) {
|
||||
const fullPath = await get_mod_full_path(profilePath, projectPath)
|
||||
return await highlightInFolder(fullPath)
|
||||
}
|
||||
|
||||
export async function restartApp() {
|
||||
return await invoke('restart_app')
|
||||
}
|
||||
|
||||
export const releaseColor = (releaseType) => {
|
||||
switch (releaseType) {
|
||||
case 'release':
|
||||
return 'green'
|
||||
case 'beta':
|
||||
return 'orange'
|
||||
case 'alpha':
|
||||
return 'red'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export function debounce(fn, wait) {
|
||||
let timer
|
||||
return function (...args) {
|
||||
if (timer) {
|
||||
clearTimeout(timer) // clear any pre-existing timer
|
||||
}
|
||||
|
||||
const context = this // get the current context
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(context, args) // call the function if time expires
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import router from '@/routes'
|
||||
import App from '@/App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import { createPlugin } from '@vintl/vintl/plugin'
|
||||
import * as Sentry from '@sentry/vue'
|
||||
|
||||
const VIntlPlugin = createPlugin({
|
||||
controllerOpts: {
|
||||
defaultLocale: 'en-US',
|
||||
locale: 'en-US',
|
||||
locales: [
|
||||
{
|
||||
tag: 'en-US',
|
||||
meta: {
|
||||
displayName: 'American English',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
globalMixin: true,
|
||||
injectInto: [],
|
||||
})
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
let app = createApp(App)
|
||||
|
||||
Sentry.init({
|
||||
app,
|
||||
dsn: 'https://9508775ee5034536bc70433f5f531dd4@o485889.ingest.us.sentry.io/4504579615227904',
|
||||
integrations: [Sentry.browserTracingIntegration({ router })],
|
||||
tracesSampleRate: 0.1,
|
||||
})
|
||||
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(FloatingVue)
|
||||
app.use(VIntlPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -1,21 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useError = defineStore('errorsStore', {
|
||||
state: () => ({
|
||||
errorModal: null,
|
||||
}),
|
||||
actions: {
|
||||
setErrorModal(ref) {
|
||||
this.errorModal = ref
|
||||
},
|
||||
showError(error, context, closable = true, source = null) {
|
||||
this.errorModal.show(error, context, closable, source)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const handleSevereError = (err, context) => {
|
||||
const error = useError()
|
||||
error.showError(err, context)
|
||||
console.error(err)
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import {
|
||||
add_project_from_version,
|
||||
check_installed,
|
||||
list,
|
||||
get,
|
||||
get_projects,
|
||||
remove_project,
|
||||
} from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_project, get_version_many } from '@/helpers/cache.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import { trackEvent } from '@/helpers/analytics.js'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export const useInstall = defineStore('installStore', {
|
||||
state: () => ({
|
||||
installConfirmModal: null,
|
||||
modInstallModal: null,
|
||||
incompatibilityWarningModal: null,
|
||||
}),
|
||||
actions: {
|
||||
setInstallConfirmModal(ref) {
|
||||
this.installConfirmModal = ref
|
||||
},
|
||||
showInstallConfirmModal(project, version_id, onInstall) {
|
||||
this.installConfirmModal.show(project, version_id, onInstall)
|
||||
},
|
||||
setIncompatibilityWarningModal(ref) {
|
||||
this.incompatibilityWarningModal = ref
|
||||
},
|
||||
showIncompatibilityWarningModal(instance, project, versions, onInstall) {
|
||||
this.incompatibilityWarningModal.show(instance, project, versions, onInstall)
|
||||
},
|
||||
setModInstallModal(ref) {
|
||||
this.modInstallModal = ref
|
||||
},
|
||||
showModInstallModal(project, versions, onInstall) {
|
||||
this.modInstallModal.show(project, versions, onInstall)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const install = async (projectId, versionId, instancePath, source, callback = () => {}) => {
|
||||
const project = await get_project(projectId, 'must_revalidate').catch(handleError)
|
||||
|
||||
if (project.project_type === 'modpack') {
|
||||
const version = versionId ?? project.versions[project.versions.length - 1]
|
||||
const packs = await list().catch(handleError)
|
||||
|
||||
if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) {
|
||||
await packInstall(project.id, version, project.title, project.icon_url).catch(handleError)
|
||||
|
||||
trackEvent('PackInstall', {
|
||||
id: project.id,
|
||||
version_id: version,
|
||||
title: project.title,
|
||||
source,
|
||||
})
|
||||
|
||||
callback(version)
|
||||
} else {
|
||||
const install = useInstall()
|
||||
install.showInstallConfirmModal(project, version, callback)
|
||||
}
|
||||
} else {
|
||||
if (instancePath) {
|
||||
const [instance, instanceProjects, versions] = await Promise.all([
|
||||
await get(instancePath).catch(handleError),
|
||||
await get_projects(instancePath).catch(handleError),
|
||||
await get_version_many(project.versions, 'must_revalidate'),
|
||||
])
|
||||
|
||||
const projectVersions = versions.sort(
|
||||
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
|
||||
)
|
||||
|
||||
let version
|
||||
if (versionId) {
|
||||
version = projectVersions.find((x) => x.id === versionId)
|
||||
} else {
|
||||
version = projectVersions.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(instance.game_version) &&
|
||||
(project.project_type === 'mod'
|
||||
? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
|
||||
: true),
|
||||
)
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
version = projectVersions[0]
|
||||
}
|
||||
|
||||
if (
|
||||
version.game_versions.includes(instance.game_version) &&
|
||||
(project.project_type === 'mod'
|
||||
? version.loaders.includes(instance.loader) || version.loaders.includes('minecraft')
|
||||
: true)
|
||||
) {
|
||||
for (const [path, file] of Object.entries(instanceProjects)) {
|
||||
if (file.metadata && file.metadata.project_id === project.id) {
|
||||
await remove_project(instance.path, path)
|
||||
}
|
||||
}
|
||||
|
||||
await add_project_from_version(instance.path, version.id).catch(handleError)
|
||||
await installVersionDependencies(instance, version)
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.id,
|
||||
project_type: project.project_type,
|
||||
version_id: version.id,
|
||||
title: project.title,
|
||||
source,
|
||||
})
|
||||
|
||||
callback(version.id)
|
||||
} else {
|
||||
const install = useInstall()
|
||||
install.showIncompatibilityWarningModal(instance, project, projectVersions, callback)
|
||||
}
|
||||
} else {
|
||||
const versions = (await get_version_many(project.versions).catch(handleError)).sort(
|
||||
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
|
||||
)
|
||||
|
||||
const install = useInstall()
|
||||
install.showModInstallModal(project, versions, callback)
|
||||
}
|
||||
}
|
||||
|
||||
// If project is modpack:
|
||||
// - We check all available instances if modpack is already installed
|
||||
// If true: show confirmation modal
|
||||
// If false: install it (latest version if passed version is null)
|
||||
// If project is mod:
|
||||
// - If instance is selected:
|
||||
// - If project is already installed
|
||||
// We first uninstall the project
|
||||
// - If no version is selected, we look check the instance for versions to select based on the versions
|
||||
// - If there are no versions, we show the incompat modal
|
||||
// - If a version is selected, and the version is incompatible, we show the incompat modal
|
||||
// - Version is inarlled, as well as version dependencies
|
||||
}
|
||||
|
||||
export const installVersionDependencies = async (profile, version) => {
|
||||
for (const dep of version.dependencies) {
|
||||
if (dep.dependency_type !== 'required') continue
|
||||
// disallow fabric api install on quilt
|
||||
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
|
||||
if (dep.version_id) {
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
await add_project_from_version(profile.path, dep.version_id)
|
||||
} else {
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
|
||||
const depProject = await get_project(dep.project_id, 'must_revalidate').catch(handleError)
|
||||
|
||||
const depVersions = (
|
||||
await get_version_many(depProject.versions, 'must_revalidate').catch(handleError)
|
||||
).sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
|
||||
|
||||
const latest = depVersions.find(
|
||||
(v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader),
|
||||
)
|
||||
if (latest) {
|
||||
await add_project_from_version(profile.path, latest.id).catch(handleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
'./src/components/**/*.{js,vue,ts}',
|
||||
'./src/layouts/**/*.vue',
|
||||
'./src/pages/**/*.vue',
|
||||
'./src/plugins/**/*.{js,ts}',
|
||||
'./src/app.vue',
|
||||
'./src/error.vue',
|
||||
// monorepo - TODO: migrate this to its own package
|
||||
'../../packages/**/*.{js,vue,ts}',
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
icon: 'var(--color-icon)',
|
||||
// Text
|
||||
primary: 'var(--color-text)',
|
||||
contrast: 'var(--color-contrast)',
|
||||
secondary: 'var(--color-secondary)',
|
||||
inactive: 'var(--color-text-inactive)',
|
||||
dark: 'var(--color-text-dark)',
|
||||
inverted: 'var(--color-text-inverted)',
|
||||
heading: 'var(--color-heading)',
|
||||
red: 'var(--color-red)',
|
||||
orange: 'var(--color-orange)',
|
||||
purple: 'var(--color-purple)',
|
||||
bg: {
|
||||
DEFAULT: 'var(--color-bg)',
|
||||
red: 'var(--color-red-bg)',
|
||||
orange: 'var(--color-orange-bg)',
|
||||
green: 'var(--color-green-bg)',
|
||||
blue: 'var(--color-blue-bg)',
|
||||
purple: 'var(--color-purple-bg)',
|
||||
raised: 'var(--color-raised-bg)',
|
||||
},
|
||||
highlight: {
|
||||
DEFAULT: 'var(--color-brand-highlight)',
|
||||
red: 'var(--color-red-highlight)',
|
||||
orange: 'var(--color-orange-highlight)',
|
||||
green: 'var(--color-green-highlight)',
|
||||
blue: 'var(--color-blue-highlight)',
|
||||
purple: 'var(--color-purple-highlight)',
|
||||
},
|
||||
divider: {
|
||||
DEFAULT: 'var(--color-divider)',
|
||||
dark: 'var(--color-divider-dark)',
|
||||
},
|
||||
brand: {
|
||||
DEFAULT: 'var(--color-brand)',
|
||||
red: 'var(--color-red)',
|
||||
orange: 'var(--color-orange)',
|
||||
green: 'var(--color-green)',
|
||||
blue: 'var(--color-blue)',
|
||||
purple: 'var(--color-purple)',
|
||||
highlight: 'var(--color-brand-highlight)',
|
||||
shadow: 'var(--color-brand-shadow)',
|
||||
inverted: 'var(--color-accent-contrast)',
|
||||
},
|
||||
tabUnderlineHovered: 'var(--tab-underline-hovered)',
|
||||
button: {
|
||||
bg: 'var(--color-button-bg)',
|
||||
text: 'var(--color-button-text)',
|
||||
bgHover: 'var(--color-button-bg-hover)',
|
||||
textHover: 'var(--color-button-text-hover)',
|
||||
bgActive: 'var(--color-button-bg-active)',
|
||||
textActive: 'var(--color-button-text-active)',
|
||||
},
|
||||
toggleHandle: 'var(--color-toggle-handle)',
|
||||
dropdown: {
|
||||
bg: 'var(--color-dropdown-bg)',
|
||||
text: 'var(--color-dropdown-text)',
|
||||
},
|
||||
tooltip: {
|
||||
bg: 'var(--color-tooltip-bg)',
|
||||
text: 'var(--color-tooltip-text)',
|
||||
},
|
||||
code: {
|
||||
bg: 'var(--color-code-bg)',
|
||||
text: 'var(--color-code-text)',
|
||||
},
|
||||
kbdShadow: 'var(--color-kbd-shadow)',
|
||||
ad: {
|
||||
DEFAULT: 'var(--color-ad)',
|
||||
raised: 'var(--color-ad-raised)',
|
||||
contrast: 'var(--color-ad-contrast)',
|
||||
highlight: 'var(--color-ad-highlight)',
|
||||
},
|
||||
greyLink: {
|
||||
DEFAULT: 'var(--color-grey-link)',
|
||||
hover: 'var(--color-grey-link-hover)',
|
||||
active: 'var(--color-grey-link-active)',
|
||||
},
|
||||
link: {
|
||||
DEFAULT: 'var(--color-link)',
|
||||
hover: 'var(--color-link-hover)',
|
||||
active: 'var(--color-link-active)',
|
||||
},
|
||||
warning: {
|
||||
bg: 'var(--color-warning-bg)',
|
||||
text: 'var(--color-warning-text)',
|
||||
banner: {
|
||||
text: 'var(--color-warning-banner-text)',
|
||||
bg: 'var(--color-warning-banner-bg)',
|
||||
side: 'var(--color-warning-banner-side)',
|
||||
},
|
||||
},
|
||||
infoBanner: {
|
||||
text: 'var(--color-info-banner-text)',
|
||||
bg: 'var(--color-info-banner-bg)',
|
||||
side: 'var(--color-info-banner-side)',
|
||||
},
|
||||
blockQuote: 'var(--color-block-quote)',
|
||||
headerUnderline: 'var(--color-header-underline)',
|
||||
hr: 'var(--color-hr)',
|
||||
table: {
|
||||
border: 'var(--color-table-border)',
|
||||
alternateRow: ' var(--color-table-alternate-row)',
|
||||
},
|
||||
},
|
||||
backgroundImage: {
|
||||
mazeBg: 'var(--landing-maze-bg)',
|
||||
mazeGradientBg: 'var(--landing-maze-gradient-bg)',
|
||||
landing: {
|
||||
mazeOuterBg: 'var(--landing-maze-outer-bg)',
|
||||
colorHeading: 'var(--landing-color-heading)',
|
||||
colorSubheading: 'var(--landing-color-subheading)',
|
||||
transitionGradientStart: 'var(--landing-transition-gradient-start)',
|
||||
transitionGradientEnd: 'var(--landing-transition-gradient-end)',
|
||||
hoverCardGradient: 'var(--landing-hover-card-gradient)',
|
||||
borderGradient: 'var(--landing-border-gradient)',
|
||||
borderColor: 'var(--landing-border-color)',
|
||||
creatorGradient: 'var(--landing-creator-gradient)',
|
||||
blobGradient: 'var(--landing-blob-gradient)',
|
||||
cardBg: 'var(--landing-card-bg)',
|
||||
blueLabel: 'var(--landing-blue-label)',
|
||||
blueLabelBg: 'var(--landing-blue-label-bg)',
|
||||
greenLabel: 'var(--landing-green-label)',
|
||||
greenLabelBg: 'var(--landing-green-label-bg)',
|
||||
rawBg: 'var(--landing-raw-bg)',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
"strict": true,
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "@modrinth/app-playground",
|
||||
"scripts": {
|
||||
"build": "cargo build --release",
|
||||
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix",
|
||||
"dev": "cargo run",
|
||||
"test": "cargo test"
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use theseus::prelude::*;
|
||||
|
||||
use theseus::profile::create::profile_create;
|
||||
|
||||
// A simple Rust implementation of the authentication run
|
||||
// 1) call the authenticate_begin_flow() function to get the URL to open (like you would in the frontend)
|
||||
// 2) open the URL in a browser
|
||||
// 3) call the authenticate_await_complete_flow() function to get the credentials (like you would in the frontend)
|
||||
pub async fn authenticate_run() -> theseus::Result<Credentials> {
|
||||
println!("A browser window will now open, follow the login flow there.");
|
||||
let login = minecraft_auth::begin_login().await?;
|
||||
|
||||
println!("URL {}", login.redirect_uri.as_str());
|
||||
webbrowser::open(login.redirect_uri.as_str())?;
|
||||
|
||||
println!("Please enter URL code: ");
|
||||
let mut input = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("error: unable to read user input");
|
||||
|
||||
println!("You entered: {}", input.trim());
|
||||
|
||||
let credentials = minecraft_auth::finish_login(&input, login).await?;
|
||||
|
||||
println!("Logged in user {}.", credentials.username);
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> theseus::Result<()> {
|
||||
println!("Starting.");
|
||||
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
// Initialize state
|
||||
State::init().await?;
|
||||
|
||||
if minecraft_auth::users().await?.is_empty() {
|
||||
println!("No users found, authenticating.");
|
||||
authenticate_run().await?; // could take credentials from here direct, but also deposited in state users
|
||||
}
|
||||
//
|
||||
// st.settings
|
||||
// .write()
|
||||
// .await
|
||||
// .java_globals
|
||||
// .insert(JAVA_8_KEY.to_string(), check_jre(path).await?.unwrap());
|
||||
// Clear profiles
|
||||
println!("Clearing profiles.");
|
||||
{
|
||||
let h = profile::list().await?;
|
||||
for profile in h.into_iter() {
|
||||
profile::remove(&profile.path).await?;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Creating/adding profile.");
|
||||
|
||||
let name = "Example".to_string();
|
||||
let game_version = "1.16.1".to_string();
|
||||
let modloader = ModLoader::Forge;
|
||||
let loader_version = "stable".to_string();
|
||||
|
||||
let profile_path = profile_create(
|
||||
name,
|
||||
game_version,
|
||||
modloader,
|
||||
Some(loader_version),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("running");
|
||||
// Run a profile, running minecraft and store the RwLock to the process
|
||||
let process = profile::run(&profile_path).await?;
|
||||
|
||||
println!("Minecraft UUID: {}", process.uuid);
|
||||
|
||||
println!("All running process UUID {:?}", process::get_all().await?);
|
||||
|
||||
// hold the lock to the process until it ends
|
||||
println!("Waiting for process to end...");
|
||||
process::wait_for(process.uuid).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
[package]
|
||||
name = "theseus_gui"
|
||||
version = "0.8.9"
|
||||
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/modrinth/code/apps/app/"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc", features = ["codegen"] }
|
||||
|
||||
[dependencies]
|
||||
theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
tauri = { version = "2.0.0-rc", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
||||
tauri-plugin-window-state = "2.0.0-rc"
|
||||
tauri-plugin-deep-link = "2.0.0-rc"
|
||||
tauri-plugin-os = "2.0.0-rc"
|
||||
tauri-plugin-shell = "2.0.0-rc"
|
||||
tauri-plugin-dialog = "2.0.0-rc"
|
||||
tauri-plugin-updater = { version = "2.0.0-rc" }
|
||||
tauri-plugin-single-instance = { version = "2.0.0-rc" }
|
||||
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
futures = "0.3"
|
||||
daedalus = "0.2.3"
|
||||
chrono = "0.4.26"
|
||||
|
||||
dirs = "5.0.1"
|
||||
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||
os_info = "3.7.0"
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-error = "0.2.0"
|
||||
|
||||
lazy_static = "1"
|
||||
once_cell = "1"
|
||||
|
||||
dashmap = "6.0.1"
|
||||
paste = "1.0.15"
|
||||
|
||||
opener = { version = "0.7.2", features = ["reveal", "dbus-vendored"] }
|
||||
|
||||
native-dialog = "0.7.0"
|
||||
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
window-shadows = "0.2.1"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.25.0"
|
||||
objc = "0.2.7"
|
||||
rand = "0.8.5"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tauri-plugin-updater = { version = "2.0.0-rc", optional = true, features = ["native-tls-vendored", "zip"], default-features = false }
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||
default = ["custom-protocol"]
|
||||
# this feature is used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
updater = []
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>ModrinthApp</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>modrinth</string>
|
||||
<string>modrinthscheme</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your camera.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your microphone.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
674
apps/app/LICENSE
674
apps/app/LICENSE
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -1,29 +0,0 @@
|
||||
# 
|
||||
|
||||
## Modrinth App
|
||||
|
||||
The Modrinth App is a desktop application for managing your Minecraft mods. It is built with [Tauri](https://tauri.app/) and [Vue](https://vuejs.org/).
|
||||
|
||||
If you're not a developer and you've stumbled upon this repository, you can download the latest release of the app from the [Modrinth website](https://modrinth.com/app).
|
||||
|
||||
## Development
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
Before you begin, ensure you have the following installed on your machine:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/)
|
||||
- [pnpm](https://pnpm.io/)
|
||||
- [Rust](https://www.rust-lang.org/tools/install)
|
||||
- [Tauri](https://v2.tauri.app/start/prerequisites/)
|
||||
|
||||
### Setup
|
||||
|
||||
Follow these steps to set up your development environment:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm app:dev
|
||||
```
|
||||
|
||||
You should now have a development build of the app running with hot-reloading enabled. Any changes you make to the code will automatically refresh the app.
|
||||
@@ -1,239 +0,0 @@
|
||||
use tauri_build::{DefaultPermissionRule, InlinedPlugin};
|
||||
|
||||
fn main() {
|
||||
// Sadly, there is no better way to do it right now
|
||||
// You could try parsing source code here and detecting #[tauri::command]
|
||||
// But I think it's not worth it
|
||||
// https://github.com/tauri-apps/tauri/issues/10075
|
||||
tauri_build::try_build(
|
||||
tauri_build::Attributes::new()
|
||||
.codegen(tauri_build::CodegenContext::new())
|
||||
.plugin(
|
||||
"auth",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"login",
|
||||
"remove_user",
|
||||
"get_default_user",
|
||||
"set_default_user",
|
||||
"get_users",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"cache",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_project",
|
||||
"get_project_many",
|
||||
"get_version",
|
||||
"get_version_many",
|
||||
"get_user",
|
||||
"get_user_many",
|
||||
"get_team",
|
||||
"get_team_many",
|
||||
"get_organization",
|
||||
"get_organization_many",
|
||||
"get_search_results",
|
||||
"get_search_results_many",
|
||||
"purge_cache_types",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"import",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_importable_instances",
|
||||
"import_instance",
|
||||
"is_valid_importable_instance",
|
||||
"get_default_launcher_path",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"jre",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_java_versions",
|
||||
"set_java_version",
|
||||
"jre_find_filtered_jres",
|
||||
"jre_get_jre",
|
||||
"jre_test_jre",
|
||||
"jre_auto_install_java",
|
||||
"jre_get_max_memory",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"logs",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"logs_get_logs",
|
||||
"logs_get_logs_by_filename",
|
||||
"logs_get_output_by_filename",
|
||||
"logs_delete_logs",
|
||||
"logs_delete_logs_by_filename",
|
||||
"logs_get_latest_log_cursor",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"metadata",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"metadata_get_game_versions",
|
||||
"metadata_get_loader_versions",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"mr-auth",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"login_pass",
|
||||
"login_2fa",
|
||||
"create_account",
|
||||
"logout",
|
||||
"get",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"pack",
|
||||
InlinedPlugin::new()
|
||||
.commands(&["pack_install", "pack_get_profile_from_pack"])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"process",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"process_get_all",
|
||||
"process_get_by_profile_path",
|
||||
"process_kill",
|
||||
"process_wait_for",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"profile",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"profile_remove",
|
||||
"profile_get",
|
||||
"profile_get_many",
|
||||
"profile_get_projects",
|
||||
"profile_get_optimal_jre_key",
|
||||
"profile_get_full_path",
|
||||
"profile_get_mod_full_path",
|
||||
"profile_list",
|
||||
"profile_check_installed",
|
||||
"profile_install",
|
||||
"profile_update_all",
|
||||
"profile_update_project",
|
||||
"profile_add_project_from_version",
|
||||
"profile_add_project_from_path",
|
||||
"profile_toggle_disable_project",
|
||||
"profile_remove_project",
|
||||
"profile_update_managed_modrinth_version",
|
||||
"profile_repair_managed_modrinth",
|
||||
"profile_run",
|
||||
"profile_run_credentials",
|
||||
"profile_kill",
|
||||
"profile_edit",
|
||||
"profile_edit_icon",
|
||||
"profile_export_mrpack",
|
||||
"profile_get_pack_export_candidates",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"profile-create",
|
||||
InlinedPlugin::new()
|
||||
.commands(&["profile_create", "profile_duplicate"])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"settings",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"settings_get",
|
||||
"settings_set",
|
||||
"cancel_directory_change",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"tags",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"tags_get_categories",
|
||||
"tags_get_report_types",
|
||||
"tags_get_loaders",
|
||||
"tags_get_game_versions",
|
||||
"tags_get_donation_platforms",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"utils",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_os",
|
||||
"should_disable_mouseover",
|
||||
"highlight_in_folder",
|
||||
"open_path",
|
||||
"show_launcher_logs_folder",
|
||||
"progress_bars_list",
|
||||
"get_opening_command",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"ads",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"init_ads_window",
|
||||
"hide_ads_window",
|
||||
"scroll_ads_window",
|
||||
"show_ads_window",
|
||||
"record_ads_click",
|
||||
"open_link",
|
||||
"get_ads_personalization",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
),
|
||||
)
|
||||
.expect("Failed to run tauri-build");
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"identifier": "ads",
|
||||
"description": "",
|
||||
"local": false,
|
||||
"remote": {
|
||||
"urls": ["https://modrinth.com/*", "http://localhost:3000/*"]
|
||||
},
|
||||
"webviews": [
|
||||
"ads-window"
|
||||
],
|
||||
"permissions": [
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"identifier": "core",
|
||||
"description": "",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"core:path:default",
|
||||
"core:event:default",
|
||||
"core:window:default",
|
||||
"core:app:default",
|
||||
"core:resources:default",
|
||||
"core:menu:default",
|
||||
"core:tray:default",
|
||||
"core:window:allow-create",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-unminimize",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-set-decorations",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:webview:allow-set-webview-zoom"
|
||||
]
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"identifier": "plugins",
|
||||
"description": "",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-confirm",
|
||||
"shell:allow-open",
|
||||
"os:allow-platform",
|
||||
"os:allow-version",
|
||||
"os:allow-os-type",
|
||||
"os:allow-family",
|
||||
"os:allow-arch",
|
||||
"os:allow-exe-extension",
|
||||
"os:allow-locale",
|
||||
"os:allow-hostname",
|
||||
"deep-link:default",
|
||||
"window-state:default",
|
||||
"window-state:allow-restore-state",
|
||||
"window-state:allow-save-window-state",
|
||||
|
||||
"auth:default",
|
||||
"import:default",
|
||||
"jre:default",
|
||||
"logs:default",
|
||||
"metadata:default",
|
||||
"mr-auth:default",
|
||||
"profile-create:default",
|
||||
"pack:default",
|
||||
"process:default",
|
||||
"profile:default",
|
||||
"cache:default",
|
||||
"settings:default",
|
||||
"tags:default",
|
||||
"utils:default",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"identifier": "updater",
|
||||
"description": "",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
||||
!macro NSIS_HOOK_POSTINSTALL
|
||||
SetShellVarContext current
|
||||
|
||||
IfFileExists "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe" file_found file_not_found
|
||||
file_found:
|
||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
||||
|
||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\uninstall.exe"
|
||||
RMDir "$LOCALAPPDATA${PRODUCTNAME}"
|
||||
|
||||
!insertmacro DeleteAppUserModelId
|
||||
|
||||
; Remove start menu shortcut
|
||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
||||
!insertmacro IsShortcutTarget "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
||||
Pop $0
|
||||
${If} $0 = 1
|
||||
!insertmacro UnpinShortcut "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
||||
Delete "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
||||
RMDir "$SMPROGRAMS$AppStartMenuFolder"
|
||||
${EndIf}
|
||||
!insertmacro IsShortcutTarget "$SMPROGRAMS${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
||||
Pop $0
|
||||
${If} $0 = 1
|
||||
!insertmacro UnpinShortcut "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
||||
Delete "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
||||
${EndIf}
|
||||
|
||||
!insertmacro IsShortcutTarget "$DESKTOP${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
||||
Pop $0
|
||||
${If} $0 = 1
|
||||
!insertmacro UnpinShortcut "$DESKTOP${PRODUCTNAME}.lnk"
|
||||
Delete "$DESKTOP${PRODUCTNAME}.lnk"
|
||||
${EndIf}
|
||||
|
||||
DeleteRegKey HKCU "${UNINSTKEY}"
|
||||
|
||||
goto end_of_test ;<== important for not continuing on the else branch
|
||||
file_not_found:
|
||||
end_of_test:
|
||||
!macroend
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"name": "@modrinth/app",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"dev": "tauri dev",
|
||||
"test": "cargo test",
|
||||
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-rc.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/app-frontend": "workspace:*",
|
||||
"@modrinth/app-lib": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
document.addEventListener(
|
||||
'click',
|
||||
function (e) {
|
||||
window.top.postMessage({ modrinthAdClick: true }, 'https://modrinth.com')
|
||||
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
e.preventDefault()
|
||||
if (target.href) {
|
||||
window.top.postMessage({ modrinthOpenUrl: target.href }, 'https://modrinth.com')
|
||||
}
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
window.open = (url, target, features) => {
|
||||
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
use serde::Serialize;
|
||||
use std::collections::HashSet;
|
||||
use std::time::{Duration, Instant};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
use theseus::settings;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub size: Option<LogicalSize<f32>>,
|
||||
pub position: Option<LogicalPosition<f32>>,
|
||||
pub last_click: Option<Instant>,
|
||||
pub malicious_origins: HashSet<String>,
|
||||
}
|
||||
|
||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("ads")
|
||||
.setup(|app, _api| {
|
||||
app.manage(RwLock::new(AdsState {
|
||||
shown: true,
|
||||
size: None,
|
||||
position: None,
|
||||
last_click: None,
|
||||
malicious_origins: HashSet::new(),
|
||||
}));
|
||||
|
||||
// We refresh the ads window every 5 minutes for performance
|
||||
let app = app.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
loop {
|
||||
if let Some(webview) = app.webviews().get_mut("ads-window")
|
||||
{
|
||||
let _ = webview.navigate(AD_LINK.parse().unwrap());
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(60 * 5))
|
||||
.await;
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
init_ads_window,
|
||||
hide_ads_window,
|
||||
scroll_ads_window,
|
||||
show_ads_window,
|
||||
record_ads_click,
|
||||
open_link,
|
||||
get_ads_personalization,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn init_ads_window<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
override_shown: bool,
|
||||
) -> crate::api::Result<()> {
|
||||
use tauri::WebviewUrl;
|
||||
const LINK_SCRIPT: &str = include_str!("ads-init.js");
|
||||
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
state.size = Some(LogicalSize::new(width, height));
|
||||
state.position = Some(LogicalPosition::new(x, y));
|
||||
|
||||
if override_shown {
|
||||
state.shown = true;
|
||||
}
|
||||
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
if state.shown {
|
||||
let _ = webview.set_position(LogicalPosition::new(x, y));
|
||||
let _ = webview.set_size(LogicalSize::new(width, height));
|
||||
}
|
||||
} else if let Some(window) = app.get_window("main") {
|
||||
let _ = window.add_child(
|
||||
tauri::webview::WebviewBuilder::new(
|
||||
"ads-window",
|
||||
WebviewUrl::External(
|
||||
AD_LINK.parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.initialization_script(LINK_SCRIPT)
|
||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
|
||||
.zoom_hotkeys_enabled(false)
|
||||
.transparent(true),
|
||||
if state.shown {
|
||||
LogicalPosition::new(x, y)
|
||||
} else {
|
||||
LogicalPosition::new(-1000.0, -1000.0)
|
||||
},
|
||||
LogicalSize::new(width, height),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: make ads work on linux
|
||||
#[tauri::command]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn init_ads_window() {}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn show_ads_window<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<()> {
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
state.shown = true;
|
||||
if let Some(size) = state.size {
|
||||
let _ = webview.set_size(size);
|
||||
}
|
||||
|
||||
if let Some(position) = state.position {
|
||||
let _ = webview.set_position(position);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hide_ads_window<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
reset: Option<bool>,
|
||||
) -> crate::api::Result<()> {
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
state.shown = false;
|
||||
|
||||
if reset.unwrap_or(false) {
|
||||
state.size = None;
|
||||
state.position = None;
|
||||
}
|
||||
|
||||
let _ = webview.set_position(LogicalPosition::new(-1000, -1000));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
struct ScrollEvent {
|
||||
scroll: f32,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn scroll_ads_window<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
scroll: f32,
|
||||
) -> crate::api::Result<()> {
|
||||
let _ = app.emit("ads-scroll", ScrollEvent { scroll });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn record_ads_click<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
|
||||
let mut state = state.write().await;
|
||||
state.last_click = Some(Instant::now());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_link<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
path: String,
|
||||
origin: String,
|
||||
) -> crate::api::Result<()> {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
if url::Url::parse(&path).is_ok()
|
||||
&& !state.malicious_origins.contains(&origin)
|
||||
{
|
||||
if let Some(last_click) = state.last_click {
|
||||
if last_click.elapsed() < Duration::from_millis(100) {
|
||||
let _ = app.shell().open(&path, None);
|
||||
state.last_click = None;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracing::info!("Malicious click: {path} origin {origin}");
|
||||
state.malicious_origins.insert(origin);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_ads_personalization() -> crate::api::Result<bool> {
|
||||
let res = settings::get().await?;
|
||||
Ok(res.personalized_ads)
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
use crate::api::Result;
|
||||
use chrono::{Duration, Utc};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, Runtime, UserAttentionType};
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
login,
|
||||
remove_user,
|
||||
get_default_user,
|
||||
set_default_user,
|
||||
get_users,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Authenticate a user with Hydra - part 1
|
||||
/// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
|
||||
#[tauri::command]
|
||||
pub async fn login<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> Result<Option<Credentials>> {
|
||||
let flow = minecraft_auth::begin_login().await?;
|
||||
|
||||
let start = Utc::now();
|
||||
|
||||
if let Some(window) = app.get_webview_window("signin") {
|
||||
window.close()?;
|
||||
}
|
||||
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
&app,
|
||||
"signin",
|
||||
tauri::WebviewUrl::External(flow.redirect_uri.parse().map_err(
|
||||
|_| {
|
||||
theseus::ErrorKind::OtherError(
|
||||
"Error parsing auth redirect URL".to_string(),
|
||||
)
|
||||
.as_error()
|
||||
},
|
||||
)?),
|
||||
)
|
||||
.title("Sign into Modrinth")
|
||||
.always_on_top(true)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
window.request_user_attention(Some(UserAttentionType::Critical))?;
|
||||
|
||||
while (Utc::now() - start) < Duration::minutes(10) {
|
||||
if window.title().is_err() {
|
||||
// user closed window, cancelling flow
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if window
|
||||
.url()?
|
||||
.as_str()
|
||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||
{
|
||||
if let Some((_, code)) =
|
||||
window.url()?.query_pairs().find(|x| x.0 == "code")
|
||||
{
|
||||
window.close()?;
|
||||
let val =
|
||||
minecraft_auth::finish_login(&code.clone(), flow).await?;
|
||||
|
||||
return Ok(Some(val));
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
window.close()?;
|
||||
Ok(None)
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn remove_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::remove_user(user).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_default_user() -> Result<Option<uuid::Uuid>> {
|
||||
Ok(minecraft_auth::get_default_user().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_default_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::set_default_user(user).await?)
|
||||
}
|
||||
|
||||
/// Get a copy of the list of all user credentials
|
||||
#[tauri::command]
|
||||
pub async fn get_users() -> Result<Vec<Credentials>> {
|
||||
Ok(minecraft_auth::users().await?)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
use crate::api::Result;
|
||||
use theseus::prelude::*;
|
||||
|
||||
macro_rules! impl_cache_methods {
|
||||
($(($variant:ident, $type:ty)),*) => {
|
||||
$(
|
||||
paste::paste! {
|
||||
#[tauri::command]
|
||||
pub async fn [<get_ $variant:snake>](id: &str, cache_behaviour: Option<CacheBehaviour>) -> Result<Option<$type>>
|
||||
{
|
||||
Ok(theseus::cache::[<get_ $variant:snake>](id, cache_behaviour).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn [<get_ $variant:snake _many>](
|
||||
ids: Vec<String>,
|
||||
cache_behaviour: Option<CacheBehaviour>,
|
||||
) -> Result<Vec<$type>>
|
||||
{
|
||||
let ids = ids.iter().map(|x| &**x).collect::<Vec<&str>>();
|
||||
let entries =
|
||||
theseus::cache::[<get_ $variant:snake _many>](&*ids, cache_behaviour).await?;
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
impl_cache_methods!(
|
||||
(Project, Project),
|
||||
(Version, Version),
|
||||
(User, User),
|
||||
(Team, Vec<TeamMember>),
|
||||
(Organization, Organization),
|
||||
(SearchResults, SearchResults)
|
||||
);
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("cache")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_project,
|
||||
get_project_many,
|
||||
get_version,
|
||||
get_version_many,
|
||||
get_user,
|
||||
get_user_many,
|
||||
get_team,
|
||||
get_team_many,
|
||||
get_organization,
|
||||
get_organization_many,
|
||||
get_search_results,
|
||||
get_search_results_many,
|
||||
purge_cache_types,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn purge_cache_types(cache_types: Vec<CacheValueType>) -> Result<()> {
|
||||
Ok(theseus::cache::purge_cache_types(&cache_types).await?)
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
use crate::api::Result;
|
||||
use daedalus::minecraft::VersionManifest;
|
||||
use daedalus::modded::Manifest;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("metadata")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
metadata_get_game_versions,
|
||||
metadata_get_loader_versions,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Gets the game versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_game_versions() -> Result<VersionManifest> {
|
||||
Ok(theseus::metadata::get_minecraft_versions().await?)
|
||||
}
|
||||
|
||||
/// Gets the fabric versions from daedalus
|
||||
#[tauri::command]
|
||||
pub async fn metadata_get_loader_versions(loader: &str) -> Result<Manifest> {
|
||||
Ok(theseus::metadata::get_loader_versions(loader).await?)
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
use crate::api::Result;
|
||||
use chrono::{Duration, Utc};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, UserAttentionType};
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("mr-auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
login_pass,
|
||||
login_2fa,
|
||||
create_account,
|
||||
logout,
|
||||
get,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn modrinth_auth_login(
|
||||
app: tauri::AppHandle,
|
||||
provider: &str,
|
||||
) -> Result<Option<ModrinthCredentialsResult>> {
|
||||
let redirect_uri = mr_auth::authenticate_begin_flow(provider);
|
||||
|
||||
let start = Utc::now();
|
||||
|
||||
if let Some(window) = app.get_webview_window("modrinth-signin") {
|
||||
window.close()?;
|
||||
}
|
||||
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
&app,
|
||||
"modrinth-signin",
|
||||
tauri::WebviewUrl::External(redirect_uri.parse().map_err(|_| {
|
||||
theseus::ErrorKind::OtherError(
|
||||
"Error parsing auth redirect URL".to_string(),
|
||||
)
|
||||
.as_error()
|
||||
})?),
|
||||
)
|
||||
.title("Sign into Modrinth")
|
||||
.always_on_top(true)
|
||||
.center()
|
||||
.build()?;
|
||||
|
||||
window.request_user_attention(Some(UserAttentionType::Critical))?;
|
||||
|
||||
while (Utc::now() - start) < Duration::minutes(10) {
|
||||
if window.title().is_err() {
|
||||
// user closed window, cancelling flow
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if window
|
||||
.url()?
|
||||
.as_str()
|
||||
.starts_with("https://launcher-files.modrinth.com/detect.txt")
|
||||
{
|
||||
let query = window
|
||||
.url()?
|
||||
.query_pairs()
|
||||
.map(|(key, val)| {
|
||||
(
|
||||
key.to_string(),
|
||||
serde_json::Value::String(val.to_string()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
window.close()?;
|
||||
|
||||
let val = mr_auth::authenticate_finish_flow(query).await?;
|
||||
|
||||
return Ok(Some(val));
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
window.close()?;
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn login_pass(
|
||||
username: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
) -> Result<ModrinthCredentialsResult> {
|
||||
Ok(theseus::mr_auth::login_password(username, password, challenge).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn login_2fa(code: &str, flow: &str) -> Result<ModrinthCredentials> {
|
||||
Ok(theseus::mr_auth::login_2fa(code, flow).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn create_account(
|
||||
username: &str,
|
||||
email: &str,
|
||||
password: &str,
|
||||
challenge: &str,
|
||||
sign_up_newsletter: bool,
|
||||
) -> Result<ModrinthCredentials> {
|
||||
Ok(theseus::mr_auth::create_account(
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
challenge,
|
||||
sign_up_newsletter,
|
||||
)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn logout() -> Result<()> {
|
||||
Ok(theseus::mr_auth::logout().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get() -> Result<Option<ModrinthCredentials>> {
|
||||
Ok(theseus::mr_auth::get_credentials().await?)
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use crate::api::Result;
|
||||
use theseus::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("process")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
process_get_all,
|
||||
process_get_by_profile_path,
|
||||
process_kill,
|
||||
process_wait_for,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn process_get_all() -> Result<Vec<ProcessMetadata>> {
|
||||
Ok(process::get_all().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn process_get_by_profile_path(
|
||||
path: &str,
|
||||
) -> Result<Vec<ProcessMetadata>> {
|
||||
Ok(process::get_by_profile_path(path).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn process_kill(uuid: Uuid) -> Result<()> {
|
||||
Ok(process::kill(uuid).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn process_wait_for(uuid: Uuid) -> Result<()> {
|
||||
Ok(process::wait_for(uuid).await?)
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
use tauri::{Manager, Runtime};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InitialPayload {
|
||||
pub payload: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
pub fn get_or_init_payload<R: Runtime, M: Manager<R>>(
|
||||
manager: &M,
|
||||
) -> InitialPayload {
|
||||
let initial_payload = manager.try_state::<InitialPayload>();
|
||||
let mtx = if let Some(initial_payload) = initial_payload {
|
||||
initial_payload.inner().clone()
|
||||
} else {
|
||||
tracing::info!("No initial payload found, creating new");
|
||||
let payload = InitialPayload {
|
||||
payload: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
manager.manage(payload.clone());
|
||||
|
||||
payload
|
||||
};
|
||||
|
||||
mtx
|
||||
}
|
||||
@@ -1,412 +0,0 @@
|
||||
// Stolen from https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Emitter, Runtime, Window,
|
||||
}; // 0.8
|
||||
|
||||
const WINDOW_CONTROL_PAD_X: f64 = 9.0;
|
||||
const WINDOW_CONTROL_PAD_Y: f64 = 16.0;
|
||||
|
||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||
unsafe impl Send for UnsafeWindowHandle {}
|
||||
unsafe impl Sync for UnsafeWindowHandle {}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("traffic_light_positioner")
|
||||
.on_window_ready(|window| {
|
||||
#[cfg(target_os = "macos")]
|
||||
setup_traffic_light_positioner(window);
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(
|
||||
ns_window_handle: UnsafeWindowHandle,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
let ns_window = ns_window_handle.0 as cocoa::base::id;
|
||||
unsafe {
|
||||
let close = ns_window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize = ns_window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom =
|
||||
ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y =
|
||||
NSView::frame(ns_window).size.height - title_bar_frame_height;
|
||||
let _: () =
|
||||
msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between =
|
||||
NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug)]
|
||||
struct WindowState<R: Runtime> {
|
||||
window: Window<R>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
use cocoa::base::{id, BOOL};
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use objc::runtime::{Object, Sel};
|
||||
use std::ffi::c_void;
|
||||
|
||||
// Do the initial positioning
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
|
||||
// Ensure they stay in place while resizing the window.
|
||||
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
|
||||
this: &Object,
|
||||
func: F,
|
||||
) {
|
||||
let ptr = unsafe {
|
||||
let x: *mut c_void = *this.get_ivar("app_box");
|
||||
&mut *(x as *mut WindowState<R>)
|
||||
};
|
||||
func(ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ns_win = window
|
||||
.ns_window()
|
||||
.expect("NS Window should exist to mount traffic light delegate.")
|
||||
as id;
|
||||
|
||||
let current_delegate: id = ns_win.delegate();
|
||||
|
||||
extern "C" fn on_window_should_close(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
sender: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, windowShouldClose: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_close(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillClose: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resize<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
let id = state.window.ns_window().expect(
|
||||
"NS window should exist on state to handle resize",
|
||||
) as id;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidResize: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_move(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidMove: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_change_backing_properties(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_become_key(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidBecomeKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resign_key(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidResignKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_entered(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, draggingEntered: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_prepare_for_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, prepareForDragOperation: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_perform_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
sender: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, performDragOperation: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_conclude_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, concludeDragOperation: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_exited(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, draggingExited: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_use_full_screen_presentation_options(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
proposed_options: NSUInteger,
|
||||
) -> NSUInteger {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
|
||||
let id =
|
||||
state.window.ns_window().expect("Failed to emit event")
|
||||
as id;
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_fail_to_enter_full_screen(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_change(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![
|
||||
super_del,
|
||||
effectiveAppearanceDidChangedOnMainThread: notification
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Are we deallocing this properly ? (I miss safe Rust :( )
|
||||
let window_label = window.label().to_string();
|
||||
|
||||
let app_state = WindowState { window };
|
||||
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||
let random_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(20)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
|
||||
// delegate with the same name.
|
||||
let delegate_name =
|
||||
format!("windowDelegate_{}_{}", window_label, random_str);
|
||||
|
||||
ns_win.setDelegate_(delegate!(&delegate_name, {
|
||||
window: id = ns_win,
|
||||
app_box: *mut c_void = app_box,
|
||||
toolbar: id = cocoa::base::nil,
|
||||
super_delegate: id = current_delegate,
|
||||
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
||||
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
||||
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
||||
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
||||
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
|
||||
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
||||
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
||||
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
use native_dialog::{MessageDialog, MessageType};
|
||||
use tauri::{Listener, Manager};
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
mod error;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate cocoa;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
|
||||
// Should be called in launcher initialization
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
theseus::EventState::init(app.clone()).await?;
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
{
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
let updater = app.updater_builder().build()?;
|
||||
|
||||
let update_fut = updater.check();
|
||||
|
||||
State::init().await?;
|
||||
|
||||
let check_bar = theseus::init_loading(
|
||||
theseus::LoadingBarType::CheckingForUpdates,
|
||||
1.0,
|
||||
"Checking for updates...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let update = update_fut.await;
|
||||
|
||||
drop(check_bar);
|
||||
|
||||
if let Some(update) = update.ok().flatten() {
|
||||
tracing::info!("Update found: {:?}", update.download_url);
|
||||
let loader_bar_id = theseus::init_loading(
|
||||
theseus::LoadingBarType::LauncherUpdate {
|
||||
version: update.version.clone(),
|
||||
current_version: update.current_version.clone(),
|
||||
},
|
||||
1.0,
|
||||
"Updating Modrinth App...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 100 MiB
|
||||
const DEFAULT_CONTENT_LENGTH: u64 = 1024 * 1024 * 100;
|
||||
|
||||
update
|
||||
.download_and_install(
|
||||
|chunk_length, content_length| {
|
||||
let _ = theseus::emit_loading(
|
||||
&loader_bar_id,
|
||||
(chunk_length as f64)
|
||||
/ (content_length
|
||||
.unwrap_or(DEFAULT_CONTENT_LENGTH)
|
||||
as f64),
|
||||
None,
|
||||
);
|
||||
},
|
||||
|| {},
|
||||
)
|
||||
.await?;
|
||||
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "updater"))]
|
||||
{
|
||||
State::init().await?;
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir(), true)?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir().join("icons"), true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Should be call once Vue has mounted the app
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
fn show_window(app: tauri::AppHandle) {
|
||||
let win = app.get_window("main").unwrap();
|
||||
if let Err(e) = win.show() {
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text(&format!(
|
||||
"Cannot display application window due to an error:\n{}",
|
||||
e
|
||||
))
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
panic!("cannot display application window")
|
||||
} else {
|
||||
let _ = win.set_focus();
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn is_dev() -> bool {
|
||||
cfg!(debug_assertions)
|
||||
}
|
||||
|
||||
// Toggles decorations
|
||||
#[tauri::command]
|
||||
async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
|
||||
window.set_decorations(b).map_err(|e| {
|
||||
theseus::Error::from(theseus::ErrorKind::OtherError(format!(
|
||||
"Failed to toggle decorations: {}",
|
||||
e
|
||||
)))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
/*
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
eg. RUST_LOG=info will show info, warn, and error logs
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
RUST_LOG="theseus=trace" will show *all* messages but from theseus only (and not dependencies using similar crates)
|
||||
|
||||
Error messages returned to Tauri will display as traced error logs if they return an error.
|
||||
This will also include an attached span trace if the error is from a tracing error, and the level is set to info, debug, or trace
|
||||
|
||||
on unix:
|
||||
RUST_LOG="theseus=trace" {run command}
|
||||
|
||||
*/
|
||||
let _log_guard = theseus::start_logger();
|
||||
|
||||
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
||||
}
|
||||
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||
if let Some(payload) = args.get(1) {
|
||||
tracing::info!("Handling deep link from arg {payload}");
|
||||
let payload = payload.clone();
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
payload,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(win) = app.get_window("main") {
|
||||
let _ = win.set_focus();
|
||||
}
|
||||
}))
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::default()
|
||||
.with_filename("app-window-state.json")
|
||||
.build(),
|
||||
)
|
||||
.setup(|app| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let payload = macos::deep_link::get_or_init_payload(app);
|
||||
|
||||
let mtx_copy = payload.payload.clone();
|
||||
app.listen("deep-link://new-url", move |url| {
|
||||
let mtx_copy_copy = mtx_copy.clone();
|
||||
let request = url.payload().to_owned();
|
||||
|
||||
let actual_request =
|
||||
serde_json::from_str::<Vec<String>>(&request)
|
||||
.ok()
|
||||
.map(|mut x| x.remove(0))
|
||||
.unwrap_or(request);
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tracing::info!("Handling deep link {actual_request}");
|
||||
|
||||
let mut payload = mtx_copy_copy.lock().await;
|
||||
if payload.is_none() {
|
||||
*payload = Some(actual_request.clone());
|
||||
}
|
||||
|
||||
let _ =
|
||||
api::utils::handle_command(actual_request).await;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
app.listen("deep-link://new-url", |url| {
|
||||
let payload = url.payload().to_owned();
|
||||
tracing::info!("Handling deep link {payload}");
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
payload,
|
||||
));
|
||||
dbg!(url);
|
||||
});
|
||||
|
||||
if let Some(window) = app.get_window("main") {
|
||||
// Hide window to prevent white flash on startup
|
||||
let _ = window.hide();
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
window.set_shadow(true).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
builder = builder
|
||||
.plugin(api::auth::init())
|
||||
.plugin(api::mr_auth::init())
|
||||
.plugin(api::import::init())
|
||||
.plugin(api::logs::init())
|
||||
.plugin(api::jre::init())
|
||||
.plugin(api::metadata::init())
|
||||
.plugin(api::pack::init())
|
||||
.plugin(api::process::init())
|
||||
.plugin(api::profile::init())
|
||||
.plugin(api::profile_create::init())
|
||||
.plugin(api::settings::init())
|
||||
.plugin(api::tags::init())
|
||||
.plugin(api::utils::init())
|
||||
.plugin(api::cache::init())
|
||||
.plugin(api::ads::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
initialize_state,
|
||||
is_dev,
|
||||
toggle_decorations,
|
||||
api::mr_auth::modrinth_auth_login,
|
||||
show_window,
|
||||
restart_app,
|
||||
]);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
builder = builder.plugin(macos::window_ext::init());
|
||||
}
|
||||
|
||||
let app = builder.build(tauri::generate_context!());
|
||||
|
||||
match app {
|
||||
Ok(app) => {
|
||||
#[allow(unused_variables)]
|
||||
app.run(|app, event| {
|
||||
#[cfg(target_os = "macos")]
|
||||
if let tauri::RunEvent::Opened { urls } = event {
|
||||
tracing::info!("Handling webview open {urls:?}");
|
||||
|
||||
let file = urls
|
||||
.into_iter()
|
||||
.filter_map(|url| url.to_file_path().ok())
|
||||
.next();
|
||||
|
||||
if let Some(file) = file {
|
||||
let payload =
|
||||
macos::deep_link::get_or_init_payload(app);
|
||||
|
||||
let mtx_copy = payload.payload.clone();
|
||||
let request = file.to_string_lossy().to_string();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let mut payload = mtx_copy.lock().await;
|
||||
if payload.is_none() {
|
||||
*payload = Some(request.clone());
|
||||
}
|
||||
|
||||
let _ = api::utils::handle_command(request).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
|
||||
if format!("{:?}", e).contains(
|
||||
"Runtime(CreateWebview(WebView2Error(WindowsError",
|
||||
) {
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
|
||||
panic!("webview2 initialization failed")
|
||||
}
|
||||
}
|
||||
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text(&format!(
|
||||
"Cannot initialize application due to an error:\n{:?}",
|
||||
e
|
||||
))
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
|
||||
panic!("{1}: {:?}", e, "error while running tauri application")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": "v1Compatible"
|
||||
},
|
||||
"build": {
|
||||
"features": ["updater"]
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"capabilities": ["ads", "core", "plugins", "updater"]
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
|
||||
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm turbo run dev --filter=@modrinth/app-frontend",
|
||||
"beforeBuildCommand": "pnpm turbo run build --filter=@modrinth/app-frontend",
|
||||
"frontendDist": "../app-frontend/dist",
|
||||
"devUrl": "http://localhost:1420"
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "Game",
|
||||
"copyright": "",
|
||||
"targets": "all",
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com",
|
||||
"nsis": {
|
||||
"installMode": "perMachine",
|
||||
"installerHooks": "./nsis/hooks.nsi"
|
||||
}
|
||||
},
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": "App.entitlements",
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": []
|
||||
}
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": ["mrpack"],
|
||||
"mimeType": "application/zip+mrpack"
|
||||
}
|
||||
]
|
||||
},
|
||||
"productName": "Modrinth App",
|
||||
"version": "0.8.9",
|
||||
"mainBinaryName": "Modrinth App",
|
||||
"identifier": "ModrinthApp",
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["modrinth"]
|
||||
},
|
||||
"mobile": []
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"macOSPrivateApi": true,
|
||||
"windows": [
|
||||
{
|
||||
"titleBarStyle": "Overlay",
|
||||
"hiddenTitle": true,
|
||||
"fullscreen": false,
|
||||
"height": 800,
|
||||
"resizable": true,
|
||||
"title": "Modrinth App",
|
||||
"width": 1280,
|
||||
"minHeight": 750,
|
||||
"minWidth": 1100,
|
||||
"visible": false,
|
||||
"zoomHotkeysEnabled": false,
|
||||
"decorations": false
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"assetProtocol": {
|
||||
"scope": [
|
||||
"$APPDATA/caches/icons/*",
|
||||
"$APPCONFIG/caches/icons/*",
|
||||
"$CONFIG/caches/icons/*"
|
||||
],
|
||||
"enable": true
|
||||
},
|
||||
"capabilities": ["ads", "core", "plugins"],
|
||||
"csp": {
|
||||
"default-src": "'self' customprotocol: asset:",
|
||||
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://*.cloudflare.com https://api.mclo.gs https://cmp.inmobi.com",
|
||||
"font-src": [
|
||||
"https://cdn-raw.modrinth.com/fonts/inter/"
|
||||
],
|
||||
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost blob: data:",
|
||||
"style-src": "'unsafe-inline' 'self'",
|
||||
"script-src": "https://cmp.inmobi.com https://*.cloudflare.com https://*.posthog.com 'self'",
|
||||
"frame-src": "https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"mainBinaryName": "ModrinthApp"
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
BASE_URL=https://api.modrinth.com/v2/
|
||||
BROWSER_BASE_URL=https://api.modrinth.com/v2/
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["../../packages/eslint-config-custom/nuxt.js"],
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
shamefully-hoist=true
|
||||
strict-peer-dependencies=false
|
||||
@@ -1,4 +0,0 @@
|
||||
**/.nuxt
|
||||
**/dist
|
||||
**/.output
|
||||
src/generated/**
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"endOfLine": "auto",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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
|
||||
|
||||
The use of Modrinth branding elements, including but not limited to the wrench-in-labyrinth logo, the landing image, and any variations thereof, is strictly prohibited without explicit written permission from Rinth, Inc. This includes trademarks, logos, or other branding elements.
|
||||
|
||||
> All rights reserved. © 2020-2024 Rinth, Inc.
|
||||
|
||||
This includes, but may not be limited to, the following files:
|
||||
|
||||
- assets/images/404.svg
|
||||
- assets/images/logo.svg
|
||||
- components/brand/\*
|
||||
- static/favicon.ico
|
||||
- static/favicon-light.ico
|
||||
|
||||
## External logos
|
||||
|
||||
The following files are owned by their respective copyright holders and must be used within each of their Brand Guidelines:
|
||||
|
||||
- assets/images/external/\*
|
||||
@@ -1,661 +0,0 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -1,27 +0,0 @@
|
||||
# 
|
||||
|
||||
# Modrinth Web Interface
|
||||
|
||||
Welcome to the Modrinth Web Interface! This web-based platform is designed for discovering, downloading, and managing Minecraft mods.
|
||||
|
||||
If you're not a developer and you've stumbled upon this repository, you can access the web interface on the [Modrinth website](https://modrinth.com).
|
||||
|
||||
## Development
|
||||
|
||||
### Pre-requisites
|
||||
|
||||
Before you begin, ensure you have the following installed on your machine:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/)
|
||||
- [pnpm](https://pnpm.io/)
|
||||
|
||||
### Setup
|
||||
|
||||
Follow these steps to set up your development environment:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm web:dev
|
||||
```
|
||||
|
||||
You should now have a development build of the web interface running with hot-reloading enabled. Any changes you make to the code will automatically refresh the browser.
|
||||
@@ -1,8 +0,0 @@
|
||||
project_id: 518556
|
||||
preserve_hierarchy: true
|
||||
commit_message: "[ci skip]"
|
||||
|
||||
files:
|
||||
- source: /locales/en-US/*
|
||||
dest: /%original_file_name%
|
||||
translation: /locales/%locale%/%original_file_name%
|
||||
@@ -1,467 +0,0 @@
|
||||
import { promises as fs } from "fs";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import svgLoader from "vite-svg-loader";
|
||||
import { resolve, basename, relative } from "pathe";
|
||||
import { defineNuxtConfig } from "nuxt/config";
|
||||
import { $fetch } from "ofetch";
|
||||
import { globIterate } from "glob";
|
||||
import { match as matchLocale } from "@formatjs/intl-localematcher";
|
||||
import { consola } from "consola";
|
||||
|
||||
const STAGING_API_URL = "https://staging-api.modrinth.com/v2/";
|
||||
|
||||
const preloadedFonts = [
|
||||
"inter/Inter-Regular.woff2",
|
||||
"inter/Inter-Medium.woff2",
|
||||
"inter/Inter-SemiBold.woff2",
|
||||
"inter/Inter-Bold.woff2",
|
||||
];
|
||||
|
||||
const favicons = {
|
||||
"(prefers-color-scheme:no-preference)": "/favicon-light.ico",
|
||||
"(prefers-color-scheme:light)": "/favicon-light.ico",
|
||||
"(prefers-color-scheme:dark)": "/favicon.ico",
|
||||
};
|
||||
|
||||
/**
|
||||
* Tags of locales that are auto-discovered besides the default locale.
|
||||
*
|
||||
* Preferably only the locales that reach a certain threshold of complete
|
||||
* translations would be included in this array.
|
||||
*/
|
||||
const enabledLocales: string[] = [];
|
||||
|
||||
/**
|
||||
* Overrides for the categories of the certain locales.
|
||||
*/
|
||||
const localesCategoriesOverrides: Partial<Record<string, "fun" | "experimental">> = {
|
||||
"en-x-pirate": "fun",
|
||||
"en-x-updown": "fun",
|
||||
"en-x-lolcat": "fun",
|
||||
"en-x-uwu": "fun",
|
||||
"ru-x-bandit": "fun",
|
||||
ar: "experimental",
|
||||
he: "experimental",
|
||||
pes: "experimental",
|
||||
};
|
||||
|
||||
export default defineNuxtConfig({
|
||||
srcDir: "src/",
|
||||
app: {
|
||||
head: {
|
||||
htmlAttrs: {
|
||||
lang: "en",
|
||||
},
|
||||
title: "Modrinth",
|
||||
link: [
|
||||
// The type is necessary because the linter can't always compare this very nested/complex type on itself
|
||||
...preloadedFonts.map((font): object => {
|
||||
return {
|
||||
rel: "preload",
|
||||
href: `https://cdn-raw.modrinth.com/fonts/${font}?v=3.19`,
|
||||
as: "font",
|
||||
type: "font/woff2",
|
||||
crossorigin: "anonymous",
|
||||
};
|
||||
}),
|
||||
...Object.entries(favicons).map(([media, href]): object => {
|
||||
return { rel: "icon", type: "image/x-icon", href, media };
|
||||
}),
|
||||
...Object.entries(favicons).map(([media, href]): object => {
|
||||
return { rel: "apple-touch-icon", type: "image/x-icon", href, media, sizes: "64x64" };
|
||||
}),
|
||||
{
|
||||
rel: "search",
|
||||
type: "application/opensearchdescription+xml",
|
||||
href: "/opensearch.xml",
|
||||
title: "Modrinth mods",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
vite: {
|
||||
define: {
|
||||
global: {},
|
||||
},
|
||||
esbuild: {
|
||||
define: {
|
||||
global: "globalThis",
|
||||
},
|
||||
},
|
||||
cacheDir: "../../node_modules/.vite/apps/knossos",
|
||||
resolve: {
|
||||
dedupe: ["vue"],
|
||||
},
|
||||
plugins: [
|
||||
svgLoader({
|
||||
svgoConfig: {
|
||||
plugins: [
|
||||
{
|
||||
name: "preset-default",
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
hooks: {
|
||||
async "build:before"() {
|
||||
// 30 minutes
|
||||
const TTL = 30 * 60 * 1000;
|
||||
|
||||
let state: {
|
||||
lastGenerated?: string;
|
||||
apiUrl?: string;
|
||||
categories?: any[];
|
||||
loaders?: any[];
|
||||
gameVersions?: any[];
|
||||
donationPlatforms?: any[];
|
||||
reportTypes?: any[];
|
||||
homePageProjects?: any[];
|
||||
homePageSearch?: any[];
|
||||
homePageNotifs?: any[];
|
||||
products?: any[];
|
||||
} = {};
|
||||
|
||||
try {
|
||||
state = JSON.parse(await fs.readFile("./src/generated/state.json", "utf8"));
|
||||
} catch {
|
||||
// File doesn't exist, create folder
|
||||
await fs.mkdir("./src/generated", { recursive: true });
|
||||
}
|
||||
|
||||
const API_URL = getApiUrl();
|
||||
|
||||
if (
|
||||
// Skip regeneration if within TTL...
|
||||
state.lastGenerated &&
|
||||
new Date(state.lastGenerated).getTime() + TTL > new Date().getTime() &&
|
||||
// ...but only if the API URL is the same
|
||||
state.apiUrl === API_URL
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.lastGenerated = new Date().toISOString();
|
||||
|
||||
state.apiUrl = API_URL;
|
||||
|
||||
const headers = {
|
||||
headers: {
|
||||
"user-agent": "Knossos generator (support@modrinth.com)",
|
||||
},
|
||||
};
|
||||
|
||||
const [
|
||||
categories,
|
||||
loaders,
|
||||
gameVersions,
|
||||
donationPlatforms,
|
||||
reportTypes,
|
||||
homePageProjects,
|
||||
homePageSearch,
|
||||
homePageNotifs,
|
||||
products,
|
||||
] = await Promise.all([
|
||||
$fetch(`${API_URL}tag/category`, headers),
|
||||
$fetch(`${API_URL}tag/loader`, headers),
|
||||
$fetch(`${API_URL}tag/game_version`, headers),
|
||||
$fetch(`${API_URL}tag/donation_platform`, headers),
|
||||
$fetch(`${API_URL}tag/report_type`, headers),
|
||||
$fetch(`${API_URL}projects_random?count=60`, headers),
|
||||
$fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers),
|
||||
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers),
|
||||
// TODO: dehardcode
|
||||
$fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers),
|
||||
]);
|
||||
|
||||
state.categories = categories;
|
||||
state.loaders = loaders;
|
||||
state.gameVersions = gameVersions;
|
||||
state.donationPlatforms = donationPlatforms;
|
||||
state.reportTypes = reportTypes;
|
||||
state.homePageProjects = homePageProjects;
|
||||
state.homePageSearch = homePageSearch;
|
||||
state.homePageNotifs = homePageNotifs;
|
||||
state.products = products;
|
||||
|
||||
await fs.writeFile("./src/generated/state.json", JSON.stringify(state));
|
||||
|
||||
console.log("Tags generated!");
|
||||
},
|
||||
"pages:extend"(routes) {
|
||||
routes.splice(
|
||||
routes.findIndex((x) => x.name === "search-searchProjectType"),
|
||||
1,
|
||||
);
|
||||
|
||||
const types = ["mods", "modpacks", "plugins", "resourcepacks", "shaders", "datapacks"];
|
||||
|
||||
types.forEach((type) =>
|
||||
routes.push({
|
||||
name: `search-${type}`,
|
||||
path: `/${type}`,
|
||||
file: resolve(__dirname, "src/pages/search/[searchProjectType].vue"),
|
||||
children: [],
|
||||
}),
|
||||
);
|
||||
},
|
||||
async "vintl:extendOptions"(opts) {
|
||||
opts.locales ??= [];
|
||||
|
||||
const isProduction = getDomain() === "https://modrinth.com";
|
||||
|
||||
const resolveCompactNumberDataImport = await (async () => {
|
||||
const compactNumberLocales: string[] = [];
|
||||
|
||||
for await (const localeFile of globIterate(
|
||||
"node_modules/@vintl/compact-number/dist/locale-data/*.mjs",
|
||||
{ ignore: "**/*.data.mjs" },
|
||||
)) {
|
||||
const tag = basename(localeFile, ".mjs");
|
||||
compactNumberLocales.push(tag);
|
||||
}
|
||||
|
||||
function resolveImport(tag: string) {
|
||||
const matchedTag = matchLocale([tag], compactNumberLocales, "en-x-placeholder");
|
||||
return matchedTag === "en-x-placeholder"
|
||||
? undefined
|
||||
: `@vintl/compact-number/locale-data/${matchedTag}`;
|
||||
}
|
||||
|
||||
return resolveImport;
|
||||
})();
|
||||
|
||||
const resolveOmorphiaLocaleImport = await (async () => {
|
||||
const omorphiaLocales: string[] = [];
|
||||
const omorphiaLocaleSets = new Map<string, { files: { from: string }[] }>();
|
||||
|
||||
for await (const localeDir of globIterate("node_modules/@modrinth/ui/src/locales/*", {
|
||||
posix: true,
|
||||
})) {
|
||||
const tag = basename(localeDir);
|
||||
omorphiaLocales.push(tag);
|
||||
|
||||
const localeFiles: { from: string; format?: string }[] = [];
|
||||
|
||||
omorphiaLocaleSets.set(tag, { files: localeFiles });
|
||||
|
||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
||||
localeFiles.push({
|
||||
from: pathToFileURL(localeFile).toString(),
|
||||
format: "default",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return function resolveLocaleImport(tag: string) {
|
||||
return omorphiaLocaleSets.get(matchLocale([tag], omorphiaLocales, "en-x-placeholder"));
|
||||
};
|
||||
})();
|
||||
|
||||
for await (const localeDir of globIterate("src/locales/*/", { posix: true })) {
|
||||
const tag = basename(localeDir);
|
||||
if (isProduction && !enabledLocales.includes(tag) && opts.defaultLocale !== tag) continue;
|
||||
|
||||
const locale =
|
||||
opts.locales.find((locale) => locale.tag === tag) ??
|
||||
opts.locales[opts.locales.push({ tag }) - 1]!;
|
||||
|
||||
const localeFiles = (locale.files ??= []);
|
||||
|
||||
for await (const localeFile of globIterate(`${localeDir}/*`, { posix: true })) {
|
||||
const fileName = basename(localeFile);
|
||||
if (fileName === "index.json") {
|
||||
localeFiles.push({
|
||||
from: `./${relative("./src", localeFile)}`,
|
||||
format: "crowdin",
|
||||
});
|
||||
} else if (fileName === "meta.json") {
|
||||
const meta: Record<string, { message: string }> = await fs
|
||||
.readFile(localeFile, "utf8")
|
||||
.then((date) => JSON.parse(date));
|
||||
const localeMeta = (locale.meta ??= {});
|
||||
for (const key in meta) {
|
||||
const value = meta[key];
|
||||
if (value === undefined) continue;
|
||||
localeMeta[key] = value.message;
|
||||
}
|
||||
} else {
|
||||
(locale.resources ??= {})[fileName] = `./${relative("./src", localeFile)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const categoryOverride = localesCategoriesOverrides[tag];
|
||||
if (categoryOverride != null) {
|
||||
(locale.meta ??= {}).category = categoryOverride;
|
||||
}
|
||||
|
||||
const omorphiaLocaleData = resolveOmorphiaLocaleImport(tag);
|
||||
if (omorphiaLocaleData != null) {
|
||||
localeFiles.push(...omorphiaLocaleData.files);
|
||||
}
|
||||
|
||||
const cnDataImport = resolveCompactNumberDataImport(tag);
|
||||
if (cnDataImport != null) {
|
||||
(locale.additionalImports ??= []).push({
|
||||
from: cnDataImport,
|
||||
resolve: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
runtimeConfig: {
|
||||
// @ts-ignore
|
||||
apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
|
||||
// @ts-ignore
|
||||
rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
|
||||
public: {
|
||||
apiBaseUrl: getApiUrl(),
|
||||
siteUrl: getDomain(),
|
||||
production: isProduction(),
|
||||
featureFlagOverrides: getFeatureFlagOverrides(),
|
||||
|
||||
owner: process.env.VERCEL_GIT_REPO_OWNER || "modrinth",
|
||||
slug: process.env.VERCEL_GIT_REPO_SLUG || "code",
|
||||
branch:
|
||||
process.env.VERCEL_GIT_COMMIT_REF ||
|
||||
process.env.CF_PAGES_BRANCH ||
|
||||
// @ts-ignore
|
||||
globalThis.CF_PAGES_BRANCH ||
|
||||
"master",
|
||||
hash:
|
||||
process.env.VERCEL_GIT_COMMIT_SHA ||
|
||||
process.env.CF_PAGES_COMMIT_SHA ||
|
||||
// @ts-ignore
|
||||
globalThis.CF_PAGES_COMMIT_SHA ||
|
||||
"unknown",
|
||||
|
||||
turnstile: { siteKey: "0x4AAAAAAAW3guHM6Eunbgwu" },
|
||||
|
||||
stripePublishableKey:
|
||||
process.env.STRIPE_PUBLISHABLE_KEY ||
|
||||
globalThis.STRIPE_PUBLISHABLE_KEY ||
|
||||
"pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b",
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
shim: false,
|
||||
strict: true,
|
||||
typeCheck: false,
|
||||
tsConfig: {
|
||||
compilerOptions: {
|
||||
moduleResolution: "bundler",
|
||||
allowImportingTsExtensions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
modules: ["@vintl/nuxt", "@nuxtjs/turnstile"],
|
||||
vintl: {
|
||||
defaultLocale: "en-US",
|
||||
locales: [
|
||||
{
|
||||
tag: "en-US",
|
||||
meta: {
|
||||
static: {
|
||||
iso: "en",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
storage: "cookie",
|
||||
parserless: "only-prod",
|
||||
seo: {
|
||||
defaultLocaleHasParameter: false,
|
||||
},
|
||||
onParseError({ error, message, messageId, moduleId, parseMessage, parserOptions }) {
|
||||
const errorMessage = String(error);
|
||||
const modulePath = relative(__dirname, moduleId);
|
||||
|
||||
try {
|
||||
const fallback = parseMessage(message, { ...parserOptions, ignoreTag: true });
|
||||
|
||||
consola.warn(
|
||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed normally due to ${errorMessage}. The tags will will not be parsed.`,
|
||||
);
|
||||
|
||||
return fallback;
|
||||
} catch (err) {
|
||||
const secondaryErrorMessage = String(err);
|
||||
|
||||
const reason =
|
||||
errorMessage === secondaryErrorMessage
|
||||
? errorMessage
|
||||
: `${errorMessage} and ${secondaryErrorMessage}`;
|
||||
|
||||
consola.warn(
|
||||
`[i18n] ${messageId} in ${modulePath} cannot be parsed due to ${reason}. It will be skipped.`,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
nitro: {
|
||||
moduleSideEffects: ["@vintl/compact-number/locale-data"],
|
||||
},
|
||||
devtools: {
|
||||
enabled: true,
|
||||
},
|
||||
css: ["~/assets/styles/tailwind.css"],
|
||||
postcss: {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
},
|
||||
routeRules: {
|
||||
"/**": {
|
||||
headers: {
|
||||
"Accept-CH": "Sec-CH-Prefers-Color-Scheme",
|
||||
"Critical-CH": "Sec-CH-Prefers-Color-Scheme",
|
||||
},
|
||||
},
|
||||
},
|
||||
compatibilityDate: "2024-07-03",
|
||||
telemetry: false,
|
||||
});
|
||||
|
||||
function getApiUrl() {
|
||||
// @ts-ignore
|
||||
return process.env.BROWSER_BASE_URL ?? globalThis.BROWSER_BASE_URL ?? STAGING_API_URL;
|
||||
}
|
||||
|
||||
function isProduction() {
|
||||
return process.env.NODE_ENV === "production";
|
||||
}
|
||||
|
||||
function getFeatureFlagOverrides() {
|
||||
return JSON.parse(process.env.FLAG_OVERRIDES ?? "{}");
|
||||
}
|
||||
|
||||
function getDomain() {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
if (process.env.SITE_URL) {
|
||||
return process.env.SITE_URL;
|
||||
}
|
||||
// @ts-ignore
|
||||
else if (process.env.CF_PAGES_URL || globalThis.CF_PAGES_URL) {
|
||||
// @ts-ignore
|
||||
return process.env.CF_PAGES_URL ?? globalThis.CF_PAGES_URL;
|
||||
} else if (process.env.HEROKU_APP_NAME) {
|
||||
return `https://${process.env.HEROKU_APP_NAME}.herokuapp.com`;
|
||||
} else if (process.env.VERCEL_URL) {
|
||||
return `https://${process.env.VERCEL_URL}`;
|
||||
} else if (getApiUrl() === STAGING_API_URL) {
|
||||
return "https://staging.modrinth.com";
|
||||
} else {
|
||||
return "https://modrinth.com";
|
||||
}
|
||||
} else {
|
||||
return "http://localhost:3000";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user