format
This commit is contained in:
@@ -46,7 +46,10 @@ export function BoardPage(
|
||||
);
|
||||
|
||||
const wrapper = (
|
||||
<div class="content main-content allow-drag-move" children:set={pages}></div>
|
||||
<div
|
||||
class="content main-content allow-drag-move"
|
||||
children:set={pages}
|
||||
></div>
|
||||
);
|
||||
Utility.implementPinchZoom(wrapper, boardViewModel.pinchToZoomData);
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@
|
||||
width: 100%;
|
||||
padding-left: var(--content-padding);
|
||||
gap: var(--gap-in-controls);
|
||||
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
@@ -285,7 +285,8 @@
|
||||
padding: var(--gap-between-tiles);
|
||||
}
|
||||
|
||||
#message-page[reply-view] :is(.reply-link, .inline-reply, #composer .reply-preview-wrapper) {
|
||||
#message-page[reply-view]
|
||||
:is(.reply-link, .inline-reply, #composer .reply-preview-wrapper) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
+169
-148
@@ -15,156 +15,177 @@ export function MessagePage(
|
||||
messagePageViewModel.loadData();
|
||||
|
||||
const ChatMessageViewModelToView: React.StateItemConverter<
|
||||
ChatMessageViewModel
|
||||
> = (chatMessageViewModel: ChatMessageViewModel) => {
|
||||
return ChatMessage(coreViewModel, chatMessageViewModel);
|
||||
};
|
||||
ChatMessageViewModel
|
||||
> = (chatMessageViewModel: ChatMessageViewModel) => {
|
||||
return ChatMessage(coreViewModel, chatMessageViewModel);
|
||||
};
|
||||
|
||||
const messageContainer = (
|
||||
<div
|
||||
id="message-container"
|
||||
children:append={[
|
||||
messagePageViewModel.filteredMessageViewModels,
|
||||
ChatMessageViewModelToView,
|
||||
]}
|
||||
></div>
|
||||
);
|
||||
const messageContainer = (
|
||||
<div
|
||||
id="message-container"
|
||||
children:append={[
|
||||
messagePageViewModel.filteredMessageViewModels,
|
||||
ChatMessageViewModelToView,
|
||||
]}
|
||||
></div>
|
||||
);
|
||||
|
||||
const persistenceId = messagePageViewModel.chatViewModel.chatModel.id;
|
||||
const replyPreview = ViewController.inlineReplies.setState(
|
||||
persistenceId,
|
||||
() =>
|
||||
React.createProxyState(
|
||||
[messagePageViewModel.replyingMessage],
|
||||
() => {
|
||||
if (messagePageViewModel.replyingMessage.value == undefined)
|
||||
return <div></div>;
|
||||
return ReplyPreview(
|
||||
coreViewModel,
|
||||
messagePageViewModel.replyingMessage.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
const persistenceId = messagePageViewModel.chatViewModel.chatModel.id;
|
||||
const replyPreview = ViewController.inlineReplies.setState(
|
||||
persistenceId,
|
||||
() =>
|
||||
React.createProxyState(
|
||||
[messagePageViewModel.replyingMessage],
|
||||
() => {
|
||||
if (messagePageViewModel.replyingMessage.value == undefined)
|
||||
return <div></div>;
|
||||
return ReplyPreview(
|
||||
coreViewModel,
|
||||
messagePageViewModel.replyingMessage.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
function scrollDown(hard: boolean = false) {
|
||||
if (hard) {
|
||||
messageContainer.setAttribute("scroll-hard", "");
|
||||
function scrollDown(hard: boolean = false) {
|
||||
if (hard) {
|
||||
messageContainer.setAttribute("scroll-hard", "");
|
||||
}
|
||||
messageContainer.scrollTop = messageContainer.scrollHeight;
|
||||
messageContainer.removeAttribute("scroll-hard");
|
||||
}
|
||||
messageContainer.scrollTop = messageContainer.scrollHeight;
|
||||
messageContainer.removeAttribute("scroll-hard");
|
||||
}
|
||||
function scrollDownIfApplicable() {
|
||||
const scrollFromBottom =
|
||||
messageContainer.scrollHeight -
|
||||
(messageContainer.scrollTop + messageContainer.offsetHeight);
|
||||
if (scrollFromBottom > 400) return;
|
||||
|
||||
scrollDown();
|
||||
}
|
||||
messagePageViewModel.filteredMessageViewModels.subscribeSilent(
|
||||
scrollDownIfApplicable,
|
||||
);
|
||||
React.bulkSubscribe([messagePageViewModel.reactionFilter, messagePageViewModel.searchViewModel.appliedQuery], scrollDown)
|
||||
messagePageViewModel.replyViewSelectedMessage.subscribeSilent(selectedMessage => {
|
||||
if (selectedMessage != undefined) return;
|
||||
setTimeout(scrollDown, 100);
|
||||
})
|
||||
setTimeout(() => scrollDown(true), 100);
|
||||
|
||||
messagePageViewModel.focusSetter.subscribeSilent(() => {
|
||||
ViewController.setFocusWithDelay();
|
||||
});
|
||||
|
||||
const isInReplyView = React.createProxyState([messagePageViewModel.replyViewSelectedMessage], ()=>messagePageViewModel.replyViewSelectedMessage.value != undefined);
|
||||
|
||||
const title = React.createProxyState([messagePageViewModel.isReplyViewActive], ()=>messagePageViewModel.isReplyViewActive.value ? coreViewModel.translations.chatPage.message.replyHeaderLabel((messagePageViewModel.replyViewSelectedMessage.value!).body.value) : coreViewModel.translations.chatPage.message.messagesHeadline);
|
||||
|
||||
return (
|
||||
<div id="message-page" toggle:reply-view={isInReplyView}>
|
||||
<div class="pane-wrapper">
|
||||
<div class="pane">
|
||||
<div class="toolbar">
|
||||
<button
|
||||
class="ghost"
|
||||
on:click={messagePageViewModel.resetReplyView}
|
||||
toggle:hidden={messagePageViewModel.isReplyViewInactive}
|
||||
>
|
||||
<span class="icon">arrow_back</span>
|
||||
</button>
|
||||
<span
|
||||
class="title ellipsis width-100 flex"
|
||||
subscribe:innerText={title}
|
||||
>
|
||||
</span>
|
||||
<span>
|
||||
<button
|
||||
class="ghost inset-outline"
|
||||
on:click={messagePageViewModel.showFilterModal}
|
||||
aria-label={
|
||||
coreViewModel.translations.chatPage.message
|
||||
.filterMessagesButtonAudioLabel
|
||||
}
|
||||
toggle:selected={
|
||||
messagePageViewModel.isFilterActive
|
||||
}
|
||||
>
|
||||
<span class="icon">filter_alt</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
{messageContainer}
|
||||
<div id="composer">
|
||||
<div class="content-width-constraint">
|
||||
<div
|
||||
class="reply-preview-wrapper"
|
||||
children:set={replyPreview}
|
||||
></div>
|
||||
<div class="input-width-constraint">
|
||||
<input
|
||||
id="focused"
|
||||
class="blur"
|
||||
bind:value={
|
||||
messagePageViewModel.composingMessage
|
||||
}
|
||||
on:enter={
|
||||
messagePageViewModel.sendMessage
|
||||
}
|
||||
placeholder={
|
||||
coreViewModel.translations.chatPage
|
||||
.message
|
||||
.composerInputPlaceholder
|
||||
}
|
||||
></input>
|
||||
<button
|
||||
class="primary blur"
|
||||
aria-label={
|
||||
coreViewModel.translations.chatPage
|
||||
.message
|
||||
.sendMessageButtonAudioLabel
|
||||
}
|
||||
on:click={
|
||||
messagePageViewModel.sendMessage
|
||||
}
|
||||
toggle:disabled={
|
||||
messagePageViewModel.cannotSendMessage
|
||||
}
|
||||
>
|
||||
<span class="icon">send</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{MessageFilterModal(
|
||||
coreViewModel,
|
||||
messagePageViewModel,
|
||||
ChatMessageViewModelToView,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
function scrollDownIfApplicable() {
|
||||
const scrollFromBottom =
|
||||
messageContainer.scrollHeight -
|
||||
(messageContainer.scrollTop + messageContainer.offsetHeight);
|
||||
if (scrollFromBottom > 400) return;
|
||||
|
||||
scrollDown();
|
||||
}
|
||||
messagePageViewModel.filteredMessageViewModels.subscribeSilent(
|
||||
scrollDownIfApplicable,
|
||||
);
|
||||
React.bulkSubscribe(
|
||||
[
|
||||
messagePageViewModel.reactionFilter,
|
||||
messagePageViewModel.searchViewModel.appliedQuery,
|
||||
],
|
||||
scrollDown,
|
||||
);
|
||||
messagePageViewModel.replyViewSelectedMessage.subscribeSilent(
|
||||
(selectedMessage) => {
|
||||
if (selectedMessage != undefined) return;
|
||||
setTimeout(scrollDown, 100);
|
||||
},
|
||||
);
|
||||
setTimeout(() => scrollDown(true), 100);
|
||||
|
||||
messagePageViewModel.focusSetter.subscribeSilent(() => {
|
||||
ViewController.setFocusWithDelay();
|
||||
});
|
||||
|
||||
const isInReplyView = React.createProxyState(
|
||||
[messagePageViewModel.replyViewSelectedMessage],
|
||||
() => messagePageViewModel.replyViewSelectedMessage.value != undefined,
|
||||
);
|
||||
|
||||
const title = React.createProxyState(
|
||||
[messagePageViewModel.isReplyViewActive],
|
||||
() =>
|
||||
messagePageViewModel.isReplyViewActive.value
|
||||
? coreViewModel.translations.chatPage.message.replyHeaderLabel(
|
||||
messagePageViewModel.replyViewSelectedMessage.value!.body
|
||||
.value,
|
||||
)
|
||||
: coreViewModel.translations.chatPage.message.messagesHeadline,
|
||||
);
|
||||
|
||||
return (
|
||||
<div id="message-page" toggle:reply-view={isInReplyView}>
|
||||
<div class="pane-wrapper">
|
||||
<div class="pane">
|
||||
<div class="toolbar">
|
||||
<button
|
||||
class="ghost"
|
||||
on:click={messagePageViewModel.resetReplyView}
|
||||
toggle:hidden={
|
||||
messagePageViewModel.isReplyViewInactive
|
||||
}
|
||||
>
|
||||
<span class="icon">arrow_back</span>
|
||||
</button>
|
||||
<span
|
||||
class="title ellipsis width-100 flex"
|
||||
subscribe:innerText={title}
|
||||
></span>
|
||||
<span>
|
||||
<button
|
||||
class="ghost inset-outline"
|
||||
on:click={messagePageViewModel.showFilterModal}
|
||||
aria-label={
|
||||
coreViewModel.translations.chatPage.message
|
||||
.filterMessagesButtonAudioLabel
|
||||
}
|
||||
toggle:selected={
|
||||
messagePageViewModel.isFilterActive
|
||||
}
|
||||
>
|
||||
<span class="icon">filter_alt</span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="content">
|
||||
{messageContainer}
|
||||
<div id="composer">
|
||||
<div class="content-width-constraint">
|
||||
<div
|
||||
class="reply-preview-wrapper"
|
||||
children:set={replyPreview}
|
||||
></div>
|
||||
<div class="input-width-constraint">
|
||||
<input
|
||||
id="focused"
|
||||
class="blur"
|
||||
bind:value={
|
||||
messagePageViewModel.composingMessage
|
||||
}
|
||||
on:enter={
|
||||
messagePageViewModel.sendMessage
|
||||
}
|
||||
placeholder={
|
||||
coreViewModel.translations.chatPage
|
||||
.message
|
||||
.composerInputPlaceholder
|
||||
}
|
||||
></input>
|
||||
<button
|
||||
class="primary blur"
|
||||
aria-label={
|
||||
coreViewModel.translations.chatPage
|
||||
.message
|
||||
.sendMessageButtonAudioLabel
|
||||
}
|
||||
on:click={
|
||||
messagePageViewModel.sendMessage
|
||||
}
|
||||
toggle:disabled={
|
||||
messagePageViewModel.cannotSendMessage
|
||||
}
|
||||
>
|
||||
<span class="icon">send</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{MessageFilterModal(
|
||||
coreViewModel,
|
||||
messagePageViewModel,
|
||||
ChatMessageViewModelToView,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,12 +47,8 @@ export function SettingsPage(
|
||||
}
|
||||
</span>
|
||||
<input
|
||||
bind:value={
|
||||
settingsPageViewModel.nameInput
|
||||
}
|
||||
on:enter={
|
||||
settingsPageViewModel.setName
|
||||
}
|
||||
bind:value={settingsPageViewModel.nameInput}
|
||||
on:enter={settingsPageViewModel.setName}
|
||||
></input>
|
||||
</div>
|
||||
</label>
|
||||
@@ -63,9 +59,7 @@ export function SettingsPage(
|
||||
coreViewModel.translations.chatPage.settings
|
||||
.setNameButtonAudioLabel
|
||||
}
|
||||
on:click={
|
||||
settingsPageViewModel.setName
|
||||
}
|
||||
on:click={settingsPageViewModel.setName}
|
||||
toggle:disabled={
|
||||
settingsPageViewModel.cannotSetPrimaryChannel
|
||||
}
|
||||
@@ -177,7 +171,7 @@ export function SettingsPage(
|
||||
|
||||
<hr></hr>
|
||||
|
||||
{ColorPicker(settingsPageViewModel.color)}
|
||||
{ColorPicker(settingsPageViewModel.color)}
|
||||
|
||||
<div class="flex-row justify-end width-input">
|
||||
<button
|
||||
@@ -186,17 +180,16 @@ export function SettingsPage(
|
||||
coreViewModel.translations.chatPage.settings
|
||||
.setColorButtonAudioLabel
|
||||
}
|
||||
on:click={
|
||||
settingsPageViewModel.applyColor
|
||||
}
|
||||
toggle:disabled={settingsPageViewModel.cannotSetColor}
|
||||
on:click={settingsPageViewModel.applyColor}
|
||||
toggle:disabled={
|
||||
settingsPageViewModel.cannotSetColor
|
||||
}
|
||||
>
|
||||
{coreViewModel.translations.general.setButton}
|
||||
<span class="icon">check</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<hr></hr>
|
||||
|
||||
<div class="width-input">
|
||||
|
||||
@@ -2,11 +2,11 @@ import "./taskPage.css";
|
||||
import { BoardPage } from "./boardPage";
|
||||
import { ViewController } from "../viewController";
|
||||
import { PlaceholderView } from "../Components/placeholderView";
|
||||
import { NewItemEntry } from "../Components/newItemEntry";
|
||||
import { BoardViewModelToEntry } from "../Components/boardEntry";
|
||||
import * as React from "../../react";
|
||||
import TaskPageViewModel from "../../ViewModel/Pages/taskPageViewModel";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import {NewItemEntry} from "../Components/newItemEntry";
|
||||
|
||||
export function TaskPage(
|
||||
coreViewModel: CoreViewModel,
|
||||
@@ -66,13 +66,18 @@ export function TaskPage(
|
||||
bind:value={taskPageViewModel.boardQuery}
|
||||
on:enter={taskPageViewModel.createBoard}
|
||||
placeholder={
|
||||
coreViewModel.translations.general.filterOrCreateLabel
|
||||
coreViewModel.translations.general
|
||||
.filterOrCreateLabel
|
||||
}
|
||||
></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content gap">
|
||||
{NewItemEntry(coreViewModel, taskPageViewModel.boardQuery, taskPageViewModel.createBoard)}
|
||||
<div class="content gap">
|
||||
{NewItemEntry(
|
||||
coreViewModel,
|
||||
taskPageViewModel.boardQuery,
|
||||
taskPageViewModel.createBoard,
|
||||
)}
|
||||
<div
|
||||
class="grid gap"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr))"
|
||||
|
||||
@@ -12,14 +12,10 @@ export function ChatEntry(chatViewModel: ChatViewModel) {
|
||||
>
|
||||
<span
|
||||
class="shadow"
|
||||
subscribe:innerText={
|
||||
chatViewModel.settingsPageViewModel.name
|
||||
}
|
||||
subscribe:innerText={chatViewModel.settingsPageViewModel.name}
|
||||
></span>
|
||||
<h2
|
||||
subscribe:innerText={
|
||||
chatViewModel.settingsPageViewModel.name
|
||||
}
|
||||
subscribe:innerText={chatViewModel.settingsPageViewModel.name}
|
||||
></h2>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ReplyLink } from "./replyLink";
|
||||
import { MessageReactionButtonRow } from "./messageReactionButtonRow";
|
||||
import { InlineReply } from "./inlineReply";
|
||||
import { ChatMessageInfoModal } from "../Modals/chatMessageInfoModal";
|
||||
@@ -5,7 +6,6 @@ import * as React from "../../react";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
|
||||
import { ChatMessageStatuses } from "../../Model/Chat/chatModel";
|
||||
import {ReplyLink} from "./replyLink";
|
||||
|
||||
export function ChatMessage(
|
||||
coreViewModel: CoreViewModel,
|
||||
@@ -69,8 +69,11 @@ export function ChatMessage(
|
||||
aria-label={
|
||||
coreViewModel.translations.chatPage.message
|
||||
.replyToMessageButton
|
||||
}
|
||||
toggle:hidden={chatMessageViewModel.messagePageViewModel.isReplyViewActive}
|
||||
}
|
||||
toggle:hidden={
|
||||
chatMessageViewModel.messagePageViewModel
|
||||
.isReplyViewActive
|
||||
}
|
||||
>
|
||||
<span class="icon">reply</span>
|
||||
</button>
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import * as React from "../../react";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
|
||||
export function NewItemEntry(coreViewModel: CoreViewModel, query: React.State<string>, fn: ()=>void) {
|
||||
const isHidden = React.createProxyState([query],()=>query.value == "");
|
||||
const label = React.createProxyState([query],()=>coreViewModel.translations.general.createLabel(query.value));
|
||||
export function NewItemEntry(
|
||||
coreViewModel: CoreViewModel,
|
||||
query: React.State<string>,
|
||||
fn: () => void,
|
||||
) {
|
||||
const isHidden = React.createProxyState([query], () => query.value == "");
|
||||
const label = React.createProxyState([query], () =>
|
||||
coreViewModel.translations.general.createLabel(query.value),
|
||||
);
|
||||
|
||||
return <button
|
||||
class="standard slide-up"
|
||||
toggle:hidden={isHidden}
|
||||
on:click={fn}
|
||||
>
|
||||
<span subscribe:innerText={label}></span>
|
||||
<span class="icon">add</span>
|
||||
</button>
|
||||
return (
|
||||
<button
|
||||
class="standard slide-up"
|
||||
toggle:hidden={isHidden}
|
||||
on:click={fn}
|
||||
>
|
||||
<span subscribe:innerText={label}></span>
|
||||
<span class="icon">add</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
import * as React from "../../react";
|
||||
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
|
||||
|
||||
export function ReplyLink(coreViewModel: CoreViewModel, chatMessageViewModel: ChatMessageViewModel) {
|
||||
const isHidden = React.createProxyState([chatMessageViewModel.replyCount], ()=>chatMessageViewModel.replyCount.value == 0);
|
||||
export function ReplyLink(
|
||||
coreViewModel: CoreViewModel,
|
||||
chatMessageViewModel: ChatMessageViewModel,
|
||||
) {
|
||||
const isHidden = React.createProxyState(
|
||||
[chatMessageViewModel.replyCount],
|
||||
() => chatMessageViewModel.replyCount.value == 0,
|
||||
);
|
||||
|
||||
function select() {
|
||||
chatMessageViewModel.messagePageViewModel.setReplyView(chatMessageViewModel);
|
||||
chatMessageViewModel.messagePageViewModel.setReplyView(
|
||||
chatMessageViewModel,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class="reply-link"
|
||||
toggle:hidden={isHidden}
|
||||
on:click={select}
|
||||
>
|
||||
<span>{coreViewModel.translations.chatPage.message.replyPrefixLabel}</span>
|
||||
<b class="ellipsis" subscribe:innerText={chatMessageViewModel.replyCount}></b>
|
||||
<div class="reply-link" toggle:hidden={isHidden} on:click={select}>
|
||||
<span>
|
||||
{coreViewModel.translations.chatPage.message.replyPrefixLabel}
|
||||
</span>
|
||||
<b
|
||||
class="ellipsis"
|
||||
subscribe:innerText={chatMessageViewModel.replyCount}
|
||||
></b>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,48 @@
|
||||
import * as React from "../../react";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
|
||||
export function ReplyViewHeader(coreViewModel: CoreViewModel, messagePageViewModel: MessagePageViewModel) {
|
||||
const isHidden = React.createProxyState([messagePageViewModel.isReplyViewActive], ()=>!messagePageViewModel.isReplyViewActive.value);
|
||||
export function ReplyViewHeader(
|
||||
coreViewModel: CoreViewModel,
|
||||
messagePageViewModel: MessagePageViewModel,
|
||||
) {
|
||||
const isHidden = React.createProxyState(
|
||||
[messagePageViewModel.isReplyViewActive],
|
||||
() => !messagePageViewModel.isReplyViewActive.value,
|
||||
);
|
||||
|
||||
const label = React.createProxyState([messagePageViewModel.replyViewSelectedMessage], ()=>messagePageViewModel.replyViewSelectedMessage.value == undefined ? "" : coreViewModel.translations.chatPage.message.replyHeaderLabel(messagePageViewModel.replyViewSelectedMessage.value.sender));
|
||||
const message = React.createProxyState([messagePageViewModel.replyViewSelectedMessage], ()=>messagePageViewModel.replyViewSelectedMessage.value == undefined ? "" : messagePageViewModel.replyViewSelectedMessage.value.body.value);
|
||||
const label = React.createProxyState(
|
||||
[messagePageViewModel.replyViewSelectedMessage],
|
||||
() =>
|
||||
messagePageViewModel.replyViewSelectedMessage.value == undefined
|
||||
? ""
|
||||
: coreViewModel.translations.chatPage.message.replyHeaderLabel(
|
||||
messagePageViewModel.replyViewSelectedMessage.value
|
||||
.sender,
|
||||
),
|
||||
);
|
||||
const message = React.createProxyState(
|
||||
[messagePageViewModel.replyViewSelectedMessage],
|
||||
() =>
|
||||
messagePageViewModel.replyViewSelectedMessage.value == undefined
|
||||
? ""
|
||||
: messagePageViewModel.replyViewSelectedMessage.value.body
|
||||
.value,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
class="reply-header"
|
||||
toggle:hidden={isHidden}
|
||||
>
|
||||
<b class="ellipsis" subscribe:innerText={label}></b>
|
||||
<span class="secondary ellipsis" subscribe:innerText={message}></span>
|
||||
<button
|
||||
class="standard square"
|
||||
on:click={messagePageViewModel.resetReplyView}
|
||||
>
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="reply-header" toggle:hidden={isHidden}>
|
||||
<b class="ellipsis" subscribe:innerText={label}></b>
|
||||
<span
|
||||
class="secondary ellipsis"
|
||||
subscribe:innerText={message}
|
||||
></span>
|
||||
<button
|
||||
class="standard square"
|
||||
on:click={messagePageViewModel.resetReplyView}
|
||||
>
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,9 +11,11 @@ export function TaskEntry(taskViewModel: TaskViewModel) {
|
||||
schedule: taskViewModel.time.value || "---",
|
||||
};
|
||||
|
||||
const className = React.createProxyState([taskViewModel.isNotNext], ()=>
|
||||
`${taskViewModel.isNotNext.value ? "standard" : "primary"} tile flex-no`
|
||||
)
|
||||
const className = React.createProxyState(
|
||||
[taskViewModel.isNotNext],
|
||||
() =>
|
||||
`${taskViewModel.isNotNext.value ? "standard" : "primary"} tile flex-no`,
|
||||
);
|
||||
|
||||
const view = (
|
||||
<button
|
||||
@@ -22,45 +24,45 @@ export function TaskEntry(taskViewModel: TaskViewModel) {
|
||||
style="user-select: none; -webkit-user-select: none"
|
||||
on:click={taskViewModel.open}
|
||||
on:dragstart={taskViewModel.dragStart}
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
class="secondary"
|
||||
toggle:hidden={taskViewModel.isNotNext}
|
||||
>
|
||||
{taskViewModel.coreViewModel.translations.chatPage.calendar.eventNext}
|
||||
<hr></hr>
|
||||
</span>
|
||||
<b
|
||||
class="ellipsis"
|
||||
subscribe:innerText={taskViewModel.name}
|
||||
></b>
|
||||
<hr></hr>
|
||||
<div
|
||||
class="grid secondary"
|
||||
style="grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem"
|
||||
>
|
||||
{...Object.entries(details).map((entry) => (
|
||||
<span
|
||||
class="flex-row align-center width-100 flex-no clip"
|
||||
style="gap: 1rem"
|
||||
>
|
||||
<span class="icon" style="font-size: 1.1rem">
|
||||
{entry[0]}
|
||||
</span>
|
||||
<span class="ellipsis">{entry[1]}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
>
|
||||
<div>
|
||||
<span class="secondary" toggle:hidden={taskViewModel.isNotNext}>
|
||||
{
|
||||
taskViewModel.coreViewModel.translations.chatPage
|
||||
.calendar.eventNext
|
||||
}
|
||||
<hr></hr>
|
||||
</span>
|
||||
<b
|
||||
class="ellipsis"
|
||||
subscribe:innerText={taskViewModel.name}
|
||||
></b>
|
||||
<hr></hr>
|
||||
<div
|
||||
class="grid secondary"
|
||||
style="grid-template-columns: repeat(2, 1fr); column-gap: 1rem; row-gap: .5rem"
|
||||
>
|
||||
{...Object.entries(details).map((entry) => (
|
||||
<span
|
||||
class="flex-row align-center width-100 flex-no clip"
|
||||
style="gap: 1rem"
|
||||
>
|
||||
<span class="icon" style="font-size: 1.1rem">
|
||||
{entry[0]}
|
||||
</span>
|
||||
<span class="ellipsis">{entry[1]}</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
||||
taskViewModel.index.subscribe((newIndex) => {
|
||||
view.style.order = newIndex;
|
||||
});
|
||||
taskViewModel.index.subscribe((newIndex) => {
|
||||
view.style.order = newIndex;
|
||||
});
|
||||
|
||||
return view;
|
||||
return view;
|
||||
}
|
||||
|
||||
export const TaskViewModelToEntry: React.StateItemConverter<TaskViewModel> = (
|
||||
|
||||
@@ -1,26 +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)}%`)
|
||||
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>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,10 +72,13 @@ export function ChatMessageInfoModal(
|
||||
}
|
||||
<span class="icon">key</span>
|
||||
</button>
|
||||
<button
|
||||
on:click={chatMessageViewModel.reply}
|
||||
toggle:disabled={chatMessageViewModel.messagePageViewModel.isReplyViewActive}
|
||||
>
|
||||
<button
|
||||
on:click={chatMessageViewModel.reply}
|
||||
toggle:disabled={
|
||||
chatMessageViewModel.messagePageViewModel
|
||||
.isReplyViewActive
|
||||
}
|
||||
>
|
||||
{
|
||||
coreViewModel.translations.chatPage.message
|
||||
.replyToMessageButton
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { v4 } from "uuid";
|
||||
import { ViewController } from "../viewController";
|
||||
import { StringToOption } from "../Components/option";
|
||||
import { DeletableListItem } from "../Components/deletableListItem";
|
||||
import * as React from "../../react";
|
||||
import SearchViewModel from "../../ViewModel/Utility/searchViewModel";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import {DeletableListItem} from "../Components/deletableListItem";
|
||||
|
||||
export function SearchModal<T>(
|
||||
coreViewModel: CoreViewModel,
|
||||
@@ -13,92 +13,92 @@ export function SearchModal<T>(
|
||||
isOpen: React.State<boolean>,
|
||||
) {
|
||||
function close() {
|
||||
isOpen.value = false;
|
||||
isOpen.value = false;
|
||||
}
|
||||
|
||||
const suggestionId = v4();
|
||||
|
||||
isOpen.subscribe((isOpen) => {
|
||||
if (!isOpen) return;
|
||||
ViewController.setFocusWithDelay();
|
||||
if (!isOpen) return;
|
||||
ViewController.setFocusWithDelay();
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="modal" toggle:open={isOpen} extended>
|
||||
<div>
|
||||
<main>
|
||||
<h2>{headline}</h2>
|
||||
<div class="flex-row">
|
||||
<input
|
||||
id="focused"
|
||||
style="max-width: unset"
|
||||
placeholder={
|
||||
coreViewModel.translations.general.searchLabel
|
||||
}
|
||||
bind:value={searchViewModel.searchInput}
|
||||
on:enter={searchViewModel.applySearch}
|
||||
list={suggestionId}
|
||||
></input>
|
||||
<datalist
|
||||
hidden
|
||||
id={suggestionId}
|
||||
children:append={[
|
||||
searchViewModel.suggestions,
|
||||
StringToOption,
|
||||
]}
|
||||
></datalist>
|
||||
<button
|
||||
class="standard"
|
||||
aria-label={
|
||||
coreViewModel.translations.general
|
||||
.searchButtonClearAudioLabel
|
||||
}
|
||||
on:click={searchViewModel.clear}
|
||||
toggle:disabled={searchViewModel.cannotClear}
|
||||
>
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={
|
||||
coreViewModel.translations.general
|
||||
.searchButtonAudioLabel
|
||||
}
|
||||
on:click={searchViewModel.applySearch}
|
||||
toggle:disabled={searchViewModel.cannotApplySearch}
|
||||
>
|
||||
<span class="icon">search</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal" toggle:open={isOpen} extended>
|
||||
<div>
|
||||
<main>
|
||||
<h2>{headline}</h2>
|
||||
<div class="flex-row">
|
||||
<input
|
||||
id="focused"
|
||||
style="max-width: unset"
|
||||
placeholder={
|
||||
coreViewModel.translations.general.searchLabel
|
||||
}
|
||||
bind:value={searchViewModel.searchInput}
|
||||
on:enter={searchViewModel.applySearch}
|
||||
list={suggestionId}
|
||||
></input>
|
||||
<datalist
|
||||
hidden
|
||||
id={suggestionId}
|
||||
children:append={[
|
||||
searchViewModel.suggestions,
|
||||
StringToOption,
|
||||
]}
|
||||
></datalist>
|
||||
<button
|
||||
class="standard"
|
||||
aria-label={
|
||||
coreViewModel.translations.general
|
||||
.searchButtonClearAudioLabel
|
||||
}
|
||||
on:click={searchViewModel.clear}
|
||||
toggle:disabled={searchViewModel.cannotClear}
|
||||
>
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={
|
||||
coreViewModel.translations.general
|
||||
.searchButtonAudioLabel
|
||||
}
|
||||
on:click={searchViewModel.applySearch}
|
||||
toggle:disabled={searchViewModel.cannotApplySearch}
|
||||
>
|
||||
<span class="icon">search</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div toggle:hidden={searchViewModel.hasNoSuggestions}>
|
||||
<hr></hr>
|
||||
<h3>
|
||||
{
|
||||
coreViewModel.translations.general
|
||||
.searchSuggestionsLabel
|
||||
}
|
||||
</h3>
|
||||
<div
|
||||
class="flex-column gap"
|
||||
children:append={[
|
||||
searchViewModel.suggestions,
|
||||
(suggestion: string) =>
|
||||
SuggestionView(
|
||||
suggestion,
|
||||
searchViewModel,
|
||||
coreViewModel,
|
||||
),
|
||||
]}
|
||||
></div>
|
||||
</div>
|
||||
</main>
|
||||
<button on:click={close}>
|
||||
{coreViewModel.translations.general.closeButton}
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div toggle:hidden={searchViewModel.hasNoSuggestions}>
|
||||
<hr></hr>
|
||||
<h3>
|
||||
{
|
||||
coreViewModel.translations.general
|
||||
.searchSuggestionsLabel
|
||||
}
|
||||
</h3>
|
||||
<div
|
||||
class="flex-column gap"
|
||||
children:append={[
|
||||
searchViewModel.suggestions,
|
||||
(suggestion: string) =>
|
||||
SuggestionView(
|
||||
suggestion,
|
||||
searchViewModel,
|
||||
coreViewModel,
|
||||
),
|
||||
]}
|
||||
></div>
|
||||
</div>
|
||||
</main>
|
||||
<button on:click={close}>
|
||||
{coreViewModel.translations.general.closeButton}
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,26 +108,29 @@ function SuggestionView<T>(
|
||||
coreViewModel: CoreViewModel,
|
||||
) {
|
||||
const isApplied = React.createProxyState(
|
||||
[searchViewModel.appliedQuery],
|
||||
() => searchViewModel.appliedQuery.value == suggestion,
|
||||
[searchViewModel.appliedQuery],
|
||||
() => searchViewModel.appliedQuery.value == suggestion,
|
||||
);
|
||||
|
||||
function deleteSuggestion() {
|
||||
searchViewModel.deleteSuggestion(suggestion);
|
||||
searchViewModel.deleteSuggestion(suggestion);
|
||||
}
|
||||
|
||||
function applySuggestion() {
|
||||
searchViewModel.search(suggestion);
|
||||
searchViewModel.search(suggestion);
|
||||
}
|
||||
|
||||
return DeletableListItem(coreViewModel, suggestion,
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={coreViewModel.translations.general.applyButton}
|
||||
on:click={applySuggestion}
|
||||
toggle:disabled={isApplied}
|
||||
>
|
||||
<span class="icon">check</span>
|
||||
</button>
|
||||
, deleteSuggestion)
|
||||
return DeletableListItem(
|
||||
coreViewModel,
|
||||
suggestion,
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={coreViewModel.translations.general.applyButton}
|
||||
on:click={applySuggestion}
|
||||
toggle:disabled={isApplied}
|
||||
>
|
||||
<span class="icon">check</span>
|
||||
</button>,
|
||||
deleteSuggestion,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -125,25 +125,20 @@ function SettingsInfoPane(
|
||||
settingsViewModel.coreViewModel.version,
|
||||
)}
|
||||
|
||||
<button
|
||||
class="primary"
|
||||
on:click={coreViewModel.update}
|
||||
toggle:hidden={coreViewModel.noUpdateAvailable}
|
||||
>
|
||||
<span
|
||||
subscribe:innerText={coreViewModel.updateText}
|
||||
></span>
|
||||
<span class="icon">update</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
on:click={coreViewModel.update}
|
||||
toggle:hidden={coreViewModel.noUpdateAvailable}
|
||||
>
|
||||
<span subscribe:innerText={coreViewModel.updateText}></span>
|
||||
<span class="icon">update</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="standard"
|
||||
on:click={coreViewModel.checkUpdates}
|
||||
>
|
||||
{coreViewModel.translations.settings.checkUpdatesButton}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="standard" on:click={coreViewModel.checkUpdates}>
|
||||
{coreViewModel.translations.settings.checkUpdatesButton}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -152,30 +147,30 @@ function SettingsRegionalPane(
|
||||
settingsViewModel: SettingsViewModel,
|
||||
) {
|
||||
return (
|
||||
<div class="slide-up">
|
||||
<h2>{coreViewModel.translations.settings.pages.regional}</h2>
|
||||
<div class="slide-up">
|
||||
<h2>{coreViewModel.translations.settings.pages.regional}</h2>
|
||||
|
||||
<hr></hr>
|
||||
<h3>{coreViewModel.translations.settings.language}</h3>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>(
|
||||
Object.values(Languages).map((x) => [languageNames[x], x]),
|
||||
),
|
||||
settingsViewModel.language,
|
||||
)}
|
||||
<hr></hr>
|
||||
<h3>{coreViewModel.translations.settings.language}</h3>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>(
|
||||
Object.values(Languages).map((x) => [languageNames[x], x]),
|
||||
),
|
||||
settingsViewModel.language,
|
||||
)}
|
||||
|
||||
<hr></hr>
|
||||
<h3>{coreViewModel.translations.settings.firstDayOfWeekLabel}</h3>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>(
|
||||
coreViewModel.translations.regional.weekdays.full.map(
|
||||
(x, i) => [x, i.toString()],
|
||||
),
|
||||
),
|
||||
settingsViewModel.firstDayOfWeek,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<hr></hr>
|
||||
<h3>{coreViewModel.translations.settings.firstDayOfWeekLabel}</h3>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>(
|
||||
coreViewModel.translations.regional.weekdays.full.map(
|
||||
(x, i) => [x, i.toString()],
|
||||
),
|
||||
),
|
||||
settingsViewModel.firstDayOfWeek,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsAppearancePane(
|
||||
@@ -183,34 +178,34 @@ function SettingsAppearancePane(
|
||||
settingsViewModel: SettingsViewModel,
|
||||
) {
|
||||
return (
|
||||
<div class="slide-up">
|
||||
<h2>{coreViewModel.translations.settings.pages.appearance}</h2>
|
||||
<hr></hr>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>([
|
||||
[
|
||||
coreViewModel.translations.settings.themes.dynamic,
|
||||
ThemeSettings.Dynamic,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.system,
|
||||
ThemeSettings.System,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.dark,
|
||||
ThemeSettings.Dark,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.light,
|
||||
ThemeSettings.Light,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.black,
|
||||
ThemeSettings.Black,
|
||||
],
|
||||
]),
|
||||
settingsViewModel.theme,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<div class="slide-up">
|
||||
<h2>{coreViewModel.translations.settings.pages.appearance}</h2>
|
||||
<hr></hr>
|
||||
{OptionButtonList(
|
||||
new React.ListState<[string, string]>([
|
||||
[
|
||||
coreViewModel.translations.settings.themes.dynamic,
|
||||
ThemeSettings.Dynamic,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.system,
|
||||
ThemeSettings.System,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.dark,
|
||||
ThemeSettings.Dark,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.light,
|
||||
ThemeSettings.Light,
|
||||
],
|
||||
[
|
||||
coreViewModel.translations.settings.themes.black,
|
||||
ThemeSettings.Black,
|
||||
],
|
||||
]),
|
||||
settingsViewModel.theme,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,94 +1,105 @@
|
||||
import { SplitModal } from "./splitModal";
|
||||
import { UsageBar } from "../Components/usageBar";
|
||||
import { DirectoryItemList } from "../Components/directoryItemList";
|
||||
import { DangerousActionButton } from "../Components/dangerousActionButton";
|
||||
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 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 class="flex-column gap">
|
||||
<span class="secondary">
|
||||
{coreViewModel.translations.storage.noItemSelected}
|
||||
</span>
|
||||
[storageViewModel.selectedPath],
|
||||
() => {
|
||||
if (storageViewModel.selectedPath.value == PATH_COMPONENT_SEPARATOR)
|
||||
return (
|
||||
<div class="flex-column gap">
|
||||
<span class="secondary">
|
||||
{coreViewModel.translations.storage.noItemSelected}
|
||||
</span>
|
||||
|
||||
<hr></hr>
|
||||
<hr></hr>
|
||||
|
||||
{UsageBar(coreViewModel.translations.storage.usageLabel, usageValueLabel, storageViewModel.occupiedSpaceMB, storageViewModel.maximumSpaceMB)}
|
||||
{UsageBar(
|
||||
coreViewModel.translations.storage.usageLabel,
|
||||
usageValueLabel,
|
||||
storageViewModel.occupiedSpaceMB,
|
||||
storageViewModel.maximumSpaceMB,
|
||||
)}
|
||||
|
||||
<hr></hr>
|
||||
<hr></hr>
|
||||
|
||||
{DangerousActionButton(
|
||||
coreViewModel,
|
||||
coreViewModel.translations.storage.removeJunkButton,
|
||||
"delete_forever",
|
||||
storageViewModel.removeJunk,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{DangerousActionButton(
|
||||
coreViewModel,
|
||||
coreViewModel.translations.storage.removeJunkButton,
|
||||
"delete_forever",
|
||||
storageViewModel.removeJunk,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div class="flex-column gap">
|
||||
<div class="tile flex-no">
|
||||
<div>
|
||||
<b>{coreViewModel.translations.storage.path}</b>
|
||||
<span
|
||||
class="break-all"
|
||||
subscribe:innerText={
|
||||
storageViewModel.selectedPath
|
||||
}
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile flex-no">
|
||||
<div>
|
||||
<b>{coreViewModel.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>{coreViewModel.translations.storage.path}</b>
|
||||
<span
|
||||
class="break-all"
|
||||
subscribe:innerText={
|
||||
storageViewModel.selectedPath
|
||||
}
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tile flex-no">
|
||||
<div>
|
||||
<b>{coreViewModel.translations.storage.content}</b>
|
||||
<code
|
||||
subscribe:innerText={
|
||||
storageViewModel.selectedFileContent
|
||||
}
|
||||
></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{DangerousActionButton(
|
||||
coreViewModel,
|
||||
coreViewModel.translations.storage.deleteItem,
|
||||
"delete_forever",
|
||||
storageViewModel.deleteSelectedItem,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
{DangerousActionButton(
|
||||
coreViewModel,
|
||||
coreViewModel.translations.storage.deleteItem,
|
||||
"delete_forever",
|
||||
storageViewModel.deleteSelectedItem,
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
return (
|
||||
<div class="modal" toggle:open={storageViewModel.isShowingStorageModal}>
|
||||
<div>
|
||||
<main class="padding-0">
|
||||
{SplitModal(
|
||||
coreViewModel,
|
||||
new React.State(DirectoryItemList(storageViewModel)),
|
||||
detailView,
|
||||
true,
|
||||
)}
|
||||
</main>
|
||||
<button on:click={storageViewModel.close}>
|
||||
{coreViewModel.translations.general.closeButton}
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal" toggle:open={storageViewModel.isShowingStorageModal}>
|
||||
<div>
|
||||
<main class="padding-0">
|
||||
{SplitModal(
|
||||
coreViewModel,
|
||||
new React.State(DirectoryItemList(storageViewModel)),
|
||||
detailView,
|
||||
true,
|
||||
)}
|
||||
</main>
|
||||
<button on:click={storageViewModel.close}>
|
||||
{coreViewModel.translations.general.closeButton}
|
||||
<span class="icon">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+174
-176
@@ -20,195 +20,193 @@ export function HomePage(
|
||||
) {
|
||||
// sections
|
||||
const overviewSection = (
|
||||
<div id="overview-section">
|
||||
<h2>{coreViewModel.translations.homePage.overviewHeadline}</h2>
|
||||
<div id="overview-section">
|
||||
<h2>{coreViewModel.translations.homePage.overviewHeadline}</h2>
|
||||
|
||||
<label class="tile flex-no">
|
||||
<span class="icon">cell_tower</span>
|
||||
<div>
|
||||
<span>
|
||||
{coreViewModel.translations.homePage.serverAddress}
|
||||
</span>
|
||||
<input
|
||||
list="previous-connection-list"
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage
|
||||
.serverAddressPlaceholder
|
||||
}
|
||||
bind:value={connectionViewModel.serverAddressInput}
|
||||
on:enter={connectionViewModel.connect}
|
||||
></input>
|
||||
<datalist
|
||||
hidden
|
||||
id="previous-connection-list"
|
||||
children:append={[
|
||||
connectionViewModel.previousAddresses,
|
||||
StringToOption,
|
||||
]}
|
||||
></datalist>
|
||||
</div>
|
||||
</label>
|
||||
<label class="tile flex-no">
|
||||
<span class="icon">cell_tower</span>
|
||||
<div>
|
||||
<span>
|
||||
{coreViewModel.translations.homePage.serverAddress}
|
||||
</span>
|
||||
<input
|
||||
list="previous-connection-list"
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage
|
||||
.serverAddressPlaceholder
|
||||
}
|
||||
bind:value={connectionViewModel.serverAddressInput}
|
||||
on:enter={connectionViewModel.connect}
|
||||
></input>
|
||||
<datalist
|
||||
hidden
|
||||
id="previous-connection-list"
|
||||
children:append={[
|
||||
connectionViewModel.previousAddresses,
|
||||
StringToOption,
|
||||
]}
|
||||
></datalist>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div class="flex-row">
|
||||
<button
|
||||
class="danger flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.disconnectAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.disconnect}
|
||||
toggle:disabled={connectionViewModel.cannotDisonnect}
|
||||
>
|
||||
<span class="icon">link_off</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage
|
||||
.manageConnectionsAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.showConnectionModal}
|
||||
toggle:disabled={
|
||||
connectionViewModel.hasNoPreviousConnections
|
||||
}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.connectAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.connect}
|
||||
toggle:disabled={connectionViewModel.cannotConnect}
|
||||
>
|
||||
<span class="icon">link</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
<button
|
||||
class="danger flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.disconnectAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.disconnect}
|
||||
toggle:disabled={connectionViewModel.cannotDisonnect}
|
||||
>
|
||||
<span class="icon">link_off</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage
|
||||
.manageConnectionsAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.showConnectionModal}
|
||||
toggle:disabled={
|
||||
connectionViewModel.hasNoPreviousConnections
|
||||
}
|
||||
>
|
||||
<span class="icon">history</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary flex justify-center"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.connectAudioLabel
|
||||
}
|
||||
on:click={connectionViewModel.connect}
|
||||
toggle:disabled={connectionViewModel.cannotConnect}
|
||||
>
|
||||
<span class="icon">link</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr></hr>
|
||||
<hr></hr>
|
||||
|
||||
<label class="tile flex-no">
|
||||
<span class="icon">account_circle</span>
|
||||
<div>
|
||||
<span>
|
||||
{coreViewModel.translations.homePage.yourNameLabel}
|
||||
</span>
|
||||
<input
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage
|
||||
.yourNamePlaceholder
|
||||
}
|
||||
bind:value={settingsViewModel.usernameInput}
|
||||
on:enter={settingsViewModel.setName}
|
||||
></input>
|
||||
</div>
|
||||
</label>
|
||||
<div class="flex-row justify-end">
|
||||
<button
|
||||
class="width-50"
|
||||
on:click={settingsViewModel.setName}
|
||||
toggle:disabled={settingsViewModel.cannotSetName}
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage
|
||||
.setNameButtonAudioLabel
|
||||
}
|
||||
>
|
||||
{coreViewModel.translations.general.setButton}
|
||||
<span class="icon">check</span>
|
||||
</button>
|
||||
</div>
|
||||
<label class="tile flex-no">
|
||||
<span class="icon">account_circle</span>
|
||||
<div>
|
||||
<span>
|
||||
{coreViewModel.translations.homePage.yourNameLabel}
|
||||
</span>
|
||||
<input
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage
|
||||
.yourNamePlaceholder
|
||||
}
|
||||
bind:value={settingsViewModel.usernameInput}
|
||||
on:enter={settingsViewModel.setName}
|
||||
></input>
|
||||
</div>
|
||||
</label>
|
||||
<div class="flex-row justify-end">
|
||||
<button
|
||||
class="width-50"
|
||||
on:click={settingsViewModel.setName}
|
||||
toggle:disabled={settingsViewModel.cannotSetName}
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage
|
||||
.setNameButtonAudioLabel
|
||||
}
|
||||
>
|
||||
{coreViewModel.translations.general.setButton}
|
||||
<span class="icon">check</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr></hr>
|
||||
<hr></hr>
|
||||
|
||||
{HomePageButton(
|
||||
settingsViewModel.showSettingsModal,
|
||||
coreViewModel.translations.homePage.settingsButton,
|
||||
"settings",
|
||||
)}
|
||||
{HomePageButton(
|
||||
fileTransferViewModel.showDirectionSelectionModal,
|
||||
coreViewModel.translations.homePage.transferDataButton,
|
||||
"sync_alt",
|
||||
)}
|
||||
{HomePageButton(
|
||||
storageViewModel.showStorageModal,
|
||||
coreViewModel.translations.homePage.manageStorageButton,
|
||||
"hard_drive",
|
||||
)}
|
||||
{HomePageButton(
|
||||
settingsViewModel.showSettingsModal,
|
||||
coreViewModel.translations.homePage.settingsButton,
|
||||
"settings",
|
||||
)}
|
||||
{HomePageButton(
|
||||
fileTransferViewModel.showDirectionSelectionModal,
|
||||
coreViewModel.translations.homePage.transferDataButton,
|
||||
"sync_alt",
|
||||
)}
|
||||
{HomePageButton(
|
||||
storageViewModel.showStorageModal,
|
||||
coreViewModel.translations.homePage.manageStorageButton,
|
||||
"hard_drive",
|
||||
)}
|
||||
|
||||
<button
|
||||
class="primary"
|
||||
on:click={coreViewModel.update}
|
||||
toggle:hidden={coreViewModel.noUpdateAvailable}
|
||||
>
|
||||
<span
|
||||
subscribe:innerText={coreViewModel.updateText}
|
||||
></span>
|
||||
<span class="icon">update</span>
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
on:click={coreViewModel.update}
|
||||
toggle:hidden={coreViewModel.noUpdateAvailable}
|
||||
>
|
||||
<span subscribe:innerText={coreViewModel.updateText}></span>
|
||||
<span class="icon">update</span>
|
||||
</button>
|
||||
|
||||
<div class="mobile-only">
|
||||
<hr></hr>
|
||||
<div class="mobile-only">
|
||||
<hr></hr>
|
||||
|
||||
<div class="flex-row justify-end">
|
||||
<button class="ghost width-50" on:click={scrollToChat}>
|
||||
{coreViewModel.translations.homePage.scrollToChatButton}
|
||||
<span class="icon">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div class="flex-row justify-end">
|
||||
<button class="ghost width-50" on:click={scrollToChat}>
|
||||
{coreViewModel.translations.homePage.scrollToChatButton}
|
||||
<span class="icon">arrow_forward</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const chatSection = (
|
||||
<div id="chat-section">
|
||||
<h2>{coreViewModel.translations.homePage.chatsHeadline}</h2>
|
||||
const chatSection = (
|
||||
<div id="chat-section">
|
||||
<h2>{coreViewModel.translations.homePage.chatsHeadline}</h2>
|
||||
|
||||
<div class="flex-row width-input">
|
||||
<input
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage.addChatPlaceholder
|
||||
}
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.addChatAudioLabel
|
||||
}
|
||||
bind:value={chatListViewModel.newChatPrimaryChannel}
|
||||
on:enter={chatListViewModel.createChat}
|
||||
></input>
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.addChatButton
|
||||
}
|
||||
on:click={chatListViewModel.createChat}
|
||||
toggle:disabled={chatListViewModel.cannotCreateChat}
|
||||
>
|
||||
<span class="icon">add</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex-row width-input">
|
||||
<input
|
||||
placeholder={
|
||||
coreViewModel.translations.homePage.addChatPlaceholder
|
||||
}
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.addChatAudioLabel
|
||||
}
|
||||
bind:value={chatListViewModel.newChatPrimaryChannel}
|
||||
on:enter={chatListViewModel.createChat}
|
||||
></input>
|
||||
<button
|
||||
class="primary"
|
||||
aria-label={
|
||||
coreViewModel.translations.homePage.addChatButton
|
||||
}
|
||||
on:click={chatListViewModel.createChat}
|
||||
toggle:disabled={chatListViewModel.cannotCreateChat}
|
||||
>
|
||||
<span class="icon">add</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="chat-grid"
|
||||
children:append={[
|
||||
chatListViewModel.chatViewModels,
|
||||
ChatViewModelToChatEntry,
|
||||
]}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
<div
|
||||
id="chat-grid"
|
||||
children:append={[
|
||||
chatListViewModel.chatViewModels,
|
||||
ChatViewModelToChatEntry,
|
||||
]}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// methods
|
||||
function scrollToChat() {
|
||||
chatSection.scrollIntoView();
|
||||
}
|
||||
// methods
|
||||
function scrollToChat() {
|
||||
chatSection.scrollIntoView();
|
||||
}
|
||||
|
||||
// final
|
||||
return (
|
||||
<article id="home-page">
|
||||
<div>
|
||||
{overviewSection}
|
||||
{chatSection}
|
||||
</div>
|
||||
</article>
|
||||
// final
|
||||
return (
|
||||
<article id="home-page">
|
||||
<div>
|
||||
{overviewSection}
|
||||
{chatSection}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
+790
-786
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user