add file browser
This commit is contained in:
Vendored
+32
@@ -487,3 +487,35 @@ body[showing-chat] #chat-page-wrapper {
|
|||||||
opacity: 0.1;
|
opacity: 0.1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* src/View/Components/fileBrowser.css */
|
||||||
|
.file-browser {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
.file-browser > div {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
flex: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: var(--content-padding);
|
||||||
|
scroll-snap-align: center;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.file-browser code {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.file-browser > div {
|
||||||
|
width: 50%;
|
||||||
|
min-width: 50%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+119
-22
@@ -302,24 +302,30 @@
|
|||||||
storageEntryTree = {};
|
storageEntryTree = {};
|
||||||
// basic
|
// basic
|
||||||
store = (pathComponents, value) => {
|
store = (pathComponents, value) => {
|
||||||
const key = _StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString = _StorageModel.pathComponentsToString(
|
||||||
localStorage.setItem(key, value);
|
...pathComponents
|
||||||
|
);
|
||||||
|
localStorage.setItem(pathString, value);
|
||||||
this.updateTree(...pathComponents);
|
this.updateTree(...pathComponents);
|
||||||
};
|
};
|
||||||
restore = (pathComponents) => {
|
restore = (pathComponents) => {
|
||||||
const key = _StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString = _StorageModel.pathComponentsToString(
|
||||||
return localStorage.getItem(key);
|
...pathComponents
|
||||||
|
);
|
||||||
|
return localStorage.getItem(pathString);
|
||||||
};
|
};
|
||||||
remove = (pathComponents, shouldInitialize = true) => {
|
remove = (pathComponents, shouldInitialize = true) => {
|
||||||
const key = _StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString = _StorageModel.pathComponentsToString(
|
||||||
localStorage.removeItem(key);
|
...pathComponents
|
||||||
|
);
|
||||||
|
localStorage.removeItem(pathString);
|
||||||
if (shouldInitialize == true) {
|
if (shouldInitialize == true) {
|
||||||
this.initializeTree();
|
this.initializeTree();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
removeRecursive = (pathComponentsOfEntityToDelete) => {
|
removeRecursive = (pathComponentsOfEntityToDelete) => {
|
||||||
a: for (const key of Object.keys(localStorage)) {
|
a: for (const key of Object.keys(localStorage)) {
|
||||||
const pathComponentsOfCurrentEntity = _StorageModel.keyToPathComponents(key);
|
const pathComponentsOfCurrentEntity = _StorageModel.stringToPathComponents(key);
|
||||||
for (let i = 0; i < pathComponentsOfEntityToDelete.length; i++) {
|
for (let i = 0; i < pathComponentsOfEntityToDelete.length; i++) {
|
||||||
if (!pathComponentsOfCurrentEntity[i]) continue a;
|
if (!pathComponentsOfCurrentEntity[i]) continue a;
|
||||||
if (pathComponentsOfCurrentEntity[i] != pathComponentsOfEntityToDelete[i])
|
if (pathComponentsOfCurrentEntity[i] != pathComponentsOfEntityToDelete[i])
|
||||||
@@ -336,7 +342,7 @@
|
|||||||
if (nextParent == void 0) return [];
|
if (nextParent == void 0) return [];
|
||||||
currentParent = nextParent;
|
currentParent = nextParent;
|
||||||
}
|
}
|
||||||
return [...Object.keys(currentParent)];
|
return [...Object.keys(currentParent).sort(localeCompare)];
|
||||||
};
|
};
|
||||||
// stringifiable
|
// stringifiable
|
||||||
storeStringifiable = (pathComponents, value) => {
|
storeStringifiable = (pathComponents, value) => {
|
||||||
@@ -357,7 +363,7 @@
|
|||||||
console.log("initializing tree");
|
console.log("initializing tree");
|
||||||
this.storageEntryTree = {};
|
this.storageEntryTree = {};
|
||||||
for (const key of Object.keys(localStorage)) {
|
for (const key of Object.keys(localStorage)) {
|
||||||
const components = _StorageModel.keyToPathComponents(key);
|
const components = _StorageModel.stringToPathComponents(key);
|
||||||
this.updateTree(...components);
|
this.updateTree(...components);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -370,22 +376,22 @@
|
|||||||
currentParent = currentParent[pathPart];
|
currentParent = currentParent[pathPart];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
print = () => {
|
printTree = () => {
|
||||||
console.log(stringify(this.storageEntryTree));
|
return stringify(this.storageEntryTree);
|
||||||
};
|
};
|
||||||
static pathComponentsToKey = (...pathComponents) => {
|
static pathComponentsToString = (...pathComponents) => {
|
||||||
return pathComponents.join(PATH_COMPONENT_SEPARATOR);
|
return pathComponents.join(PATH_COMPONENT_SEPARATOR);
|
||||||
};
|
};
|
||||||
static keyToPathComponents = (key) => {
|
static stringToPathComponents = (string) => {
|
||||||
return key.split(PATH_COMPONENT_SEPARATOR);
|
return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != "");
|
||||||
};
|
};
|
||||||
static join = (...items) => {
|
static join = (...items) => {
|
||||||
let allComponents = [];
|
let allComponents = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const parts = this.keyToPathComponents(item);
|
const parts = this.stringToPathComponents(item);
|
||||||
allComponents.push(...parts);
|
allComponents.push(...parts);
|
||||||
}
|
}
|
||||||
return _StorageModel.pathComponentsToKey(...allComponents);
|
return _StorageModel.pathComponentsToString(...allComponents);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
var storageKeys = {
|
var storageKeys = {
|
||||||
@@ -1116,6 +1122,7 @@
|
|||||||
yourNamePlaceholder: "Jane Doe",
|
yourNamePlaceholder: "Jane Doe",
|
||||||
setNameButtonAudioLabel: "set name",
|
setNameButtonAudioLabel: "set name",
|
||||||
firstDayOfWeekLabel: "First day of week",
|
firstDayOfWeekLabel: "First day of week",
|
||||||
|
manageStorageButton: "Manage storage",
|
||||||
scrollToChatButton: "Chats",
|
scrollToChatButton: "Chats",
|
||||||
///
|
///
|
||||||
backToOverviewAudioLabel: "go back to overview",
|
backToOverviewAudioLabel: "go back to overview",
|
||||||
@@ -1901,7 +1908,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// src/View/homePage.tsx
|
// src/View/homePage.tsx
|
||||||
function HomePage(settingsViewModel2, connectionViewModel2, chatListViewModel2) {
|
function HomePage(storageViewModel2, settingsViewModel2, connectionViewModel2, chatListViewModel2) {
|
||||||
const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.serverAddress), /* @__PURE__ */ createElement(
|
const overviewSection = /* @__PURE__ */ createElement("div", { id: "overview-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.overviewHeadline), /* @__PURE__ */ createElement("label", { class: "tile flex-no" }, /* @__PURE__ */ createElement("span", { class: "icon" }, "cell_tower"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.serverAddress), /* @__PURE__ */ createElement(
|
||||||
"input",
|
"input",
|
||||||
{
|
{
|
||||||
@@ -1970,7 +1977,7 @@
|
|||||||
i.toString(),
|
i.toString(),
|
||||||
i == settingsViewModel2.firstDayOfWeekInput.value
|
i == settingsViewModel2.firstDayOfWeekInput.value
|
||||||
)
|
)
|
||||||
)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))));
|
)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))), /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("button", { class: "tile flex-no", "on:click": storageViewModel2.showStorageModal }, /* @__PURE__ */ createElement("span", { class: "icon" }, "hard_drive_2"), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("span", null, translations.homePage.manageStorageButton))), /* @__PURE__ */ createElement("div", { class: "mobile-only" }, /* @__PURE__ */ createElement("hr", null), /* @__PURE__ */ createElement("div", { class: "flex-row justify-end" }, /* @__PURE__ */ createElement("button", { class: "ghost width-50", "on:click": scrollToChat }, translations.homePage.scrollToChatButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_forward")))));
|
||||||
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
const chatSection = /* @__PURE__ */ createElement("div", { id: "chat-section" }, /* @__PURE__ */ createElement("h2", null, translations.homePage.chatsHeadline), /* @__PURE__ */ createElement("div", { class: "flex-row width-input" }, /* @__PURE__ */ createElement(
|
||||||
"input",
|
"input",
|
||||||
{
|
{
|
||||||
@@ -1992,7 +1999,10 @@
|
|||||||
"div",
|
"div",
|
||||||
{
|
{
|
||||||
id: "chat-grid",
|
id: "chat-grid",
|
||||||
"children:append": [chatListViewModel2.chatViewModels, ChatViewModelToChatEntry]
|
"children:append": [
|
||||||
|
chatListViewModel2.chatViewModels,
|
||||||
|
ChatViewModelToChatEntry
|
||||||
|
]
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
function scrollToChat() {
|
function scrollToChat() {
|
||||||
@@ -2067,6 +2077,87 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/View/Components/directoryItemList.tsx
|
||||||
|
function DirectoryItemList(storageModel2, pathString, selectedPath) {
|
||||||
|
const StringToDirectoryItemList = (pathString2) => DirectoryItemList(storageModel2, pathString2, selectedPath);
|
||||||
|
const path = StorageModel.stringToPathComponents(pathString);
|
||||||
|
const fileName = path[path.length - 1] || "\\";
|
||||||
|
const items = new ListState();
|
||||||
|
const style = `text-indent: ${path.length * 2}rem`;
|
||||||
|
function loadItems() {
|
||||||
|
items.clear();
|
||||||
|
const directoryItems = storageModel2.list(path);
|
||||||
|
for (const directoryItem of directoryItems) {
|
||||||
|
const itemPath = [...path, directoryItem];
|
||||||
|
const pathString2 = StorageModel.pathComponentsToString(...itemPath);
|
||||||
|
items.add(pathString2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function select() {
|
||||||
|
selectedPath.value = pathString;
|
||||||
|
}
|
||||||
|
const isSelected = createProxyState(
|
||||||
|
[selectedPath],
|
||||||
|
() => selectedPath.value == pathString
|
||||||
|
);
|
||||||
|
isSelected.subscribe(() => {
|
||||||
|
if (isSelected.value == false) return;
|
||||||
|
loadItems();
|
||||||
|
});
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "flex-column" }, /* @__PURE__ */ createElement(
|
||||||
|
"button",
|
||||||
|
{
|
||||||
|
class: "width-100 flex-1 clip",
|
||||||
|
"toggle:selected": isSelected,
|
||||||
|
"on:click": select
|
||||||
|
},
|
||||||
|
/* @__PURE__ */ createElement("span", { class: "ellipsis width-100 flex-1", style }, fileName)
|
||||||
|
), /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "flex-column",
|
||||||
|
"children:append": [items, StringToDirectoryItemList]
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/View/Components/fileBrowser.tsx
|
||||||
|
function FileBrowser(storageModel2, selectedPath) {
|
||||||
|
const fileContent = createProxyState([selectedPath], () => {
|
||||||
|
const path = StorageModel.stringToPathComponents(selectedPath.value);
|
||||||
|
const content = storageModel2.restore(path);
|
||||||
|
return content || "";
|
||||||
|
});
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "file-browser" }, DirectoryItemList(storageModel2, "\\", selectedPath), /* @__PURE__ */ createElement("div", null, /* @__PURE__ */ createElement("code", { "subscribe:innerText": fileContent })));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/View/Modals/storageModal.tsx
|
||||||
|
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(
|
||||||
|
storageViewModel2.storageModel,
|
||||||
|
storageViewModel2.selectedPath
|
||||||
|
)), /* @__PURE__ */ createElement("button", { "on:click": storageViewModel2.hideStorageModal }, translations.general.closeButton, /* @__PURE__ */ createElement("span", { class: "icon" }, "close"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/ViewModel/storageViewModel.ts
|
||||||
|
var StorageViewModel = class {
|
||||||
|
storageModel;
|
||||||
|
// state
|
||||||
|
isShowingStorageModal = new State(false);
|
||||||
|
selectedPath = new State("\\");
|
||||||
|
// view methods
|
||||||
|
showStorageModal = () => {
|
||||||
|
this.isShowingStorageModal.value = true;
|
||||||
|
};
|
||||||
|
hideStorageModal = () => {
|
||||||
|
this.isShowingStorageModal.value = false;
|
||||||
|
};
|
||||||
|
// init
|
||||||
|
constructor(storageModel2) {
|
||||||
|
this.storageModel = storageModel2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/index.tsx
|
// src/index.tsx
|
||||||
var storageModel = new StorageModel();
|
var storageModel = new StorageModel();
|
||||||
var settingsModel = new SettingsModel(storageModel);
|
var settingsModel = new SettingsModel(storageModel);
|
||||||
@@ -2076,7 +2167,7 @@
|
|||||||
settingsModel,
|
settingsModel,
|
||||||
connectionModel
|
connectionModel
|
||||||
);
|
);
|
||||||
storageModel.print();
|
var storageViewModel = new StorageViewModel(storageModel);
|
||||||
var settingsViewModel = new SettingsViewModel(settingsModel);
|
var settingsViewModel = new SettingsViewModel(settingsModel);
|
||||||
var connectionViewModel = new ConnectionViewModel(connectionModel);
|
var connectionViewModel = new ConnectionViewModel(connectionModel);
|
||||||
var chatListViewModel = new ChatListViewModel(
|
var chatListViewModel = new ChatListViewModel(
|
||||||
@@ -2094,8 +2185,14 @@
|
|||||||
/* @__PURE__ */ createElement("div", { id: "background-wrapper" }, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" }))
|
/* @__PURE__ */ createElement("div", { id: "background-wrapper" }, /* @__PURE__ */ createElement("div", { id: "sky" }), /* @__PURE__ */ createElement("div", { id: "grass-1" }), /* @__PURE__ */ createElement("div", { id: "grass-2" }))
|
||||||
);
|
);
|
||||||
document.querySelector("main").append(
|
document.querySelector("main").append(
|
||||||
HomePage(settingsViewModel, connectionViewModel, chatListViewModel),
|
HomePage(
|
||||||
|
storageViewModel,
|
||||||
|
settingsViewModel,
|
||||||
|
connectionViewModel,
|
||||||
|
chatListViewModel
|
||||||
|
),
|
||||||
ChatPageWrapper(chatListViewModel),
|
ChatPageWrapper(chatListViewModel),
|
||||||
ConnectionModal(connectionViewModel)
|
ConnectionModal(connectionViewModel),
|
||||||
|
StorageModal(storageViewModel)
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|||||||
Vendored
+6
@@ -1035,6 +1035,7 @@ menu {
|
|||||||
article,
|
article,
|
||||||
body,
|
body,
|
||||||
main,
|
main,
|
||||||
|
.clip,
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -1080,6 +1081,11 @@ main {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
/* scrolling */
|
/* scrolling */
|
||||||
main {
|
main {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|||||||
+23
-17
@@ -1,6 +1,6 @@
|
|||||||
// this file is responsible for reading and writing persistent data; all storage shall be handled by this file.
|
// this file is responsible for reading and writing persistent data; all storage shall be handled by this file.
|
||||||
|
|
||||||
import { parse, stringify } from "./Utility/utility";
|
import { localeCompare, parse, stringify } from "./Utility/utility";
|
||||||
|
|
||||||
import { DATA_VERSION } from "./Utility/typeSafety";
|
import { DATA_VERSION } from "./Utility/typeSafety";
|
||||||
|
|
||||||
@@ -11,22 +11,28 @@ export default class StorageModel {
|
|||||||
|
|
||||||
// basic
|
// basic
|
||||||
store = (pathComponents: string[], value: string): void => {
|
store = (pathComponents: string[], value: string): void => {
|
||||||
const key: string = StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString: string = StorageModel.pathComponentsToString(
|
||||||
localStorage.setItem(key, value);
|
...pathComponents
|
||||||
|
);
|
||||||
|
localStorage.setItem(pathString, value);
|
||||||
this.updateTree(...pathComponents);
|
this.updateTree(...pathComponents);
|
||||||
};
|
};
|
||||||
|
|
||||||
restore = (pathComponents: string[]): string | null => {
|
restore = (pathComponents: string[]): string | null => {
|
||||||
const key: string = StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString: string = StorageModel.pathComponentsToString(
|
||||||
return localStorage.getItem(key);
|
...pathComponents
|
||||||
|
);
|
||||||
|
return localStorage.getItem(pathString);
|
||||||
};
|
};
|
||||||
|
|
||||||
remove = (
|
remove = (
|
||||||
pathComponents: string[],
|
pathComponents: string[],
|
||||||
shouldInitialize: boolean = true
|
shouldInitialize: boolean = true
|
||||||
): void => {
|
): void => {
|
||||||
const key: string = StorageModel.pathComponentsToKey(...pathComponents);
|
const pathString: string = StorageModel.pathComponentsToString(
|
||||||
localStorage.removeItem(key);
|
...pathComponents
|
||||||
|
);
|
||||||
|
localStorage.removeItem(pathString);
|
||||||
|
|
||||||
if (shouldInitialize == true) {
|
if (shouldInitialize == true) {
|
||||||
this.initializeTree();
|
this.initializeTree();
|
||||||
@@ -36,7 +42,7 @@ export default class StorageModel {
|
|||||||
removeRecursive = (pathComponentsOfEntityToDelete: string[]): void => {
|
removeRecursive = (pathComponentsOfEntityToDelete: string[]): void => {
|
||||||
a: for (const key of Object.keys(localStorage)) {
|
a: for (const key of Object.keys(localStorage)) {
|
||||||
const pathComponentsOfCurrentEntity: string[] =
|
const pathComponentsOfCurrentEntity: string[] =
|
||||||
StorageModel.keyToPathComponents(key);
|
StorageModel.stringToPathComponents(key);
|
||||||
|
|
||||||
for (let i = 0; i < pathComponentsOfEntityToDelete.length; i++) {
|
for (let i = 0; i < pathComponentsOfEntityToDelete.length; i++) {
|
||||||
if (!pathComponentsOfCurrentEntity[i]) continue a;
|
if (!pathComponentsOfCurrentEntity[i]) continue a;
|
||||||
@@ -59,7 +65,7 @@ export default class StorageModel {
|
|||||||
currentParent = nextParent;
|
currentParent = nextParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...Object.keys(currentParent)];
|
return [...Object.keys(currentParent).sort(localeCompare)];
|
||||||
};
|
};
|
||||||
|
|
||||||
// stringifiable
|
// stringifiable
|
||||||
@@ -86,7 +92,7 @@ export default class StorageModel {
|
|||||||
|
|
||||||
this.storageEntryTree = {};
|
this.storageEntryTree = {};
|
||||||
for (const key of Object.keys(localStorage)) {
|
for (const key of Object.keys(localStorage)) {
|
||||||
const components: string[] = StorageModel.keyToPathComponents(key);
|
const components: string[] = StorageModel.stringToPathComponents(key);
|
||||||
this.updateTree(...components);
|
this.updateTree(...components);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -102,25 +108,25 @@ export default class StorageModel {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
print = () => {
|
printTree = (): string => {
|
||||||
console.log(stringify(this.storageEntryTree));
|
return stringify(this.storageEntryTree);
|
||||||
};
|
};
|
||||||
|
|
||||||
static pathComponentsToKey = (...pathComponents: string[]): string => {
|
static pathComponentsToString = (...pathComponents: string[]): string => {
|
||||||
return pathComponents.join(PATH_COMPONENT_SEPARATOR);
|
return pathComponents.join(PATH_COMPONENT_SEPARATOR);
|
||||||
};
|
};
|
||||||
|
|
||||||
static keyToPathComponents = (key: string): string[] => {
|
static stringToPathComponents = (string: string): string[] => {
|
||||||
return key.split(PATH_COMPONENT_SEPARATOR);
|
return string.split(PATH_COMPONENT_SEPARATOR).filter((x) => x != "");
|
||||||
};
|
};
|
||||||
|
|
||||||
static join = (...items: string[]): string => {
|
static join = (...items: string[]): string => {
|
||||||
let allComponents: string[] = [];
|
let allComponents: string[] = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const parts = this.keyToPathComponents(item);
|
const parts = this.stringToPathComponents(item);
|
||||||
allComponents.push(...parts);
|
allComponents.push(...parts);
|
||||||
}
|
}
|
||||||
return StorageModel.pathComponentsToKey(...allComponents);
|
return StorageModel.pathComponentsToString(...allComponents);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import StorageModel from "../../Model/storageModel";
|
||||||
|
import { translations } from "../translations";
|
||||||
|
|
||||||
|
export function DirectoryItemList(
|
||||||
|
storageModel: StorageModel,
|
||||||
|
pathString: string,
|
||||||
|
selectedPath: React.State<string>
|
||||||
|
) {
|
||||||
|
// converter
|
||||||
|
const StringToDirectoryItemList: React.StateItemConverter<string> = (
|
||||||
|
pathString: string
|
||||||
|
) => DirectoryItemList(storageModel, pathString, selectedPath);
|
||||||
|
|
||||||
|
// data
|
||||||
|
const path = StorageModel.stringToPathComponents(pathString);
|
||||||
|
const fileName = path[path.length - 1] || "\\";
|
||||||
|
const items = new React.ListState<string>();
|
||||||
|
|
||||||
|
const style = `text-indent: ${path.length * 2}rem`;
|
||||||
|
|
||||||
|
// methods
|
||||||
|
function loadItems() {
|
||||||
|
items.clear();
|
||||||
|
|
||||||
|
const directoryItems = storageModel.list(path);
|
||||||
|
|
||||||
|
for (const directoryItem of directoryItems) {
|
||||||
|
const itemPath = [...path, directoryItem];
|
||||||
|
const pathString = StorageModel.pathComponentsToString(...itemPath);
|
||||||
|
items.add(pathString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function select() {
|
||||||
|
selectedPath.value = pathString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// state
|
||||||
|
const isSelected = React.createProxyState(
|
||||||
|
[selectedPath],
|
||||||
|
() => selectedPath.value == pathString
|
||||||
|
);
|
||||||
|
|
||||||
|
isSelected.subscribe(() => {
|
||||||
|
if (isSelected.value == false) return;
|
||||||
|
loadItems();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex-column">
|
||||||
|
<button
|
||||||
|
class="width-100 flex-1 clip"
|
||||||
|
toggle:selected={isSelected}
|
||||||
|
on:click={select}
|
||||||
|
>
|
||||||
|
<span class="ellipsis width-100 flex-1" style={style}>
|
||||||
|
{fileName}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex-column"
|
||||||
|
children:append={[items, StringToDirectoryItemList]}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
.file-browser {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-browser > div {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
flex: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
padding: var(--content-padding);
|
||||||
|
|
||||||
|
scroll-snap-align: center;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-browser code {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.file-browser > div {
|
||||||
|
width: 50%;
|
||||||
|
min-width: 50%;
|
||||||
|
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import "./fileBrowser.css";
|
||||||
|
|
||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { DirectoryItemList } from "./directoryItemList";
|
||||||
|
import StorageModel from "../../Model/storageModel";
|
||||||
|
|
||||||
|
export function FileBrowser(
|
||||||
|
storageModel: StorageModel,
|
||||||
|
selectedPath: React.State<string>
|
||||||
|
) {
|
||||||
|
const fileContent = React.createProxyState([selectedPath], () => {
|
||||||
|
const path = StorageModel.stringToPathComponents(selectedPath.value);
|
||||||
|
const content = storageModel.restore(path);
|
||||||
|
return content || "";
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="file-browser">
|
||||||
|
{DirectoryItemList(storageModel, "\\", selectedPath)}
|
||||||
|
<div>
|
||||||
|
<code subscribe:innerText={fileContent}></code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import { DirectoryItemList } from "../Components/directoryItemList";
|
||||||
|
import { FileBrowser } from "../Components/fileBrowser";
|
||||||
|
import StorageModel from "../../Model/storageModel";
|
||||||
|
import StorageViewModel from "../../ViewModel/storageViewModel";
|
||||||
|
import { translations } from "../translations";
|
||||||
|
|
||||||
|
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
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
<button on:click={storageViewModel.hideStorageModal}>
|
||||||
|
{translations.general.closeButton}
|
||||||
|
<span class="icon">close</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
+15
-1
@@ -8,9 +8,11 @@ import ChatListViewModel from "../ViewModel/chatListViewModel";
|
|||||||
import { ChatViewModelToChatEntry } from "./Components/chatEntry";
|
import { ChatViewModelToChatEntry } from "./Components/chatEntry";
|
||||||
import ConnectionViewModel from "../ViewModel/connectionViewModel";
|
import ConnectionViewModel from "../ViewModel/connectionViewModel";
|
||||||
import SettingsViewModel from "../ViewModel/settingsViewModel";
|
import SettingsViewModel from "../ViewModel/settingsViewModel";
|
||||||
|
import StorageViewModel from "../ViewModel/storageViewModel";
|
||||||
import { translations } from "./translations";
|
import { translations } from "./translations";
|
||||||
|
|
||||||
export function HomePage(
|
export function HomePage(
|
||||||
|
storageViewModel: StorageViewModel,
|
||||||
settingsViewModel: SettingsViewModel,
|
settingsViewModel: SettingsViewModel,
|
||||||
connectionViewModel: ConnectionViewModel,
|
connectionViewModel: ConnectionViewModel,
|
||||||
chatListViewModel: ChatListViewModel
|
chatListViewModel: ChatListViewModel
|
||||||
@@ -112,6 +114,15 @@ export function HomePage(
|
|||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<hr></hr>
|
||||||
|
|
||||||
|
<button class="tile flex-no" on:click={storageViewModel.showStorageModal}>
|
||||||
|
<span class="icon">hard_drive_2</span>
|
||||||
|
<div>
|
||||||
|
<span>{translations.homePage.manageStorageButton}</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="mobile-only">
|
<div class="mobile-only">
|
||||||
<hr></hr>
|
<hr></hr>
|
||||||
|
|
||||||
@@ -148,7 +159,10 @@ export function HomePage(
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
id="chat-grid"
|
id="chat-grid"
|
||||||
children:append={[chatListViewModel.chatViewModels, ChatViewModelToChatEntry]}
|
children:append={[
|
||||||
|
chatListViewModel.chatViewModels,
|
||||||
|
ChatViewModelToChatEntry,
|
||||||
|
]}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ const englishTranslations = {
|
|||||||
|
|
||||||
firstDayOfWeekLabel: "First day of week",
|
firstDayOfWeekLabel: "First day of week",
|
||||||
|
|
||||||
|
manageStorageButton: "Manage storage",
|
||||||
|
|
||||||
scrollToChatButton: "Chats",
|
scrollToChatButton: "Chats",
|
||||||
|
|
||||||
///
|
///
|
||||||
@@ -106,7 +108,7 @@ const englishTranslations = {
|
|||||||
resendMessageButton: "Resend message",
|
resendMessageButton: "Resend message",
|
||||||
decryptMessageButton: "Decrypt message",
|
decryptMessageButton: "Decrypt message",
|
||||||
deleteMessageButton: "Delete message",
|
deleteMessageButton: "Delete message",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import ConnectionModel from "../Model/connectionModel";
|
import ConnectionModel from "../Model/connectionModel";
|
||||||
import { Message } from "udn-frontend";
|
|
||||||
|
|
||||||
export default class ConnectionViewModel {
|
export default class ConnectionViewModel {
|
||||||
connectionModel: ConnectionModel;
|
connectionModel: ConnectionModel;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
import StorageModel from "../Model/storageModel";
|
||||||
|
|
||||||
|
export default class StorageViewModel {
|
||||||
|
storageModel: StorageModel;
|
||||||
|
|
||||||
|
// state
|
||||||
|
isShowingStorageModal: React.State<boolean> = new React.State(false);
|
||||||
|
selectedPath: React.State<string> = new React.State("\\");
|
||||||
|
|
||||||
|
// view methods
|
||||||
|
showStorageModal = (): void => {
|
||||||
|
this.isShowingStorageModal.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideStorageModal = (): void => {
|
||||||
|
this.isShowingStorageModal.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
constructor(storageModel: StorageModel) {
|
||||||
|
this.storageModel = storageModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-4
@@ -12,7 +12,9 @@ import ConnectionViewModel from "./ViewModel/connectionViewModel";
|
|||||||
import { HomePage } from "./View/homePage";
|
import { HomePage } from "./View/homePage";
|
||||||
import SettingsModel from "./Model/settingsModel";
|
import SettingsModel from "./Model/settingsModel";
|
||||||
import SettingsViewModel from "./ViewModel/settingsViewModel";
|
import SettingsViewModel from "./ViewModel/settingsViewModel";
|
||||||
|
import { StorageModal } from "./View/Modals/storageModal";
|
||||||
import StorageModel from "./Model/storageModel";
|
import StorageModel from "./Model/storageModel";
|
||||||
|
import StorageViewModel from "./ViewModel/storageViewModel";
|
||||||
|
|
||||||
// models
|
// models
|
||||||
const storageModel = new StorageModel();
|
const storageModel = new StorageModel();
|
||||||
@@ -24,9 +26,8 @@ const chatListModel = new ChatListModel(
|
|||||||
connectionModel
|
connectionModel
|
||||||
);
|
);
|
||||||
|
|
||||||
storageModel.print();
|
|
||||||
|
|
||||||
// viewModels
|
// viewModels
|
||||||
|
const storageViewModel = new StorageViewModel(storageModel);
|
||||||
const settingsViewModel = new SettingsViewModel(settingsModel);
|
const settingsViewModel = new SettingsViewModel(settingsModel);
|
||||||
const connectionViewModel = new ConnectionViewModel(connectionModel);
|
const connectionViewModel = new ConnectionViewModel(connectionModel);
|
||||||
const chatListViewModel = new ChatListViewModel(
|
const chatListViewModel = new ChatListViewModel(
|
||||||
@@ -53,7 +54,13 @@ document.body.append(
|
|||||||
document
|
document
|
||||||
.querySelector("main")!
|
.querySelector("main")!
|
||||||
.append(
|
.append(
|
||||||
HomePage(settingsViewModel, connectionViewModel, chatListViewModel),
|
HomePage(
|
||||||
|
storageViewModel,
|
||||||
|
settingsViewModel,
|
||||||
|
connectionViewModel,
|
||||||
|
chatListViewModel
|
||||||
|
),
|
||||||
ChatPageWrapper(chatListViewModel),
|
ChatPageWrapper(chatListViewModel),
|
||||||
ConnectionModal(connectionViewModel)
|
ConnectionModal(connectionViewModel),
|
||||||
|
StorageModal(storageViewModel)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user