add DangerousActionButton, add option to delete files thru StorageModal
This commit is contained in:
Vendored
+103
-41
@@ -1100,6 +1100,8 @@
|
|||||||
general: {
|
general: {
|
||||||
closeButton: "Close",
|
closeButton: "Close",
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
|
abortButton: "Abort",
|
||||||
|
confirmButton: "Confirm",
|
||||||
setButton: "Set"
|
setButton: "Set"
|
||||||
},
|
},
|
||||||
regional: {
|
regional: {
|
||||||
@@ -1143,12 +1145,13 @@
|
|||||||
///
|
///
|
||||||
connectButtonAudioLabel: "connect"
|
connectButtonAudioLabel: "connect"
|
||||||
},
|
},
|
||||||
fileBrowser: {
|
storage: {
|
||||||
noItemSelected: "No item selected",
|
noItemSelected: "No item selected",
|
||||||
notAFile: "(not a file)",
|
notAFile: "(not a file)",
|
||||||
contentEmpty: "(empty)",
|
contentEmpty: "(empty)",
|
||||||
path: "Path",
|
path: "Path",
|
||||||
content: "Content"
|
content: "Content",
|
||||||
|
deleteItem: "Delete item"
|
||||||
},
|
},
|
||||||
chatPage: {
|
chatPage: {
|
||||||
closeChatAudioLabe: "close chat",
|
closeChatAudioLabe: "close chat",
|
||||||
@@ -1306,6 +1309,40 @@
|
|||||||
))))));
|
))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/View/Components/dangerousActionButton.tsx
|
||||||
|
function DangerousActionButton(label, icon, action) {
|
||||||
|
const isActionRequested = new State(false);
|
||||||
|
const cannotConfirm = createProxyState(
|
||||||
|
[isActionRequested],
|
||||||
|
() => isActionRequested.value == false
|
||||||
|
);
|
||||||
|
function requestAction() {
|
||||||
|
isActionRequested.value = true;
|
||||||
|
}
|
||||||
|
function abort() {
|
||||||
|
isActionRequested.value = false;
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "flex-row" }, /* @__PURE__ */ createElement("button", { class: "flex", "on:click": abort, "toggle:hidden": cannotConfirm }, translations.general.abortButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "undo")), /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "danger flex",
|
||||||
|
"on:click": requestAction,
|
||||||
|
"toggle:hidden": isActionRequested
|
||||||
|
},
|
||||||
|
label,
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, icon)
|
||||||
|
), /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "danger flex",
|
||||||
|
"on:click": action,
|
||||||
|
"toggle:hidden": cannotConfirm
|
||||||
|
},
|
||||||
|
translations.general.confirmButton,
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "icon" }, "warning")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// src/View/Components/deletableListItem.tsx
|
// src/View/Components/deletableListItem.tsx
|
||||||
function DeletableListItem(text, primaryButton, ondelete) {
|
function DeletableListItem(text, primaryButton, ondelete) {
|
||||||
return /* @__PURE__ */ createElement("div", { class: "tile flex-row justify-apart align-center padding-0" }, /* @__PURE__ */ createElement("span", { class: "padding-h ellipsis" }, text), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, primaryButton, /* @__PURE__ */ createElement(
|
return /* @__PURE__ */ createElement("div", { class: "tile flex-row justify-apart align-center padding-0" }, /* @__PURE__ */ createElement("span", { class: "padding-h ellipsis" }, text), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, primaryButton, /* @__PURE__ */ createElement(
|
||||||
@@ -1409,15 +1446,11 @@
|
|||||||
"on:click": setColor
|
"on:click": setColor
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement(
|
})), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "width-input" }, DangerousActionButton(
|
||||||
"button",
|
|
||||||
{
|
|
||||||
class: "danger width-input flex-no",
|
|
||||||
"on:click": chatViewModel.remove
|
|
||||||
},
|
|
||||||
translations.chatPage.settings.deleteChatButton,
|
translations.chatPage.settings.deleteChatButton,
|
||||||
/* @__PURE__ */ createElement("span", { class: "icon" }, "delete_forever")
|
"chat_error",
|
||||||
)));
|
chatViewModel.remove
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/View/chatPage.tsx
|
// src/View/chatPage.tsx
|
||||||
@@ -2092,15 +2125,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/View/Components/directoryItemList.tsx
|
// src/View/Components/directoryItemList.tsx
|
||||||
function DirectoryItemList(storageModel2, pathString, selectedPath) {
|
function DirectoryItemList(storageViewModel2, pathString = PATH_COMPONENT_SEPARATOR) {
|
||||||
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageModel2, pathString2, selectedPath);
|
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageViewModel2, pathString2);
|
||||||
const path = StorageModel.stringToPathComponents(pathString);
|
const path = StorageModel.stringToPathComponents(pathString);
|
||||||
const fileName = StorageModel.getFileName(path);
|
const fileName = StorageModel.getFileName(path);
|
||||||
const items = new ListState();
|
const items = new ListState();
|
||||||
const style = `text-indent: ${path.length * 2}rem`;
|
const style = `text-indent: ${path.length * 2}rem`;
|
||||||
function loadItems() {
|
function loadItems() {
|
||||||
items.clear();
|
items.clear();
|
||||||
const directoryItems = storageModel2.list(path);
|
const directoryItems = storageViewModel2.storageModel.list(path);
|
||||||
for (const directoryItem of directoryItems) {
|
for (const directoryItem of directoryItems) {
|
||||||
const itemPath = [...path, directoryItem];
|
const itemPath = [...path, directoryItem];
|
||||||
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
||||||
@@ -2108,11 +2141,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function select() {
|
function select() {
|
||||||
selectedPath.value = pathString;
|
storageViewModel2.selectedPath.value = pathString;
|
||||||
}
|
}
|
||||||
|
storageViewModel2.lastDeletedItemPath.subscribe((lastDeletedItemPath) => {
|
||||||
|
if (!items.value.has(lastDeletedItemPath)) return;
|
||||||
|
select();
|
||||||
|
setTimeout(() => loadItems(), 50);
|
||||||
|
});
|
||||||
const isSelected = createProxyState(
|
const isSelected = createProxyState(
|
||||||
[selectedPath],
|
[storageViewModel2.selectedPath],
|
||||||
() => selectedPath.value == pathString
|
() => storageViewModel2.selectedPath.value == pathString
|
||||||
);
|
);
|
||||||
isSelected.subscribe(() => {
|
isSelected.subscribe(() => {
|
||||||
if (isSelected.value == false) return;
|
if (isSelected.value == false) return;
|
||||||
@@ -2136,30 +2174,35 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/View/Components/fileBrowser.tsx
|
// src/View/Components/fileBrowser.tsx
|
||||||
function FileBrowser(storageModel2, selectedPath, didMakeChanges) {
|
function FileBrowser(storageViewModel2) {
|
||||||
const fileContentToShow = createProxyState([selectedPath], () => {
|
const detailView = createProxyState(
|
||||||
const path = StorageModel.stringToPathComponents(selectedPath.value);
|
[storageViewModel2.selectedPath],
|
||||||
const content = storageModel2.restore(path);
|
() => {
|
||||||
return (content ?? translations.fileBrowser.notAFile) || translations.fileBrowser.contentEmpty;
|
if (storageViewModel2.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
||||||
});
|
return /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.storage.noItemSelected);
|
||||||
const selectedFileName = createProxyState(
|
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.storage.path), /* @__PURE__ */ createElement(
|
||||||
[selectedPath],
|
"span",
|
||||||
() => StorageModel.getFileNameFromString(selectedPath.value) ?? PATH_COMPONENT_SEPARATOR
|
{
|
||||||
|
class: "break-all",
|
||||||
|
"subscribe:innerText": storageViewModel2.selectedPath
|
||||||
|
}
|
||||||
|
))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.storage.content), /* @__PURE__ */ createElement(
|
||||||
|
"code",
|
||||||
|
{
|
||||||
|
"subscribe:innerText": storageViewModel2.selectedFileContent
|
||||||
|
}
|
||||||
|
))), DangerousActionButton(
|
||||||
|
translations.storage.deleteItem,
|
||||||
|
"delete_forever",
|
||||||
|
storageViewModel2.deleteSelectedItem
|
||||||
|
));
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const detailView = createProxyState([selectedPath], () => {
|
const view = /* @__PURE__ */ createElement("div", { class: "file-browser" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area" }, DirectoryItemList(storageViewModel2)), /* @__PURE__ */ createElement("div", { class: "detail-button-wrapper" }, /* @__PURE__ */ createElement("button", { class: "ghost", "on:click": scrollToDetails }, /* @__PURE__ */ createElement(
|
||||||
if (selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
|
||||||
return /* @__PURE__ */ createElement("span", { class: "secondary" }, translations.fileBrowser.noItemSelected);
|
|
||||||
return /* @__PURE__ */ createElement("div", { class: "flex-column gap" }, /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.fileBrowser.path), /* @__PURE__ */ createElement("span", { class: "break-all", "subscribe:innerText": selectedPath }))), /* @__PURE__ */ createElement("div", { class: "tile flex-no" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("b", null, translations.fileBrowser.content), /* @__PURE__ */ createElement("code", { "subscribe:innerText": fileContentToShow }))));
|
|
||||||
});
|
|
||||||
const view = /* @__PURE__ */ createElement("div", { class: "file-browser" }, /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("div", { class: "scroll-area" }, DirectoryItemList(
|
|
||||||
storageModel2,
|
|
||||||
PATH_COMPONENT_SEPARATOR,
|
|
||||||
selectedPath
|
|
||||||
)), /* @__PURE__ */ createElement("div", { class: "detail-button-wrapper" }, /* @__PURE__ */ createElement("button", { class: "ghost", "on:click": scrollToDetails }, /* @__PURE__ */ createElement(
|
|
||||||
"span",
|
"span",
|
||||||
{
|
{
|
||||||
class: "ellipsis",
|
class: "ellipsis",
|
||||||
"subscribe:innerText": selectedFileName
|
"subscribe:innerText": storageViewModel2.selectedFileName
|
||||||
}
|
}
|
||||||
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))), /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": detailView }));
|
), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))), /* @__PURE__ */ createElement("div", { class: "scroll-area", "children:set": detailView }));
|
||||||
function scrollToDetails() {
|
function scrollToDetails() {
|
||||||
@@ -2170,11 +2213,7 @@
|
|||||||
|
|
||||||
// src/View/Modals/storageModal.tsx
|
// src/View/Modals/storageModal.tsx
|
||||||
function StorageModal(storageViewModel2) {
|
function StorageModal(storageViewModel2) {
|
||||||
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", { class: "padding-0" }, FileBrowser(
|
return /* @__PURE__ */ createElement("div", { class: "modal", "toggle:open": storageViewModel2.isShowingStorageModal }, /* @__PURE__ */ createElement("div", { style: "max-width: 64rem" }, /* @__PURE__ */ createElement("main", { class: "padding-0" }, FileBrowser(storageViewModel2)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
||||||
storageViewModel2.storageModel,
|
|
||||||
storageViewModel2.selectedPath,
|
|
||||||
storageViewModel2.didMakeChanges
|
|
||||||
)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// src/ViewModel/storageViewModel.ts
|
// src/ViewModel/storageViewModel.ts
|
||||||
@@ -2184,6 +2223,21 @@
|
|||||||
isShowingStorageModal = new State(false);
|
isShowingStorageModal = new State(false);
|
||||||
selectedPath = new State(PATH_COMPONENT_SEPARATOR);
|
selectedPath = new State(PATH_COMPONENT_SEPARATOR);
|
||||||
didMakeChanges = new State(false);
|
didMakeChanges = new State(false);
|
||||||
|
selectedFileName;
|
||||||
|
selectedFileContent;
|
||||||
|
lastDeletedItemPath = new State("");
|
||||||
|
// methods
|
||||||
|
getSelectedItemContent = () => {
|
||||||
|
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
|
||||||
|
const content = this.storageModel.restore(path);
|
||||||
|
return (content ?? translations.storage.notAFile) || translations.storage.contentEmpty;
|
||||||
|
};
|
||||||
|
deleteSelectedItem = () => {
|
||||||
|
const path = StorageModel.stringToPathComponents(this.selectedPath.value);
|
||||||
|
this.lastDeletedItemPath.value = this.selectedPath.value;
|
||||||
|
this.storageModel.removeRecursive(path);
|
||||||
|
this.didMakeChanges.value = true;
|
||||||
|
};
|
||||||
// view methods
|
// view methods
|
||||||
showStorageModal = () => {
|
showStorageModal = () => {
|
||||||
this.isShowingStorageModal.value = true;
|
this.isShowingStorageModal.value = true;
|
||||||
@@ -2197,6 +2251,14 @@
|
|||||||
// init
|
// init
|
||||||
constructor(storageModel2) {
|
constructor(storageModel2) {
|
||||||
this.storageModel = storageModel2;
|
this.storageModel = storageModel2;
|
||||||
|
this.selectedFileName = createProxyState(
|
||||||
|
[this.selectedPath],
|
||||||
|
() => StorageModel.getFileNameFromString(this.selectedPath.value)
|
||||||
|
);
|
||||||
|
this.selectedFileContent = createProxyState(
|
||||||
|
[this.selectedPath],
|
||||||
|
() => this.getSelectedItemContent()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Vendored
+5
-2
@@ -457,11 +457,14 @@ textarea {
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal,
|
.modal{
|
||||||
[hidden] {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
main,
|
main,
|
||||||
.tile,
|
.tile,
|
||||||
.tile > div {
|
.tile > div {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import * as React from "bloatless-react";
|
|||||||
|
|
||||||
import ChatViewModel from "../../ViewModel/chatViewModel";
|
import ChatViewModel from "../../ViewModel/chatViewModel";
|
||||||
import { Color } from "../../ViewModel/colors";
|
import { Color } from "../../ViewModel/colors";
|
||||||
|
import { DangerousActionButton } from "../Components/dangerousActionButton";
|
||||||
import { DeletableListItem } from "../Components/deletableListItem";
|
import { DeletableListItem } from "../Components/deletableListItem";
|
||||||
import { translations } from "../translations";
|
import { translations } from "../translations";
|
||||||
|
|
||||||
@@ -139,13 +140,13 @@ export function SettingsPage(chatViewModel: ChatViewModel) {
|
|||||||
|
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
<button
|
<div class="width-input">
|
||||||
class="danger width-input flex-no"
|
{DangerousActionButton(
|
||||||
on:click={chatViewModel.remove}
|
translations.chatPage.settings.deleteChatButton,
|
||||||
>
|
"chat_error",
|
||||||
{translations.chatPage.settings.deleteChatButton}
|
chatViewModel.remove
|
||||||
<span class="icon">delete_forever</span>
|
)}
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import StorageModel from "../../Model/storageModel";
|
import StorageModel, {
|
||||||
import { translations } from "../translations";
|
PATH_COMPONENT_SEPARATOR,
|
||||||
|
} from "../../Model/storageModel";
|
||||||
|
|
||||||
|
import StorageViewModel from "../../ViewModel/storageViewModel";
|
||||||
|
|
||||||
export function DirectoryItemList(
|
export function DirectoryItemList(
|
||||||
storageModel: StorageModel,
|
storageViewModel: StorageViewModel,
|
||||||
pathString: string,
|
pathString: string = PATH_COMPONENT_SEPARATOR
|
||||||
selectedPath: React.State<string>
|
|
||||||
) {
|
) {
|
||||||
// converter
|
// converter
|
||||||
const StringToDirectoryItemList: React.StateItemConverter<string> = (
|
const StringToDirectoryItemList: React.StateItemConverter<string> = (
|
||||||
pathString: string
|
pathString: string
|
||||||
) => DirectoryItemList(storageModel, pathString, selectedPath);
|
) => DirectoryItemList(storageViewModel, pathString);
|
||||||
|
|
||||||
// data
|
// data
|
||||||
const path = StorageModel.stringToPathComponents(pathString);
|
const path = StorageModel.stringToPathComponents(pathString);
|
||||||
@@ -24,7 +26,7 @@ export function DirectoryItemList(
|
|||||||
function loadItems() {
|
function loadItems() {
|
||||||
items.clear();
|
items.clear();
|
||||||
|
|
||||||
const directoryItems = storageModel.list(path);
|
const directoryItems = storageViewModel.storageModel.list(path);
|
||||||
|
|
||||||
for (const directoryItem of directoryItems) {
|
for (const directoryItem of directoryItems) {
|
||||||
const itemPath = [...path, directoryItem];
|
const itemPath = [...path, directoryItem];
|
||||||
@@ -34,13 +36,21 @@ export function DirectoryItemList(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function select() {
|
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
|
// state
|
||||||
const isSelected = React.createProxyState(
|
const isSelected = React.createProxyState(
|
||||||
[selectedPath],
|
[storageViewModel.selectedPath],
|
||||||
() => selectedPath.value == pathString
|
() => storageViewModel.selectedPath.value == pathString
|
||||||
);
|
);
|
||||||
|
|
||||||
isSelected.subscribe(() => {
|
isSelected.subscribe(() => {
|
||||||
|
|||||||
@@ -6,71 +6,59 @@ import StorageModel, {
|
|||||||
PATH_COMPONENT_SEPARATOR,
|
PATH_COMPONENT_SEPARATOR,
|
||||||
} from "../../Model/storageModel";
|
} from "../../Model/storageModel";
|
||||||
|
|
||||||
|
import { DangerousActionButton } from "./dangerousActionButton";
|
||||||
import { DirectoryItemList } from "./directoryItemList";
|
import { DirectoryItemList } from "./directoryItemList";
|
||||||
|
import StorageViewModel from "../../ViewModel/storageViewModel";
|
||||||
import { translations } from "../translations";
|
import { translations } from "../translations";
|
||||||
|
|
||||||
export function FileBrowser(
|
export function FileBrowser(storageViewModel: StorageViewModel) {
|
||||||
storageModel: StorageModel,
|
const detailView = React.createProxyState(
|
||||||
selectedPath: React.State<string>,
|
[storageViewModel.selectedPath],
|
||||||
didMakeChanges: React.State<boolean>
|
() => {
|
||||||
) {
|
if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
||||||
// state
|
return (
|
||||||
const fileContentToShow = React.createProxyState([selectedPath], () => {
|
<span class="secondary">{translations.storage.noItemSelected}</span>
|
||||||
const path = StorageModel.stringToPathComponents(selectedPath.value);
|
);
|
||||||
const content = storageModel.restore(path);
|
|
||||||
return (
|
|
||||||
(content ?? translations.fileBrowser.notAFile) ||
|
|
||||||
translations.fileBrowser.contentEmpty
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 (
|
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 (
|
{DangerousActionButton(
|
||||||
<div class="flex-column gap">
|
translations.storage.deleteItem,
|
||||||
<div class="tile flex-no">
|
"delete_forever",
|
||||||
<div>
|
storageViewModel.deleteSelectedItem
|
||||||
<b>{translations.fileBrowser.path}</b>
|
)}
|
||||||
<span class="break-all" subscribe:innerText={selectedPath}></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tile flex-no">
|
);
|
||||||
<div>
|
}
|
||||||
<b>{translations.fileBrowser.content}</b>
|
);
|
||||||
<code subscribe:innerText={fileContentToShow}></code>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const view = (
|
const view = (
|
||||||
<div class="file-browser">
|
<div class="file-browser">
|
||||||
<div>
|
<div>
|
||||||
<div class="scroll-area">
|
<div class="scroll-area">{DirectoryItemList(storageViewModel)}</div>
|
||||||
{DirectoryItemList(
|
|
||||||
storageModel,
|
|
||||||
PATH_COMPONENT_SEPARATOR,
|
|
||||||
selectedPath
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div class="detail-button-wrapper">
|
<div class="detail-button-wrapper">
|
||||||
<button class="ghost" on:click={scrollToDetails}>
|
<button class="ghost" on:click={scrollToDetails}>
|
||||||
<span
|
<span
|
||||||
class="ellipsis"
|
class="ellipsis"
|
||||||
subscribe:innerText={selectedFileName}
|
subscribe:innerText={storageViewModel.selectedFileName}
|
||||||
></span>
|
></span>
|
||||||
<span class="icon">arrow_forward</span>
|
<span class="icon">arrow_forward</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -8,13 +8,7 @@ export function StorageModal(storageViewModel: StorageViewModel) {
|
|||||||
return (
|
return (
|
||||||
<div class="modal" toggle:open={storageViewModel.isShowingStorageModal}>
|
<div class="modal" toggle:open={storageViewModel.isShowingStorageModal}>
|
||||||
<div style="max-width: 64rem">
|
<div style="max-width: 64rem">
|
||||||
<main class="padding-0">
|
<main class="padding-0">{FileBrowser(storageViewModel)}</main>
|
||||||
{FileBrowser(
|
|
||||||
storageViewModel.storageModel,
|
|
||||||
storageViewModel.selectedPath,
|
|
||||||
storageViewModel.didMakeChanges
|
|
||||||
)}
|
|
||||||
</main>
|
|
||||||
<button on:click={storageViewModel.hideStorageModal}>
|
<button on:click={storageViewModel.hideStorageModal}>
|
||||||
{translations.general.closeButton}
|
{translations.general.closeButton}
|
||||||
<span class="icon">close</span>
|
<span class="icon">close</span>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ const englishTranslations = {
|
|||||||
general: {
|
general: {
|
||||||
closeButton: "Close",
|
closeButton: "Close",
|
||||||
deleteItemButtonAudioLabel: "delete item",
|
deleteItemButtonAudioLabel: "delete item",
|
||||||
|
abortButton: "Abort",
|
||||||
|
confirmButton: "Confirm",
|
||||||
|
|
||||||
setButton: "Set",
|
setButton: "Set",
|
||||||
},
|
},
|
||||||
@@ -62,13 +64,15 @@ const englishTranslations = {
|
|||||||
connectButtonAudioLabel: "connect",
|
connectButtonAudioLabel: "connect",
|
||||||
},
|
},
|
||||||
|
|
||||||
fileBrowser: {
|
storage: {
|
||||||
noItemSelected: "No item selected",
|
noItemSelected: "No item selected",
|
||||||
notAFile: "(not a file)",
|
notAFile: "(not a file)",
|
||||||
contentEmpty: "(empty)",
|
contentEmpty: "(empty)",
|
||||||
|
|
||||||
path: "Path",
|
path: "Path",
|
||||||
content: "Content",
|
content: "Content",
|
||||||
|
|
||||||
|
deleteItem: "Delete item"
|
||||||
},
|
},
|
||||||
|
|
||||||
chatPage: {
|
chatPage: {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import * as React from "bloatless-react";
|
|||||||
|
|
||||||
import StorageModel, { PATH_COMPONENT_SEPARATOR } from "../Model/storageModel";
|
import StorageModel, { PATH_COMPONENT_SEPARATOR } from "../Model/storageModel";
|
||||||
|
|
||||||
|
import { translations } from "../View/translations";
|
||||||
|
|
||||||
export default class StorageViewModel {
|
export default class StorageViewModel {
|
||||||
storageModel: StorageModel;
|
storageModel: StorageModel;
|
||||||
|
|
||||||
@@ -10,6 +12,29 @@ export default class StorageViewModel {
|
|||||||
selectedPath: React.State<string> = new React.State(PATH_COMPONENT_SEPARATOR);
|
selectedPath: React.State<string> = new React.State(PATH_COMPONENT_SEPARATOR);
|
||||||
didMakeChanges: React.State<boolean> = new React.State(false);
|
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
|
// view methods
|
||||||
showStorageModal = (): void => {
|
showStorageModal = (): void => {
|
||||||
this.isShowingStorageModal.value = true;
|
this.isShowingStorageModal.value = true;
|
||||||
@@ -25,5 +50,12 @@ export default class StorageViewModel {
|
|||||||
// init
|
// init
|
||||||
constructor(storageModel: StorageModel) {
|
constructor(storageModel: StorageModel) {
|
||||||
this.storageModel = storageModel;
|
this.storageModel = storageModel;
|
||||||
|
|
||||||
|
this.selectedFileName = React.createProxyState([this.selectedPath], () =>
|
||||||
|
StorageModel.getFileNameFromString(this.selectedPath.value)
|
||||||
|
);
|
||||||
|
this.selectedFileContent = React.createProxyState([this.selectedPath], () =>
|
||||||
|
this.getSelectedItemContent()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user