import fixes
This commit is contained in:
parent
69bfc1a5a7
commit
61bf7b0cb7
8 changed files with 305 additions and 4 deletions
|
|
@ -3,7 +3,7 @@ import * as react from 'react'
|
|||
import { gt } from 'semver'
|
||||
import { proxy } from 'valtio'
|
||||
import { options } from './optionsStorage'
|
||||
import { getStoredValue, setStoredValue } from './react/storageProvider'
|
||||
import { appStorage } from './react/appStorageProvider'
|
||||
import { showOptionsModal } from './react/SelectOption'
|
||||
|
||||
// #region Database
|
||||
|
|
@ -243,11 +243,11 @@ const checkModsUpdates = async () => {
|
|||
|
||||
const refreshModRepositories = async () => {
|
||||
if (options.modsAutoUpdate === 'never') return
|
||||
const lastCheck = getStoredValue('modsAutoUpdateLastCheck')
|
||||
const lastCheck = appStorage.modsAutoUpdateLastCheck
|
||||
if (lastCheck && Date.now() - lastCheck < 1000 * 60 * 60 * options.modsUpdatePeriodCheck) return
|
||||
await fetchAllRepositories()
|
||||
// todo think of not updating check timestamp on offline access
|
||||
setStoredValue('modsAutoUpdateLastCheck', Date.now())
|
||||
appStorage.modsAutoUpdateLastCheck = Date.now()
|
||||
}
|
||||
|
||||
export const installModByName = async (repoUrl: string, name: string) => {
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ export const guiOptionsScheme: {
|
|||
{
|
||||
custom () {
|
||||
const modsUpdateSnapshot = useSnapshot(modsUpdateStatus)
|
||||
return <Button label={`Client Mods: ${Object.keys(window.loadedMods ?? {}).length} (${Object.keys(modsUpdateSnapshot).length})}`} onClick={() => showModal({ reactType: 'mods' })} inScreen />
|
||||
return <Button label={`Client Mods: ${Object.keys(window.loadedMods ?? {}).length} (${Object.keys(modsUpdateSnapshot).length})`} onClick={() => showModal({ reactType: 'mods' })} inScreen />
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -62,6 +62,9 @@ const defaultOptions = {
|
|||
// todo ui setting, maybe enable by default?
|
||||
waitForChunksRender: 'sp-only' as 'sp-only' | boolean,
|
||||
jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>,
|
||||
modsSupport: false,
|
||||
modsAutoUpdate: 'check' as 'check' | 'never' | 'always',
|
||||
modsUpdatePeriodCheck: 24, // hours
|
||||
|
||||
// antiAliasing: false,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,169 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { getAllModsDisplayList } from '../clientMods'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import Input from './Input'
|
||||
import Button from './Button'
|
||||
import styles from './mods.module.css'
|
||||
|
||||
type ModsData = Awaited<ReturnType<typeof getAllModsDisplayList>>
|
||||
|
||||
export default () => {
|
||||
const isModalActive = useIsModalActive('mods')
|
||||
const [modsData, setModsData] = useState<ModsData | null>(null)
|
||||
const [search, setSearch] = useState('')
|
||||
const [showOnlyInstalled, setShowOnlyInstalled] = useState(false)
|
||||
const [selectedMod, setSelectedMod] = useState<ModsData['repos'][0]['packages'][0] | null>(null)
|
||||
const [expandedRepos, setExpandedRepos] = useState<Record<string, boolean>>({})
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalActive) {
|
||||
void getAllModsDisplayList().then(setModsData)
|
||||
}
|
||||
}, [isModalActive])
|
||||
|
||||
if (!isModalActive) return null
|
||||
|
||||
const toggleRepo = (repoUrl: string) => {
|
||||
setExpandedRepos(prev => ({
|
||||
...prev,
|
||||
[repoUrl]: !prev[repoUrl]
|
||||
}))
|
||||
}
|
||||
|
||||
const filteredMods = modsData ? {
|
||||
repos: modsData.repos.map(repo => ({
|
||||
...repo,
|
||||
packages: repo.packages.filter(mod => {
|
||||
const matchesSearch = mod.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
mod.description?.toLowerCase().includes(search.toLowerCase())
|
||||
const matchesFilter = !showOnlyInstalled || mod.installed
|
||||
return matchesSearch && matchesFilter
|
||||
})
|
||||
})),
|
||||
modsWithoutRepos: modsData.modsWithoutRepos.filter(mod => {
|
||||
const matchesSearch = mod.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
mod.description?.toLowerCase().includes(search.toLowerCase())
|
||||
const matchesFilter = !showOnlyInstalled || mod.installed
|
||||
return matchesSearch && matchesFilter
|
||||
})
|
||||
} : null
|
||||
|
||||
return <div>
|
||||
<div className="dirt-bg" />
|
||||
<div className="fullscreen">
|
||||
<div className="screen-title">Client Mods</div>
|
||||
<div className={styles.root}>
|
||||
<div className={styles.header}>
|
||||
<Input
|
||||
className={styles.searchBar}
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
placeholder="Search mods..."
|
||||
/>
|
||||
<Button
|
||||
className={styles.filterButton}
|
||||
onClick={() => setShowOnlyInstalled(!showOnlyInstalled)}
|
||||
>
|
||||
{showOnlyInstalled ? 'Show All' : 'Show Installed'}
|
||||
</Button>
|
||||
<Button onClick={() => {}}>Manage Repos</Button>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.modList}>
|
||||
{filteredMods ? (
|
||||
<>
|
||||
{filteredMods.repos.map(repo => (
|
||||
<div key={repo.url}>
|
||||
<div
|
||||
className={styles.repoHeader}
|
||||
onClick={() => toggleRepo(repo.url)}
|
||||
>
|
||||
<span>{expandedRepos[repo.url] ? '▼' : '▶'}</span>
|
||||
<span>{repo.name || repo.url}</span>
|
||||
<span>({repo.packages.length})</span>
|
||||
</div>
|
||||
{expandedRepos[repo.url] && (
|
||||
<div className={styles.repoContent}>
|
||||
{repo.packages.map(mod => (
|
||||
<div
|
||||
key={mod.name}
|
||||
className={styles.modRow}
|
||||
onClick={() => setSelectedMod(mod)}
|
||||
>
|
||||
<div className={styles.modRowTitle}>{mod.name}</div>
|
||||
<div className={styles.modRowInfo}>
|
||||
{mod.description}
|
||||
{mod.author && ` • By ${mod.author}`}
|
||||
{mod.version && ` • v${mod.version}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{filteredMods.modsWithoutRepos.length > 0 && (
|
||||
<div>
|
||||
<div className={styles.repoHeader}>
|
||||
<span>▼</span>
|
||||
<span>Other Mods</span>
|
||||
<span>({filteredMods.modsWithoutRepos.length})</span>
|
||||
</div>
|
||||
<div className={styles.repoContent}>
|
||||
{filteredMods.modsWithoutRepos.map(mod => (
|
||||
<div
|
||||
key={mod.name}
|
||||
className={styles.modRow}
|
||||
onClick={() => setSelectedMod(mod)}
|
||||
>
|
||||
<div className={styles.modRowTitle}>{mod.name}</div>
|
||||
<div className={styles.modRowInfo}>
|
||||
{mod.description}
|
||||
{mod.author && ` • By ${mod.author}`}
|
||||
{mod.version && ` • v${mod.version}`}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className={styles.modRowInfo}>Loading mods...</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.sidebar}>
|
||||
{selectedMod ? (
|
||||
<>
|
||||
<div className={styles.modInfo}>
|
||||
<div className={styles.modInfoTitle}>{selectedMod.name}</div>
|
||||
<div className={styles.modInfoText}>
|
||||
{selectedMod.description}
|
||||
</div>
|
||||
<div className={styles.modInfoText}>
|
||||
{selectedMod.author && `Author: ${selectedMod.author}`}
|
||||
{selectedMod.version && `\nVersion: ${selectedMod.version}`}
|
||||
{selectedMod.section && `\nSection: ${selectedMod.section}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.modActions}>
|
||||
<Button onClick={() => {}}>
|
||||
{selectedMod.installed ? 'Uninstall' : 'Install'}
|
||||
</Button>
|
||||
{selectedMod.installed && (
|
||||
<>
|
||||
<Button onClick={() => {}}>Update</Button>
|
||||
<Button onClick={() => {}}>Delete</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className={styles.modInfoText}>Select a mod to view details</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ type StorageData = {
|
|||
serversHistory: ServerHistoryEntry[]
|
||||
authenticatedAccounts: AuthenticatedAccount[]
|
||||
serversList: StoreServerItem[] | undefined
|
||||
modsAutoUpdateLastCheck: number | undefined
|
||||
}
|
||||
|
||||
const oldKeysAliases: Partial<Record<keyof StorageData, string>> = {
|
||||
|
|
@ -76,6 +77,7 @@ const defaultStorageData: StorageData = {
|
|||
serversHistory: [],
|
||||
authenticatedAccounts: [],
|
||||
serversList: undefined,
|
||||
modsAutoUpdateLastCheck: undefined,
|
||||
}
|
||||
|
||||
export const setStorageDataOnAppConfigLoad = () => {
|
||||
|
|
|
|||
117
src/react/mods.module.css
Normal file
117
src/react/mods.module.css
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.filterButton {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modList {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.modInfoTitle {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.modInfoText {
|
||||
font-size: 10px;
|
||||
color: #bcbcbc;
|
||||
}
|
||||
|
||||
.modActions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.modRow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modRow:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.modRowTitle {
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.modRowInfo {
|
||||
font-size: 10px;
|
||||
color: #bcbcbc;
|
||||
}
|
||||
|
||||
.repoHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #bcbcbc;
|
||||
font-size: 8px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.repoHeader:hover {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.repoContent {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
22
src/react/mods.module.css.d.ts
vendored
Normal file
22
src/react/mods.module.css.d.ts
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
content: string;
|
||||
filterButton: string;
|
||||
header: string;
|
||||
modActions: string;
|
||||
modInfo: string;
|
||||
modInfoText: string;
|
||||
modInfoTitle: string;
|
||||
modList: string;
|
||||
modRow: string;
|
||||
modRowInfo: string;
|
||||
modRowTitle: string;
|
||||
repoContent: string;
|
||||
repoHeader: string;
|
||||
root: string;
|
||||
searchBar: string;
|
||||
sidebar: string;
|
||||
}
|
||||
declare const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
|
@ -211,6 +211,7 @@ const App = () => {
|
|||
<NoModalFoundProvider />
|
||||
<PacketsReplayProvider />
|
||||
<NotificationProvider />
|
||||
<ModsPage />
|
||||
</RobustPortal>
|
||||
<RobustPortal to={document.body}>
|
||||
<div className='overlay-top-scaled'>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue