add DangerousActionButton, add option to delete files thru StorageModal

This commit is contained in:
marlon-erler
2024-07-21 20:21:32 +02:00
parent 3824224e85
commit 331823f2ec
9 changed files with 259 additions and 118 deletions
+8 -7
View File
@@ -2,6 +2,7 @@ import * as React from "bloatless-react";
import ChatViewModel from "../../ViewModel/chatViewModel";
import { Color } from "../../ViewModel/colors";
import { DangerousActionButton } from "../Components/dangerousActionButton";
import { DeletableListItem } from "../Components/deletableListItem";
import { translations } from "../translations";
@@ -139,13 +140,13 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
<hr></hr>
<button
class="danger width-input flex-no"
on:click={chatViewModel.remove}
>
{translations.chatPage.settings.deleteChatButton}
<span class="icon">delete_forever</span>
</button>
<div class="width-input">
{DangerousActionButton(
translations.chatPage.settings.deleteChatButton,
"chat_error",
chatViewModel.remove
)}
</div>
</div>
</div>
);
@@ -0,0 +1,47 @@
import * as React from "bloatless-react";
import { translations } from "../translations";
export function DangerousActionButton(
label: string,
icon: string,
action: () => void
) {
const isActionRequested = new React.State(false);
const cannotConfirm = React.createProxyState(
[isActionRequested],
() => isActionRequested.value == false
);
function requestAction() {
isActionRequested.value = true;
}
function abort() {
isActionRequested.value = false;
}
return (
<div class="flex-row">
<button class="flex" on:click={abort} toggle:hidden={cannotConfirm}>
{translations.general.abortButton}
<span class="icon">undo</span>
</button>
<button
class="danger flex"
on:click={requestAction}
toggle:hidden={isActionRequested}
>
{label}
<span class="icon">{icon}</span>
</button>
<button
class="danger flex"
on:click={action}
toggle:hidden={cannotConfirm}
>
{translations.general.confirmButton}
<span class="icon">warning</span>
</button>
</div>
);
}
+20 -10
View File
@@ -1,17 +1,19 @@
import * as React from "bloatless-react";
import StorageModel from "../../Model/storageModel";
import { translations } from "../translations";
import StorageModel, {
PATH_COMPONENT_SEPARATOR,
} from "../../Model/storageModel";
import StorageViewModel from "../../ViewModel/storageViewModel";
export function DirectoryItemList(
storageModel: StorageModel,
pathString: string,
selectedPath: React.State<string>
storageViewModel: StorageViewModel,
pathString: string = PATH_COMPONENT_SEPARATOR
) {
// converter
const StringToDirectoryItemList: React.StateItemConverter<string> = (
pathString: string
) => DirectoryItemList(storageModel, pathString, selectedPath);
) => DirectoryItemList(storageViewModel, pathString);
// data
const path = StorageModel.stringToPathComponents(pathString);
@@ -24,7 +26,7 @@ export function DirectoryItemList(
function loadItems() {
items.clear();
const directoryItems = storageModel.list(path);
const directoryItems = storageViewModel.storageModel.list(path);
for (const directoryItem of directoryItems) {
const itemPath = [...path, directoryItem];
@@ -34,13 +36,21 @@ export function DirectoryItemList(
}
function select() {
selectedPath.value = pathString;
storageViewModel.selectedPath.value = pathString;
}
// handlers
storageViewModel.lastDeletedItemPath.subscribe((lastDeletedItemPath) => {
if (!items.value.has(lastDeletedItemPath)) return;
select();
setTimeout(() => loadItems(), 50)
});
// state
const isSelected = React.createProxyState(
[selectedPath],
() => selectedPath.value == pathString
[storageViewModel.selectedPath],
() => storageViewModel.selectedPath.value == pathString
);
isSelected.subscribe(() => {
+38 -50
View File
@@ -6,71 +6,59 @@ import StorageModel, {
PATH_COMPONENT_SEPARATOR,
} from "../../Model/storageModel";
import { DangerousActionButton } from "./dangerousActionButton";
import { DirectoryItemList } from "./directoryItemList";
import StorageViewModel from "../../ViewModel/storageViewModel";
import { translations } from "../translations";
export function FileBrowser(
storageModel: StorageModel,
selectedPath: React.State<string>,
didMakeChanges: React.State<boolean>
) {
// state
const fileContentToShow = React.createProxyState([selectedPath], () => {
const path = StorageModel.stringToPathComponents(selectedPath.value);
const content = storageModel.restore(path);
return (
(content ?? translations.fileBrowser.notAFile) ||
translations.fileBrowser.contentEmpty
);
});
export function FileBrowser(storageViewModel: StorageViewModel) {
const detailView = React.createProxyState(
[storageViewModel.selectedPath],
() => {
if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR)
return (
<span class="secondary">{translations.storage.noItemSelected}</span>
);
const selectedFileName = React.createProxyState(
[selectedPath],
() =>
StorageModel.getFileNameFromString(selectedPath.value) ??
PATH_COMPONENT_SEPARATOR
);
// view
const detailView = React.createProxyState([selectedPath], () => {
if (selectedPath.value == PATH_COMPONENT_SEPARATOR)
return (
<span class="secondary">{translations.fileBrowser.noItemSelected}</span>
);
<div class="flex-column gap">
<div class="tile flex-no">
<div>
<b>{translations.storage.path}</b>
<span
class="break-all"
subscribe:innerText={storageViewModel.selectedPath}
></span>
</div>
</div>
<div class="tile flex-no">
<div>
<b>{translations.storage.content}</b>
<code
subscribe:innerText={storageViewModel.selectedFileContent}
></code>
</div>
</div>
return (
<div class="flex-column gap">
<div class="tile flex-no">
<div>
<b>{translations.fileBrowser.path}</b>
<span class="break-all" subscribe:innerText={selectedPath}></span>
</div>
{DangerousActionButton(
translations.storage.deleteItem,
"delete_forever",
storageViewModel.deleteSelectedItem
)}
</div>
<div class="tile flex-no">
<div>
<b>{translations.fileBrowser.content}</b>
<code subscribe:innerText={fileContentToShow}></code>
</div>
</div>
</div>
);
});
);
}
);
const view = (
<div class="file-browser">
<div>
<div class="scroll-area">
{DirectoryItemList(
storageModel,
PATH_COMPONENT_SEPARATOR,
selectedPath
)}
</div>
<div class="scroll-area">{DirectoryItemList(storageViewModel)}</div>
<div class="detail-button-wrapper">
<button class="ghost" on:click={scrollToDetails}>
<span
class="ellipsis"
subscribe:innerText={selectedFileName}
subscribe:innerText={storageViewModel.selectedFileName}
></span>
<span class="icon">arrow_forward</span>
</button>
+1 -7
View File
@@ -8,13 +8,7 @@ export function StorageModal(storageViewModel: StorageViewModel) {
return (
<div class="modal" toggle:open={storageViewModel.isShowingStorageModal}>
<div style="max-width: 64rem">
<main class="padding-0">
{FileBrowser(
storageViewModel.storageModel,
storageViewModel.selectedPath,
storageViewModel.didMakeChanges
)}
</main>
<main class="padding-0">{FileBrowser(storageViewModel)}</main>
<button on:click={storageViewModel.hideStorageModal}>
{translations.general.closeButton}
<span class="icon">close</span>
+5 -1
View File
@@ -2,6 +2,8 @@ const englishTranslations = {
general: {
closeButton: "Close",
deleteItemButtonAudioLabel: "delete item",
abortButton: "Abort",
confirmButton: "Confirm",
setButton: "Set",
},
@@ -62,13 +64,15 @@ const englishTranslations = {
connectButtonAudioLabel: "connect",
},
fileBrowser: {
storage: {
noItemSelected: "No item selected",
notAFile: "(not a file)",
contentEmpty: "(empty)",
path: "Path",
content: "Content",
deleteItem: "Delete item"
},
chatPage: {
+32
View File
@@ -2,6 +2,8 @@ import * as React from "bloatless-react";
import StorageModel, { PATH_COMPONENT_SEPARATOR } from "../Model/storageModel";
import { translations } from "../View/translations";
export default class StorageViewModel {
storageModel: StorageModel;
@@ -10,6 +12,29 @@ export default class StorageViewModel {
selectedPath: React.State<string> = new React.State(PATH_COMPONENT_SEPARATOR);
didMakeChanges: React.State<boolean> = new React.State(false);
selectedFileName: React.State<string>;
selectedFileContent: React.State<string>;
lastDeletedItemPath: React.State<string> = new React.State("");
// methods
getSelectedItemContent = (): string => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
const content = this.storageModel.restore(path);
return (
(content ?? translations.storage.notAFile) ||
translations.storage.contentEmpty
);
};
deleteSelectedItem = (): void => {
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
this.lastDeletedItemPath.value = this.selectedPath.value;
this.storageModel.removeRecursive(path);
this.didMakeChanges.value = true;
};
// view methods
showStorageModal = (): void => {
this.isShowingStorageModal.value = true;
@@ -25,5 +50,12 @@ export default class StorageViewModel {
// init
constructor(storageModel: StorageModel) {
this.storageModel = storageModel;
this.selectedFileName = React.createProxyState([this.selectedPath], () =>
StorageModel.getFileNameFromString(this.selectedPath.value)
);
this.selectedFileContent = React.createProxyState([this.selectedPath], () =>
this.getSelectedItemContent()
);
}
}