add storage quota display
This commit is contained in:
Vendored
+90
-8
@@ -306,12 +306,13 @@
|
||||
applyButton: "Apply",
|
||||
backButton: "Back",
|
||||
cancelButton: "Cancel",
|
||||
confirmButton: "Confirm",
|
||||
continueButton: "Continue",
|
||||
closeButton: "Close",
|
||||
deleteButton: "Delete",
|
||||
exitButton: "Exit",
|
||||
fullscreenButton: "Fullscreen",
|
||||
continueButton: "Continue",
|
||||
confirmButton: "Confirm",
|
||||
refreshButton: "Refresh",
|
||||
saveButton: "Save",
|
||||
setButton: "Set",
|
||||
filterOrCreateLabel: "Search or create",
|
||||
@@ -424,6 +425,8 @@
|
||||
noItemSelected: "No item selected",
|
||||
notAFile: "(not a file)",
|
||||
contentEmpty: "(empty)",
|
||||
usageLabel: "Usage",
|
||||
usageVauleLabel: (used, max) => `${used} of ${max} MB`,
|
||||
path: "Path",
|
||||
content: "Content",
|
||||
deleteItem: "Delete item",
|
||||
@@ -551,12 +554,13 @@
|
||||
applyButton: "Anwenden",
|
||||
backButton: "Zur\xFCck",
|
||||
cancelButton: "Abbrechen",
|
||||
continueButton: "Weiter",
|
||||
confirmButton: "Best\xE4tigen",
|
||||
closeButton: "Schlie\xDFen",
|
||||
deleteButton: "L\xF6schen",
|
||||
exitButton: "Schlie\xDFen",
|
||||
fullscreenButton: "Vollbild",
|
||||
continueButton: "Weiter",
|
||||
confirmButton: "Best\xE4tigen",
|
||||
refreshButton: "Aktualisieren",
|
||||
saveButton: "Speichern",
|
||||
setButton: "OK",
|
||||
filterOrCreateLabel: "Suchen oder erstellen",
|
||||
@@ -661,6 +665,8 @@
|
||||
noItemSelected: "Kein Element ausgew\xE4hlt",
|
||||
notAFile: "(keine Datei)",
|
||||
contentEmpty: "(leer)",
|
||||
usageLabel: "Speicher",
|
||||
usageVauleLabel: (used, max) => `${used} von ${max} MB belegt`,
|
||||
path: "Pfad",
|
||||
content: "Inhalt",
|
||||
deleteItem: "Element l\xF6schen",
|
||||
@@ -776,12 +782,13 @@
|
||||
applyButton: "Guardar",
|
||||
backButton: "Atr\xE1s",
|
||||
cancelButton: "Cancelar",
|
||||
continueButton: "Continuar",
|
||||
confirmButton: "Confirmar",
|
||||
closeButton: "Cerrar",
|
||||
deleteButton: "Borrar",
|
||||
exitButton: "Salir",
|
||||
fullscreenButton: "Pantalla completa",
|
||||
continueButton: "Continuar",
|
||||
confirmButton: "Confirmar",
|
||||
refreshButton: "Actualizar",
|
||||
saveButton: "Guardar",
|
||||
setButton: "OK",
|
||||
filterOrCreateLabel: "Buscar o crear",
|
||||
@@ -886,6 +893,8 @@
|
||||
noItemSelected: "Ning\xFAn elemento seleccionado",
|
||||
notAFile: "(no es un archivo)",
|
||||
contentEmpty: "(vac\xEDo)",
|
||||
usageLabel: "Almacenamiento",
|
||||
usageVauleLabel: (used, max) => `${used} de ${max} MB en uso`,
|
||||
path: "Ruta",
|
||||
content: "Contenido",
|
||||
deleteItem: "Eliminar elemento",
|
||||
@@ -1121,6 +1130,9 @@
|
||||
if (value != null) localStorage.setItem(`_${key}`, value);
|
||||
return value;
|
||||
}
|
||||
function bytesToMB(bytes) {
|
||||
return bytes / (1024 * 1024);
|
||||
}
|
||||
function stringify(data) {
|
||||
return JSON.stringify(data, null, 4);
|
||||
}
|
||||
@@ -1607,13 +1619,41 @@
|
||||
if (object == null) return null;
|
||||
return object;
|
||||
};
|
||||
// cleaning
|
||||
// cleaning & usage
|
||||
this.removeJunk = () => {
|
||||
this.recurse([], (path) => {
|
||||
if (path[0] == DATA_VERSION) return;
|
||||
this.remove(path);
|
||||
});
|
||||
};
|
||||
this.determineCapacity = () => {
|
||||
const KEY = "storage-capacity-check";
|
||||
let chunk = "0";
|
||||
for (let i = 0; i < 1024 * 256; i++) {
|
||||
chunk += "0";
|
||||
}
|
||||
localStorage.setItem(KEY, "");
|
||||
while (true) {
|
||||
try {
|
||||
const current = localStorage.getItem(KEY);
|
||||
console.log(current);
|
||||
localStorage.setItem(KEY, current + chunk);
|
||||
} catch (e) {
|
||||
const capacity = this.calculateUsage();
|
||||
localStorage.removeItem(KEY);
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
};
|
||||
this.calculateUsage = () => {
|
||||
let data = JSON.stringify(localStorage);
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(data);
|
||||
const bytes = encoded.byteLength;
|
||||
const mb = bytesToMB(bytes);
|
||||
const rounded = Math.round(mb * 100) / 100;
|
||||
return rounded;
|
||||
};
|
||||
// tree
|
||||
this.initializeTree = () => {
|
||||
console.log("initializing tree");
|
||||
@@ -1721,6 +1761,8 @@
|
||||
PATH_COMPONENT_SEPARATOR
|
||||
);
|
||||
this.didMakeChanges = new State(false);
|
||||
this.occupiedSpaceMB = new State(0);
|
||||
this.maximumSpaceMB = new State(0);
|
||||
this.lastDeletedItemPath = new State("");
|
||||
// methods
|
||||
this.getSelectedItemContent = () => {
|
||||
@@ -1742,10 +1784,16 @@
|
||||
this.coreViewModel.storageModel.removeJunk();
|
||||
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
|
||||
};
|
||||
this.calculateUsage = () => {
|
||||
this.occupiedSpaceMB.value = this.coreViewModel.storageModel.calculateUsage();
|
||||
this.maximumSpaceMB.value = this.coreViewModel.storageModel.determineCapacity();
|
||||
console.log(this.occupiedSpaceMB.value, this.maximumSpaceMB.value);
|
||||
};
|
||||
// view
|
||||
this.showStorageModal = () => {
|
||||
this.coreViewModel.context = this;
|
||||
this.isShowingStorageModal.value = true;
|
||||
this.calculateUsage();
|
||||
};
|
||||
// exit
|
||||
this.close = () => {
|
||||
@@ -7125,13 +7173,47 @@
|
||||
));
|
||||
}
|
||||
|
||||
// src/View/Components/usageBar.tsx
|
||||
function UsageBar(label, valueLabel, value, maximum) {
|
||||
const style = createProxyState([value, maximum], () => `width: ${100 * (value.value / maximum.value)}%`);
|
||||
return /* @__PURE__ */ createElement(
|
||||
"div",
|
||||
{
|
||||
class: "surface flex-column padding gap"
|
||||
},
|
||||
/* @__PURE__ */ createElement("b", null, label),
|
||||
/* @__PURE__ */ createElement(
|
||||
"div",
|
||||
{
|
||||
class: "width-100 surface-alt",
|
||||
style: "height: 18px"
|
||||
},
|
||||
/* @__PURE__ */ createElement(
|
||||
"div",
|
||||
{
|
||||
class: "height-100 background-primary",
|
||||
"set:style": style
|
||||
}
|
||||
)
|
||||
),
|
||||
/* @__PURE__ */ createElement(
|
||||
"span",
|
||||
{
|
||||
class: "secondary",
|
||||
"subscribe:innerText": valueLabel
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// src/View/Modals/storageModal.tsx
|
||||
function StorageModal(coreViewModel2, storageViewModel2) {
|
||||
const usageValueLabel = createProxyState([storageViewModel2.occupiedSpaceMB, storageViewModel2.maximumSpaceMB], () => coreViewModel2.translations.storage.usageVauleLabel(storageViewModel2.occupiedSpaceMB.value, storageViewModel2.maximumSpaceMB.value));
|
||||
const detailView = createProxyState(
|
||||
[storageViewModel2.selectedPath],
|
||||
() => {
|
||||
if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
||||
return /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), DangerousActionButton(
|
||||
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("span", { class: "secondary" }, coreViewModel2.translations.storage.noItemSelected), /* @__PURE__ */ createElement("hr", null), UsageBar(coreViewModel2.translations.storage.usageLabel, usageValueLabel, storageViewModel2.occupiedSpaceMB, storageViewModel2.maximumSpaceMB), /* @__PURE__ */ createElement("hr", null), DangerousActionButton(
|
||||
coreViewModel2,
|
||||
coreViewModel2.translations.storage.removeJunkButton,
|
||||
"delete_forever",
|
||||
|
||||
Vendored
+4
@@ -145,6 +145,10 @@ a {
|
||||
color: var(--link);
|
||||
}
|
||||
|
||||
.background-primary {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
/* text */
|
||||
|
||||
.success {
|
||||
|
||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
const VERSION = "2610 Beta 1"
|
||||
const VERSION = "2610 Beta 2"
|
||||
|
||||
function processUrl(event) {
|
||||
const url = event.request.url;
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
2610 Beta 1
|
||||
2610 Beta 2
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
// cleanup: Phase A
|
||||
|
||||
import { localeCompare, parseValidObject, stringify } from "../Utility/utility";
|
||||
import { bytesToMB, localeCompare, parseValidObject, stringify } from "../Utility/utility";
|
||||
import { DATA_VERSION, ValidObject } from "../Utility/typeSafety";
|
||||
|
||||
export const PATH_COMPONENT_SEPARATOR = "\\";
|
||||
@@ -144,7 +143,7 @@ export default class StorageModel {
|
||||
return object;
|
||||
};
|
||||
|
||||
// cleaning
|
||||
// cleaning & usage
|
||||
readonly removeJunk = (): void => {
|
||||
this.recurse([], (path: string[]) => {
|
||||
if (path[0] == DATA_VERSION) return;
|
||||
@@ -152,6 +151,38 @@ export default class StorageModel {
|
||||
});
|
||||
};
|
||||
|
||||
readonly determineCapacity = (): number => {
|
||||
const KEY = "storage-capacity-check";
|
||||
|
||||
let chunk = "0";
|
||||
for (let i = 0; i < 1024*256; i++) {
|
||||
chunk += "0";
|
||||
}
|
||||
|
||||
localStorage.setItem(KEY, "");
|
||||
while (true) {
|
||||
try {
|
||||
const current = localStorage.getItem(KEY);
|
||||
console.log(current);
|
||||
localStorage.setItem(KEY, current + chunk);
|
||||
} catch (e) {
|
||||
const capacity = this.calculateUsage();
|
||||
localStorage.removeItem(KEY);
|
||||
return capacity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly calculateUsage = (): number => {
|
||||
let data = JSON.stringify(localStorage);
|
||||
const encoder = new TextEncoder();
|
||||
const encoded = encoder.encode(data);
|
||||
const bytes = encoded.byteLength;
|
||||
const mb = bytesToMB(bytes);
|
||||
const rounded = Math.round(mb * 100)/100;
|
||||
return rounded;
|
||||
}
|
||||
|
||||
// tree
|
||||
readonly initializeTree = (): void => {
|
||||
console.log("initializing tree");
|
||||
|
||||
@@ -177,6 +177,10 @@ export function getLocalStorageItemAndClear(key: string): string | null {
|
||||
return value;
|
||||
}
|
||||
|
||||
export function bytesToMB(bytes: number): number {
|
||||
return bytes / (1024*1024);
|
||||
}
|
||||
|
||||
// string & parsing
|
||||
export function stringify(data: any): string {
|
||||
return JSON.stringify(data, null, 4);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import * as React from "../../react";
|
||||
|
||||
export function UsageBar(label: string, valueLabel: React.State<string>, value: React.State<number>, maximum: React.State<number>) {
|
||||
const style = React.createProxyState([value, maximum], ()=>`width: ${100*(value.value/maximum.value)}%`)
|
||||
|
||||
return (
|
||||
<div
|
||||
class="surface flex-column padding gap"
|
||||
>
|
||||
<b>{label}</b>
|
||||
<div
|
||||
class="width-100 surface-alt"
|
||||
style="height: 18px"
|
||||
>
|
||||
<div
|
||||
class="height-100 background-primary"
|
||||
set:style={style}
|
||||
></div>
|
||||
</div>
|
||||
<span
|
||||
class="secondary"
|
||||
subscribe:innerText={valueLabel}
|
||||
></span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,23 +5,31 @@ import * as React from "../../react";
|
||||
import StorageViewModel from "../../ViewModel/Global/storageViewModel";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import { PATH_COMPONENT_SEPARATOR } from "../../Model/Global/storageModel";
|
||||
import {UsageBar} from "../Components/usageBar";
|
||||
|
||||
export function StorageModal(
|
||||
coreViewModel: CoreViewModel,
|
||||
storageViewModel: StorageViewModel,
|
||||
) {
|
||||
const usageValueLabel = React.createProxyState([storageViewModel.occupiedSpaceMB, storageViewModel.maximumSpaceMB], ()=>
|
||||
coreViewModel.translations.storage.usageVauleLabel(storageViewModel.occupiedSpaceMB.value, storageViewModel.maximumSpaceMB.value));
|
||||
|
||||
const detailView = React.createProxyState(
|
||||
[storageViewModel.selectedPath],
|
||||
() => {
|
||||
if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
||||
return (
|
||||
<div>
|
||||
<div class="flex-column gap">
|
||||
<span class="secondary">
|
||||
{coreViewModel.translations.storage.noItemSelected}
|
||||
</span>
|
||||
|
||||
<hr></hr>
|
||||
|
||||
{UsageBar(coreViewModel.translations.storage.usageLabel, usageValueLabel, storageViewModel.occupiedSpaceMB, storageViewModel.maximumSpaceMB)}
|
||||
|
||||
<hr></hr>
|
||||
|
||||
{DangerousActionButton(
|
||||
coreViewModel,
|
||||
coreViewModel.translations.storage.removeJunkButton,
|
||||
|
||||
@@ -12,13 +12,13 @@ const englishTranslations = {
|
||||
applyButton: "Apply",
|
||||
backButton: "Back",
|
||||
cancelButton: "Cancel",
|
||||
confirmButton: "Confirm",
|
||||
continueButton: "Continue",
|
||||
closeButton: "Close",
|
||||
deleteButton: "Delete",
|
||||
exitButton: "Exit",
|
||||
fullscreenButton: "Fullscreen",
|
||||
|
||||
continueButton: "Continue",
|
||||
confirmButton: "Confirm",
|
||||
refreshButton: "Refresh",
|
||||
saveButton: "Save",
|
||||
setButton: "Set",
|
||||
|
||||
@@ -172,6 +172,9 @@ const englishTranslations = {
|
||||
notAFile: "(not a file)",
|
||||
contentEmpty: "(empty)",
|
||||
|
||||
usageLabel: "Usage",
|
||||
usageVauleLabel: (used: number, max: number) => `${used} of ${max} MB`,
|
||||
|
||||
path: "Path",
|
||||
content: "Content",
|
||||
|
||||
@@ -355,13 +358,13 @@ export const allTranslations: {
|
||||
applyButton: "Anwenden",
|
||||
backButton: "Zurück",
|
||||
cancelButton: "Abbrechen",
|
||||
continueButton: "Weiter",
|
||||
confirmButton: "Bestätigen",
|
||||
closeButton: "Schließen",
|
||||
deleteButton: "Löschen",
|
||||
exitButton: "Schließen",
|
||||
fullscreenButton: "Vollbild",
|
||||
|
||||
continueButton: "Weiter",
|
||||
confirmButton: "Bestätigen",
|
||||
refreshButton: "Aktualisieren",
|
||||
saveButton: "Speichern",
|
||||
setButton: "OK",
|
||||
|
||||
@@ -502,6 +505,9 @@ export const allTranslations: {
|
||||
notAFile: "(keine Datei)",
|
||||
contentEmpty: "(leer)",
|
||||
|
||||
usageLabel: "Speicher",
|
||||
usageVauleLabel: (used: number, max: number) => `${used} von ${max} MB belegt`,
|
||||
|
||||
path: "Pfad",
|
||||
content: "Inhalt",
|
||||
|
||||
@@ -662,13 +668,13 @@ export const allTranslations: {
|
||||
applyButton: "Guardar",
|
||||
backButton: "Atrás",
|
||||
cancelButton: "Cancelar",
|
||||
continueButton: "Continuar",
|
||||
confirmButton: "Confirmar",
|
||||
closeButton: "Cerrar",
|
||||
deleteButton: "Borrar",
|
||||
exitButton: "Salir",
|
||||
fullscreenButton: "Pantalla completa",
|
||||
|
||||
continueButton: "Continuar",
|
||||
confirmButton: "Confirmar",
|
||||
refreshButton: "Actualizar",
|
||||
saveButton: "Guardar",
|
||||
setButton: "OK",
|
||||
|
||||
@@ -808,6 +814,9 @@ export const allTranslations: {
|
||||
notAFile: "(no es un archivo)",
|
||||
contentEmpty: "(vacío)",
|
||||
|
||||
usageLabel: "Almacenamiento",
|
||||
usageVauleLabel: (used: number, max: number) => `${used} de ${max} MB en uso`,
|
||||
|
||||
path: "Ruta",
|
||||
content: "Contenido",
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ export default class StorageViewModel extends Context {
|
||||
);
|
||||
didMakeChanges: React.State<boolean> = new React.State(false);
|
||||
|
||||
occupiedSpaceMB: React.State<number> = new React.State(0);
|
||||
maximumSpaceMB: React.State<number> = new React.State(0);
|
||||
|
||||
selectedFileName: React.State<string>;
|
||||
selectedFileContent: React.State<string>;
|
||||
|
||||
@@ -46,10 +49,17 @@ export default class StorageViewModel extends Context {
|
||||
this.selectedPath.value = PATH_COMPONENT_SEPARATOR;
|
||||
};
|
||||
|
||||
calculateUsage = (): void => {
|
||||
this.occupiedSpaceMB.value = this.coreViewModel.storageModel.calculateUsage();
|
||||
this.maximumSpaceMB.value = this.coreViewModel.storageModel.determineCapacity();
|
||||
console.log(this.occupiedSpaceMB.value, this.maximumSpaceMB.value);
|
||||
}
|
||||
|
||||
// view
|
||||
showStorageModal = (): void => {
|
||||
this.coreViewModel.context = this;
|
||||
this.isShowingStorageModal.value = true;
|
||||
this.calculateUsage();
|
||||
};
|
||||
|
||||
// exit
|
||||
|
||||
Reference in New Issue
Block a user