Skip to content

Commit

Permalink
@steedos-widgets/liveblocks
Browse files Browse the repository at this point in the history
  • Loading branch information
hotlong committed Dec 23, 2024
1 parent 856170d commit e3e8c42
Show file tree
Hide file tree
Showing 12 changed files with 530 additions and 164 deletions.
8 changes: 4 additions & 4 deletions packages/@steedos-widgets/liveblocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@
"rollup-plugin-visualizer": "^5.8.0"
},
"dependencies": {
"@liveblocks/client": "^2.11.0",
"@liveblocks/node": "^2.11.0",
"@liveblocks/react": "^2.11.0",
"@liveblocks/react-ui": "^2.11.0",
"@liveblocks/client": "2.12.0",
"@liveblocks/node": "2.12.0",
"@liveblocks/react": "2.12.0",
"@liveblocks/react-ui": "2.12.0",
"@rollup/plugin-replace": "^5.0.2",
"@steedos-widgets/amis-lib": "6.3.11",
"react-error-boundary": "^4.1.2"
Expand Down
103 changes: 1 addition & 102 deletions packages/@steedos-widgets/liveblocks/src/components/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ import { RoomProvider, useThreads } from "@liveblocks/react/suspense";
import { ErrorBoundary } from "react-error-boundary";
import "@liveblocks/react-ui/styles.css";
// import "@liveblocks/react-ui/styles/dark/media-query.css";
import { Loading } from "./Loading";

import './Comments.css';

export const Loading = () => {
return <div>Loading...</div>;
}

export const Threads = (props:any) => {
const { threads } = useThreads();

Expand All @@ -28,109 +25,12 @@ export const Threads = (props:any) => {

export const AmisComments = (props: any) => {
const {
config: configJSON = {},
data: amisData,
className = "m-2 flex flex-col gap-y-2",
roomId,
baseUrl,
dataFilter
} = props;

const [config, setConfig] = useState(configJSON);
const [token, setToken] = useState(null);

const fixedBaseUrl = baseUrl || amisData.context?.rootUrl || `${window.location.protocol}//${window.location.host}`
console.log('liveblocks baseUrl:', fixedBaseUrl);

let onDataFilter = null;

if (typeof dataFilter === 'string') {
onDataFilter = new Function('config', 'data', 'return (async () => { ' + dataFilter + ' })()')
}

useEffect(() => {
let isCancelled = false;
(async () => {
try {
if (onDataFilter) {
const newConfig = await onDataFilter(config, amisData);
if (!isCancelled) {
setConfig(newConfig || config);
}
}
} catch (e) {
console.warn(e);
}
})();

return () => {
isCancelled = true;
};
}, []);

return (
<LiveblocksProvider
//@ts-ignore
baseUrl={fixedBaseUrl}
authEndpoint={async (room) => {
const authEndpoint = `${fixedBaseUrl}/v2/c/auth`;
const headers = {
"Content-Type": "application/json",
};

const body = JSON.stringify({
room,
});

const response = await fetch(authEndpoint, {
method: "POST",
headers,
body,
credentials: 'include'
});
const result = await response.json();
setToken(result.token);

return result;
}}
// Get users' info from their ID
resolveUsers={async ({ userIds }) => {
const searchParams = new URLSearchParams(
userIds.map((userId) => ["userIds", userId])
);
const response = await fetch(`${fixedBaseUrl}/v2/c/users?${searchParams}`, {
headers: {
"Authorization": `Bearer ${token}`
}
});

if (!response.ok) {
throw new Error("Problem resolving users");
}

const users = await response.json();
return users;
}}
// publicApiKey={import.meta.env.VITE_LIVEBLOCKS_PUBLIC_KEY}

// Find a list of users that match the current search term
resolveMentionSuggestions={async ({ text = "" }) => {
const response = await fetch(
`${fixedBaseUrl}/v2/c/users/search?keyword=${encodeURIComponent(text)}`, {
headers: {
"Authorization": `Bearer ${token}`
}
}
);

if (!response.ok) {
throw new Error("Problem resolving mention suggestions");
}

const userIds = await response.json();
return userIds;
}}
>
<RoomProvider id={roomId}>
<ErrorBoundary
fallback={
Expand All @@ -144,6 +44,5 @@ export const AmisComments = (props: any) => {
</Suspense>
</ErrorBoundary>
</RoomProvider>
</LiveblocksProvider>
);
};
107 changes: 107 additions & 0 deletions packages/@steedos-widgets/liveblocks/src/components/InboxPopover.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@

.inbox {
background: #fff;
width: 460px;
height: 560px;
max-height: calc(100vh - var(--header-height) - 10px);
display: flex;
flex-direction: column;
outline: none;
overflow-y: auto;
border-radius: 0.75rem;
box-shadow:
0 0 0 1px rgb(0 0 0 / 4%),
0 2px 6px rgb(0 0 0 / 8%),
0 8px 26px rgb(0 0 0 / 12%);
}

.inbox-header {
position: sticky;
top: 0;
z-index: 1;
display: flex;
place-items: center;
justify-content: space-between;
background: #fff;
width: 100%;
padding: 0.5rem 0.5rem 0.5rem 1rem;
box-shadow:
0 0 0 1px rgb(0 0 0 / 4%),
0 2px 6px rgb(0 0 0 / 4%),
0 8px 26px rgb(0 0 0 / 6%);
}

.inbox-title {
font-weight: 500;
}

.inbox-buttons {
display: flex;
gap: 6px;
}

.inbox-list {
box-shadow: 0 0 0 1px rgb(0 0 0 / 8%);
}

.inbox-unread-count {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%);
display: flex;
place-content: center;
place-items: center;
background: var(--accent);
color: #fff;
font-size: 0.65rem;
font-weight: 500;
height: 1rem;
min-width: 1rem;
padding: 0 0.25rem;
border-radius: 9999px;
}


.inbox-content {
display: flex;
min-height: 100vh;
padding: calc(var(--header-height) + 1rem) 1rem 1rem
calc(var(--sidebar-width) + 1rem);
overflow-y: auto;
}

.inbox-button {
all: unset;
position: relative;
display: flex;
place-items: center;
place-content: center;
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
border-radius: 0.5rem;
background: #f3f3f3;
color: #555;
}

.inbox-button.square {
padding: 0;
width: 2rem;
height: 2rem;
}

.inbox-button.destructive {
background: #fde5e5;
color: #953939;
}

.inbox-button:hover,
.inbox-button:focus-visible {
background: #e8e8e8;
cursor: pointer;
}

.inbox-button.destructive:hover,
.inbox-button.destructive:focus-visible{
background: #ffd6d6;
}
131 changes: 131 additions & 0 deletions packages/@steedos-widgets/liveblocks/src/components/InboxPopover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"use client";
import React from 'react';
import { InboxNotification, InboxNotificationList } from "@liveblocks/react-ui";
import * as Popover from "@radix-ui/react-popover";
import {
useDeleteAllInboxNotifications,
useInboxNotifications,
useMarkAllInboxNotificationsAsRead,
useUnreadInboxNotificationsCount,
} from "@liveblocks/react/suspense";
import { ClientSideSuspense } from "@liveblocks/react";
import { Loading } from "./Loading";
import { ComponentPropsWithoutRef, useEffect, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import clsx from "clsx";
// import { Link } from "./Link";
// import { usePathname } from "next/navigation";
import './InboxPopover.css';


function Inbox({ className, ...props }: ComponentPropsWithoutRef<"div">) {
const { inboxNotifications } = useInboxNotifications();

return inboxNotifications.length === 0 ? (
<div className={clsx(className, "empty")}>
There aren’t any notifications yet.
</div>
) : (
<div className={className} {...props}>
<InboxNotificationList className="inbox-list">
{inboxNotifications.map((inboxNotification) => {
return (
<InboxNotification
key={inboxNotification.id}
inboxNotification={inboxNotification}
// components={{ Anchor: Link }}
/>
);
})}
</InboxNotificationList>
</div>
);
}

function InboxPopoverUnreadCount() {
const { count } = useUnreadInboxNotificationsCount();

return count ? <div className="inbox-unread-count">{count}</div> : null;
}

export function AmisInboxPopover({
className,
...props
}: Popover.PopoverContentProps) {
const [isOpen, setOpen] = useState(false);
const markAllInboxNotificationsAsRead = useMarkAllInboxNotificationsAsRead();
const deleteAllInboxNotifications = useDeleteAllInboxNotifications();
// const pathname = usePathname();

// useEffect(() => {
// setOpen(false);
// }, [pathname]);

return (
<Popover.Root open={isOpen} onOpenChange={setOpen}>
<Popover.Trigger className={clsx(className, "inbox-button square")}>
<ErrorBoundary fallback={null}>
<ClientSideSuspense fallback={null}>
<InboxPopoverUnreadCount />
</ClientSideSuspense>
</ErrorBoundary>
<svg
width="20"
height="20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m3.6 9.8 1.9-4.6A2 2 0 0 1 7.3 4h5.4a2 2 0 0 1 1.8 1.2l2 4.6V13a2 2 0 0 1-2 2h-9a2 2 0 0 1-2-2V9.8Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
<path
d="M3.5 10h3c.3 0 .6.1.8.4l.9 1.2c.2.3.5.4.8.4h2c.3 0 .6-.1.8-.4l.9-1.2c.2-.3.5-.4.8-.4h3"
stroke="currentColor"
strokeWidth="1.5"
strokeLinejoin="round"
/>
</svg>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="inbox"
collisionPadding={16}
sideOffset={8}
{...props}
>
<div className="inbox-header">
<span className="inbox-title">Notifications</span>
<div className="inbox-buttons">
<button
className="inbox-button"
onClick={markAllInboxNotificationsAsRead}
>
Mark all as read
</button>
<button
className="inbox-button destructive"
onClick={deleteAllInboxNotifications}
>
Delete all
</button>
</div>
</div>
<ErrorBoundary
fallback={
<div className="error">
There was an error while getting notifications.
</div>
}
>
<ClientSideSuspense fallback={<Loading />}>
<Inbox />
</ClientSideSuspense>
</ErrorBoundary>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';


export function Loading() {
return (
<svg className="animate-spin h-5 w-5 mr-3 ..." viewBox="0 0 24 24">
</svg>
);
}
Loading

0 comments on commit e3e8c42

Please sign in to comment.