Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 891d54c29f |
8 changed files with 121 additions and 82 deletions
|
|
@ -1,8 +1,15 @@
|
||||||
|
import { lazy, Suspense } from "react";
|
||||||
|
|
||||||
import { useCall } from "@tensamin/call/state";
|
import { useCall } from "@tensamin/call/state";
|
||||||
import Popout from "@tensamin/call/popout";
|
|
||||||
|
const Popout = lazy(() => import("@tensamin/call/popout"));
|
||||||
|
|
||||||
export default function CallPopout() {
|
export default function CallPopout() {
|
||||||
const active = useCall((state) => state.state !== "closed");
|
const active = useCall((state) => state.state !== "closed");
|
||||||
|
|
||||||
return active ? <Popout /> : null;
|
return active ? (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<Popout />
|
||||||
|
</Suspense>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
|
import { lazy, Suspense } from "react";
|
||||||
|
|
||||||
import { useCall } from "@tensamin/call/state";
|
import { useCall } from "@tensamin/call/state";
|
||||||
import SidebarBox from "@tensamin/call/sidebarBox";
|
|
||||||
|
const SidebarBox = lazy(() => import("@tensamin/call/sidebarBox"));
|
||||||
|
|
||||||
export default function CallSidebarBox() {
|
export default function CallSidebarBox() {
|
||||||
const active = useCall((state) => state.state !== "closed");
|
const active = useCall((state) => state.state !== "closed");
|
||||||
|
|
||||||
return active ? <SidebarBox /> : null;
|
return active ? (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<SidebarBox />
|
||||||
|
</Suspense>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import { useState } from "react";
|
||||||
import { SidebarTrigger, useSidebar } from "@methanium/ui";
|
import { SidebarTrigger, useSidebar } from "@methanium/ui";
|
||||||
import { WindowControls as Controls } from "@methanium/ui";
|
import { WindowControls as Controls } from "@methanium/ui";
|
||||||
import { useSession } from "@tensamin/identity/session";
|
import { useSession } from "@tensamin/identity/session";
|
||||||
import { joinCall } from "@tensamin/call/store";
|
|
||||||
import Profile from "./modals/profile";
|
import Profile from "./modals/profile";
|
||||||
|
|
||||||
export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
|
|
@ -140,7 +139,11 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
{currentCalls.length === 0 ? (
|
{currentCalls.length === 0 ? (
|
||||||
<Button
|
<Button
|
||||||
disabled={callState !== "closed"}
|
disabled={callState !== "closed"}
|
||||||
onClick={() => void joinCall(id)}
|
onClick={() => {
|
||||||
|
void import("@tensamin/call/store").then(({ joinCall }) =>
|
||||||
|
joinCall(id),
|
||||||
|
);
|
||||||
|
}}
|
||||||
className="w-9 h-9! aspect-square rounded-lg"
|
className="w-9 h-9! aspect-square rounded-lg"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
|
|
@ -150,10 +153,12 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
<Button
|
<Button
|
||||||
disabled={callState !== "closed"}
|
disabled={callState !== "closed"}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void joinCall(
|
void import("@tensamin/call/store").then(({ joinCall }) =>
|
||||||
id,
|
joinCall(
|
||||||
currentCalls[0].CallSecret,
|
id,
|
||||||
currentCalls[0].CallId,
|
currentCalls[0].CallSecret,
|
||||||
|
currentCalls[0].CallId,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="w-9 h-9! aspect-square rounded-lg"
|
className="w-9 h-9! aspect-square rounded-lg"
|
||||||
|
|
@ -180,9 +185,12 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
<SelectItem
|
<SelectItem
|
||||||
value={call.CallId}
|
value={call.CallId}
|
||||||
key={call.CallId}
|
key={call.CallId}
|
||||||
onSelect={() =>
|
onSelect={() => {
|
||||||
void joinCall(id, call.CallSecret, call.CallId)
|
void import("@tensamin/call/store").then(
|
||||||
}
|
({ joinCall }) =>
|
||||||
|
joinCall(id, call.CallSecret, call.CallId),
|
||||||
|
);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{displayCallId(call.CallId)}
|
{displayCallId(call.CallId)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,8 @@ import "@methanium/ui/index.css";
|
||||||
|
|
||||||
import NotFound from "@/routes/404";
|
import NotFound from "@/routes/404";
|
||||||
import RouteLoader from "@/components/routeLoader";
|
import RouteLoader from "@/components/routeLoader";
|
||||||
import Home from "@/routes/app/home";
|
|
||||||
import AppShell from "@/routes/app/shell";
|
|
||||||
import Login from "@/routes/screens/login";
|
|
||||||
|
|
||||||
import { createSettingsRoute } from "@tensamin/settings";
|
import { createSettingsRoute } from "@tensamin/settings";
|
||||||
import ChatScreen from "@tensamin/chat/screen";
|
|
||||||
import CallScreen from "@tensamin/call/screen";
|
|
||||||
|
|
||||||
import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
|
import DeeplinkContext from "@tensamin/tauri/deeplinkHandler";
|
||||||
import PwaRuntime from "@tensamin/pwa/runtime";
|
import PwaRuntime from "@tensamin/pwa/runtime";
|
||||||
|
|
@ -27,7 +22,14 @@ import PwaRuntime from "@tensamin/pwa/runtime";
|
||||||
import { ErrorScreen, ThemeProvider, useTheme } from "@methanium/ui";
|
import { ErrorScreen, ThemeProvider, useTheme } from "@methanium/ui";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
import {
|
||||||
|
lazy,
|
||||||
|
Suspense,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
import Storage from "@tensamin/storage/context";
|
import Storage from "@tensamin/storage/context";
|
||||||
import { log } from "@tensamin/shared/log";
|
import { log } from "@tensamin/shared/log";
|
||||||
|
|
@ -37,6 +39,13 @@ import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
import { useIsMobile, Toaster, TooltipProvider } from "@methanium/ui";
|
import { useIsMobile, Toaster, TooltipProvider } from "@methanium/ui";
|
||||||
import { HotkeysProvider } from "@tensamin/hotkeys";
|
import { HotkeysProvider } from "@tensamin/hotkeys";
|
||||||
|
|
||||||
|
const Home = lazy(() => import("@/routes/app/home"));
|
||||||
|
const ChatScreen = lazy(() => import("@tensamin/chat/screen"));
|
||||||
|
const CallScreen = lazy(() => import("@tensamin/call/screen"));
|
||||||
|
const Login = lazy(() => import("@/routes/screens/login"));
|
||||||
|
const appShellImport = import("@/routes/app/shell");
|
||||||
|
const AppShell = lazy(() => appShellImport);
|
||||||
|
|
||||||
const wrapper = document.getElementById("root");
|
const wrapper = document.getElementById("root");
|
||||||
|
|
||||||
if (!wrapper) {
|
if (!wrapper) {
|
||||||
|
|
@ -251,7 +260,9 @@ function RootShell() {
|
||||||
<HotkeysProvider>
|
<HotkeysProvider>
|
||||||
<ThemeStorageBridge />
|
<ThemeStorageBridge />
|
||||||
<LoginWrapper>
|
<LoginWrapper>
|
||||||
<Outlet />
|
<Suspense fallback={<RouteLoader />}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
</LoginWrapper>
|
</LoginWrapper>
|
||||||
</HotkeysProvider>
|
</HotkeysProvider>
|
||||||
</Storage>
|
</Storage>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useRef } from "react";
|
import { Suspense, useEffect, useRef } from "react";
|
||||||
import { Outlet, useNavigate } from "@tanstack/react-router";
|
import { Outlet, useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
import RouteLoader from "@/components/routeLoader";
|
||||||
import AppLayout from "./layout";
|
import AppLayout from "./layout";
|
||||||
import CallInit from "@/components/callRuntimeInit";
|
import CallInit from "@/components/callRuntimeInit";
|
||||||
import CacheSync from "@tensamin/cache/sync";
|
import CacheSync from "@tensamin/cache/sync";
|
||||||
|
|
@ -60,7 +61,9 @@ export default function AppShell() {
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
<ChatContext>
|
<ChatContext>
|
||||||
<NotificationsProvider>
|
<NotificationsProvider>
|
||||||
<Outlet />
|
<Suspense fallback={<RouteLoader />}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
</NotificationsProvider>
|
</NotificationsProvider>
|
||||||
</ChatContext>
|
</ChatContext>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,14 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@methanium/ui";
|
} from "@methanium/ui";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import React, { useCallback, useEffect, useState, useRef } from "react";
|
import React, {
|
||||||
|
lazy,
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
import { Button } from "@methanium/ui";
|
import { Button } from "@methanium/ui";
|
||||||
|
|
||||||
import { Plus, Laugh, FileVideo, SendHorizonal } from "lucide-react";
|
import { Plus, Laugh, FileVideo, SendHorizonal } from "lucide-react";
|
||||||
|
|
@ -26,8 +33,9 @@ import ReplyBox from "./replyBox";
|
||||||
import { useHotkey } from "@tensamin/hotkeys";
|
import { useHotkey } from "@tensamin/hotkeys";
|
||||||
import { editLastMessageHotkey } from "../hotkeys";
|
import { editLastMessageHotkey } from "../hotkeys";
|
||||||
import { useUser } from "@tensamin/identity/context";
|
import { useUser } from "@tensamin/identity/context";
|
||||||
import EmojiPicker from "./emoji/emojiPicker";
|
|
||||||
import GifPicker from "./media/gifPicker";
|
const EmojiPicker = lazy(() => import("./emoji/emojiPicker"));
|
||||||
|
const GifPicker = lazy(() => import("./media/gifPicker"));
|
||||||
|
|
||||||
export default function InputComponent({
|
export default function InputComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -351,13 +359,15 @@ export default function InputComponent({
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<PopoverContent className="w-auto p-0">
|
<PopoverContent className="w-auto p-0">
|
||||||
<EmojiPicker
|
<Suspense fallback={null}>
|
||||||
onSelect={(shortcode) => {
|
<EmojiPicker
|
||||||
setValue(`${value}${shortcode} `);
|
onSelect={(shortcode) => {
|
||||||
recordUse(shortcode);
|
setValue(`${value}${shortcode} `);
|
||||||
setEmojiPopoverOpen(false);
|
recordUse(shortcode);
|
||||||
}}
|
setEmojiPopoverOpen(false);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
|
|
@ -371,12 +381,14 @@ export default function InputComponent({
|
||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
<DrawerContent className="h-[80dvh]">
|
<DrawerContent className="h-[80dvh]">
|
||||||
<div className="min-h-0 flex-1 p-3">
|
<div className="min-h-0 flex-1 p-3">
|
||||||
<GifPicker
|
<Suspense fallback={null}>
|
||||||
onSelect={(url) => {
|
<GifPicker
|
||||||
void handleSubmit(url, true);
|
onSelect={(url) => {
|
||||||
setGifPopoverOpen(false);
|
void handleSubmit(url, true);
|
||||||
}}
|
setGifPopoverOpen(false);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
@ -408,14 +420,16 @@ export default function InputComponent({
|
||||||
className="absolute left-0 top-0 z-10 h-4 w-4 cursor-nwse-resize"
|
className="absolute left-0 top-0 z-10 h-4 w-4 cursor-nwse-resize"
|
||||||
onPointerDown={handleGifPopoverResizeStart}
|
onPointerDown={handleGifPopoverResizeStart}
|
||||||
/>
|
/>
|
||||||
<GifPicker
|
<Suspense fallback={null}>
|
||||||
resizeHeight={gifPopoverSize?.height}
|
<GifPicker
|
||||||
resizeWidth={gifPopoverSize?.width}
|
resizeHeight={gifPopoverSize?.height}
|
||||||
onSelect={(url) => {
|
resizeWidth={gifPopoverSize?.width}
|
||||||
void handleSubmit(url, true);
|
onSelect={(url) => {
|
||||||
setGifPopoverOpen(false);
|
void handleSubmit(url, true);
|
||||||
}}
|
setGifPopoverOpen(false);
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
import type { RawMessage } from "../values";
|
import type { RawMessage } from "../values";
|
||||||
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
|
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
|
||||||
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
import { memo, useCallback, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import { type SelectedUser, useUserFields } from "@tensamin/identity/context";
|
||||||
type SelectedUser,
|
|
||||||
useUser,
|
|
||||||
useUserFields,
|
|
||||||
} from "@tensamin/identity/context";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
|
|
@ -73,7 +69,6 @@ function MessageComponent({
|
||||||
// Message states
|
// Message states
|
||||||
const { load } = useStorage();
|
const { load } = useStorage();
|
||||||
const { send, sendSealedRelay } = useMTP();
|
const { send, sendSealedRelay } = useMTP();
|
||||||
const { getIota } = useUser();
|
|
||||||
const [ownId, setOwnId] = useState(0);
|
const [ownId, setOwnId] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
load("user_id").then(setOwnId);
|
load("user_id").then(setOwnId);
|
||||||
|
|
@ -81,34 +76,38 @@ function MessageComponent({
|
||||||
const messageStateReadUpdate = useCallback(async () => {
|
const messageStateReadUpdate = useCallback(async () => {
|
||||||
const readConfirmations = await load("settings.read_confirmations");
|
const readConfirmations = await load("settings.read_confirmations");
|
||||||
|
|
||||||
if (!user?.UserId || ownId === 0) return;
|
if (!user?.UserId || !message.RelayMessageId || ownId === 0) return;
|
||||||
const [ownIota, peerIota] = await Promise.all([
|
const [ownIota, peerIota] = await Promise.all([
|
||||||
getIota(ownId),
|
send("GetIotaData", { UserId: ownId }),
|
||||||
getIota(user.UserId),
|
send("GetIotaData", { UserId: user.UserId }),
|
||||||
]);
|
]);
|
||||||
|
if (ownIota.type !== "GetIotaData" || peerIota.type !== "GetIotaData") {
|
||||||
|
throw new Error("Could not resolve an Iota for this receipt");
|
||||||
|
}
|
||||||
const response = await sendSealedRelay(
|
const response = await sendSealedRelay(
|
||||||
"MessageState",
|
"MessageState",
|
||||||
{
|
{
|
||||||
ChatPartnerId: user.UserId,
|
ChatPartnerId: user.UserId,
|
||||||
|
RelayMessageId: message.RelayMessageId,
|
||||||
EventAt: Date.now(),
|
EventAt: Date.now(),
|
||||||
MessageState: readConfirmations ? "read" : "received",
|
MessageState: readConfirmations ? "read" : "received",
|
||||||
ReceiverId: user.UserId,
|
ReceiverId: user.UserId,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nextHop: { kind: "iota", id: ownIota.IotaId },
|
nextHop: { kind: "iota", id: ownIota.data.IotaId },
|
||||||
finalRecipientId: user.UserId,
|
finalRecipientId: user.UserId,
|
||||||
metadataRecipients: [
|
metadataRecipients: [
|
||||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||||
],
|
],
|
||||||
contentRecipients: [
|
contentRecipients: [
|
||||||
{ value: ownIota.PublicKey, encoding: "base64" },
|
{ value: ownIota.data.PublicKey, encoding: "base64" },
|
||||||
{ value: peerIota.PublicKey, encoding: "base64" },
|
{ value: peerIota.data.PublicKey, encoding: "base64" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
requireRelaySuccess(response);
|
requireRelaySuccess(response);
|
||||||
}, [getIota, load, ownId, sendSealedRelay, user]);
|
}, [load, message.RelayMessageId, ownId, send, sendSealedRelay, user]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (message.SenderId === ownId || ownId === 0) return;
|
if (message.SenderId === ownId || ownId === 0) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,20 @@
|
||||||
|
import { lazy } from "react";
|
||||||
import { settingsNavigation } from "./navigation";
|
import { settingsNavigation } from "./navigation";
|
||||||
|
|
||||||
import SettingsIndex from "./pages/index";
|
|
||||||
import Profile from "./pages/profile";
|
|
||||||
import Security from "./pages/security";
|
|
||||||
import Chat from "./pages/chat";
|
|
||||||
import Call from "./pages/call";
|
|
||||||
import Cache from "./pages/cache";
|
|
||||||
import Theme from "./pages/theme";
|
|
||||||
import Accessibility from "./pages/accessibility";
|
|
||||||
import Hotkeys from "./pages/hotkeys";
|
|
||||||
import Licenses from "./pages/licenses";
|
|
||||||
|
|
||||||
const pageComponents = {
|
const pageComponents = {
|
||||||
profile: Profile,
|
profile: lazy(() => import("./pages/profile")),
|
||||||
security: Security,
|
security: lazy(() => import("./pages/security")),
|
||||||
chat: Chat,
|
chat: lazy(() => import("./pages/chat")),
|
||||||
call: Call,
|
call: lazy(() => import("./pages/call")),
|
||||||
cache: Cache,
|
cache: lazy(() => import("./pages/cache")),
|
||||||
theme: Theme,
|
theme: lazy(() => import("./pages/theme")),
|
||||||
accessibility: Accessibility,
|
accessibility: lazy(() => import("./pages/accessibility")),
|
||||||
hotkeys: Hotkeys,
|
hotkeys: lazy(() => import("./pages/hotkeys")),
|
||||||
licenses: Licenses,
|
licenses: lazy(() => import("./pages/licenses")),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const settingsPages = [
|
export const settingsPages = [
|
||||||
{ path: "/", component: SettingsIndex },
|
{ path: "/", component: lazy(() => import("./pages/index")) },
|
||||||
...settingsNavigation.map((page) => ({
|
...settingsNavigation.map((page) => ({
|
||||||
...page,
|
...page,
|
||||||
component: pageComponents[page.path],
|
component: pageComponents[page.path],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue