feat: allow to load client without free space on device (or no write permissions)

This commit is contained in:
Vitaly Turovsky 2025-02-26 22:56:02 +03:00
commit edad57a225
5 changed files with 31 additions and 7 deletions

View file

@ -14,22 +14,41 @@ import { VALID_REPLAY_EXTENSIONS, openFile } from './packetsReplay/replayPackets
import { getFixedFilesize } from './downloadAndOpenFile'
import { packetsReplayState } from './react/state/packetsReplayState'
import { createFullScreenProgressReporter } from './core/progressReporter'
import { showNotification } from './react/NotificationProvider'
const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive')
browserfs.install(window)
const defaultMountablePoints = {
'/world': { fs: 'LocalStorage' }, // will be removed in future
'/data': { fs: 'IndexedDB' },
'/resourcepack': { fs: 'InMemory' }, // temporary storage for currently loaded resource pack
'/temp': { fs: 'InMemory' }
}
const fallbackMountablePoints = {
'/resourcepack': { fs: 'InMemory' }, // temporary storage for downloaded server resource pack
'/temp': { fs: 'InMemory' }
}
browserfs.configure({
fs: 'MountableFileSystem',
options: defaultMountablePoints,
}, async (e) => {
// todo disable singleplayer button
if (e) throw e
if (e) {
browserfs.configure({
fs: 'MountableFileSystem',
options: fallbackMountablePoints,
}, async (e2) => {
if (e2) {
showNotification('Unknown FS error, cannot continue', e2.message, true)
throw e2
}
showNotification('Failed to access device storage', `Check you have free space. ${e.message}`, true)
miscUiState.appLoaded = true
miscUiState.singleplayerAvailable = false
})
return
}
await updateTexturePackInstalledState()
miscUiState.appLoaded = true
miscUiState.singleplayerAvailable = true
})
export const forceCachedDataPaths = {}

View file

@ -64,7 +64,7 @@ async function handleDroppedFile (file: File) {
return
}
if (file.name.endsWith('.mca')) {
const tempPath = '/data/temp.mca'
const tempPath = '/temp/temp.mca'
try {
await fs.promises.writeFile(tempPath, Buffer.from(await file.arrayBuffer()) as any)
const region = new RegionFile(tempPath)

View file

@ -145,6 +145,7 @@ export const miscUiState = proxy({
/** currently trying to load or loaded mc version, after all data is loaded */
loadedDataVersion: null as string | null,
appLoaded: false,
singleplayerAvailable: false,
usingGamepadInput: false,
appConfig: null as AppConfig | null,
displaySearchInput: false,

View file

@ -24,6 +24,7 @@ interface Props {
bottomRightLinks?: string
versionText?: string
onVersionTextClick?: () => void
singleplayerAvailable?: boolean
}
const httpsRegex = /^https?:\/\//
@ -41,7 +42,8 @@ export default ({
versionStatus,
versionTitle,
onVersionStatusClick,
bottomRightLinks
bottomRightLinks,
singleplayerAvailable = true
}: Props) => {
if (!bottomRightLinks?.trim()) bottomRightLinks = undefined
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
@ -107,6 +109,7 @@ export default ({
style={{ width: 150 }}
{...singleplayerLongPress}
data-test-id='singleplayer-button'
disabled={!singleplayerAvailable}
initialTooltip={{
content: 'Create worlds and play offline',
placement: 'left',
@ -183,7 +186,7 @@ export default ({
</div>
})}
</div>
<span>A Minecraft client in the browser!</span>
<span>A Minecraft client clone in the browser!</span>
</span>
</div>
</div>

View file

@ -75,7 +75,7 @@ export const mainMenuState = proxy({
let disableAnimation = false
export default () => {
const haveModals = useSnapshot(activeModalStack).length
const { gameLoaded, appLoaded, appConfig } = useSnapshot(miscUiState)
const { gameLoaded, appLoaded, appConfig, singleplayerAvailable } = useSnapshot(miscUiState)
const noDisplay = haveModals || gameLoaded || !appLoaded
@ -118,6 +118,7 @@ export default () => {
return <Transition in={!noDisplay} timeout={disableAnimation ? 0 : 100} mountOnEnter unmountOnExit>
{(state) => <div style={{ transition: state === 'exiting' || disableAnimation ? '' : '100ms opacity ease-in', ...state === 'entered' ? { opacity: 1 } : { opacity: 0 } }}>
<MainMenu
singleplayerAvailable={singleplayerAvailable}
connectToServerAction={() => showModal({ reactType: 'serversList' })}
singleplayerAction={async () => {
const oldFormatSave = fs.existsSync('./world/level.dat')