(null);
+ const { content, activeMember, locked, lockedByYou, editIsNotAllowed, handleSelect, handleContentUpdate } =
+ useTextComponentLock({
+ id,
+ slide,
+ defaultText: children,
+ containerRef,
+ });
const memberName = getMemberFirstName(activeMember);
- const lockId = buildLockId(slide, id);
- const channelName = `[?rewind=1]${spaceName}${lockId}`;
- const [content, setContent] = useSlideElementContent(lockId, children);
- const miniature = useMiniature();
-
- const { channel } = useChannel(channelName, (message) => {
- if (message.connectionId === self?.connectionId || miniature) return;
- setContent(message.data);
- });
-
- const optimisticallyLocked = !!activeMember;
- const optimisticallyLockedByYou = optimisticallyLocked && activeMember?.connectionId === self?.connectionId;
- const editIsNotAllowed = !optimisticallyLockedByYou && optimisticallyLocked;
- const lockConflict = optimisticallyLockedByYou && locked && !lockedByYou && !miniature;
-
- useClickOutside(ref, self, optimisticallyLockedByYou && !miniature);
- useClearOnFailedLock(lockConflict, self);
+ const { outlineClasses, stickyLabelClasses } = getOutlineClasses(activeMember);
return (
- {optimisticallyLockedByYou ? 'You' : memberName}
+ {lockedByYou ? 'You' : memberName}
{editIsNotAllowed && }
{
- setContent(nextValue);
- channel.publish('update', nextValue);
- }}
+ onChange={handleContentUpdate}
className={cn(
'relative break-all',
{
@@ -77,8 +55,8 @@ export const Title = ({ variant = 'h1', className, id, slide, children, maxlengt
'font-semibold text-ably-avatar-stack-demo-slide-text md:text-2xl': variant === 'h2',
'font-medium uppercase text-ably-avatar-stack-demo-slide-title-highlight xs:text-xs xs:my-4 md:my-0 md:text-md':
variant === 'h3',
- [`outline-2 outline ${outlineClasses}`]: optimisticallyLocked,
- 'cursor-pointer': !optimisticallyLocked,
+ [`outline-2 outline ${outlineClasses}`]: locked,
+ 'cursor-pointer': !locked,
'cursor-not-allowed': editIsNotAllowed,
'bg-slate-200': editIsNotAllowed,
},
diff --git a/demo/src/hooks/useSlideElementContent.ts b/demo/src/hooks/useSlideElementContent.ts
index 725c515b..5ef97f84 100644
--- a/demo/src/hooks/useSlideElementContent.ts
+++ b/demo/src/hooks/useSlideElementContent.ts
@@ -3,11 +3,11 @@ import { SlidesStateContext } from '../components/SlidesStateContext.tsx';
export const useSlideElementContent = (id: string, defaultContent: string) => {
const { slidesState, setContent } = useContext(SlidesStateContext);
- const setNextContent = useCallback(
+ const updateContent = useCallback(
(nextContent: string) => {
setContent(id, nextContent);
},
[id],
);
- return [slidesState[id] ?? defaultContent, setNextContent] as const;
+ return [slidesState[id] ?? defaultContent, updateContent] as const;
};
diff --git a/demo/src/hooks/useTextComponentLock.ts b/demo/src/hooks/useTextComponentLock.ts
new file mode 100644
index 00000000..e67c5490
--- /dev/null
+++ b/demo/src/hooks/useTextComponentLock.ts
@@ -0,0 +1,55 @@
+import { MutableRefObject, useCallback } from 'react';
+import { useChannel } from '@ably-labs/react-hooks';
+import { findActiveMember, getSpaceNameFromUrl } from '../utils';
+import { buildLockId } from '../utils/locking.ts';
+import { usePreview } from '../components/PreviewContext.tsx';
+import { useMembers } from './useMembers.ts';
+import { useClearOnFailedLock, useClickOutside, useElementSelect } from './useElementSelect.ts';
+import { useLockStatus } from './useLock.ts';
+import { useSlideElementContent } from './useSlideElementContent.ts';
+
+interface UseTextComponentLockArgs {
+ id: string;
+ slide: string;
+ defaultText: string;
+ containerRef: MutableRefObject;
+}
+export const useTextComponentLock = ({ id, slide, defaultText, containerRef }: UseTextComponentLockArgs) => {
+ const spaceName = getSpaceNameFromUrl();
+ const { members, self } = useMembers();
+ const activeMember = findActiveMember(id, slide, members);
+ const { locked, lockedByYou } = useLockStatus(slide, id, self?.connectionId);
+ const lockId = buildLockId(slide, id);
+ const channelName = `[?rewind=1]${spaceName}${lockId}`;
+ const [content, updateContent] = useSlideElementContent(lockId, defaultText);
+ const preview = usePreview();
+
+ const { handleSelect } = useElementSelect(id);
+ const handleContentUpdate = useCallback((content: string) => {
+ updateContent(content);
+ channel.publish('update', content);
+ }, []);
+
+ const { channel } = useChannel(channelName, (message) => {
+ if (message.connectionId === self?.connectionId) return;
+ updateContent(message.data);
+ });
+
+ const optimisticallyLocked = !!activeMember;
+ const optimisticallyLockedByYou = optimisticallyLocked && activeMember?.connectionId === self?.connectionId;
+ const editIsNotAllowed = !optimisticallyLockedByYou && optimisticallyLocked;
+ const lockConflict = optimisticallyLockedByYou && locked && !lockedByYou && !preview;
+
+ useClickOutside(containerRef, self, optimisticallyLockedByYou && !preview);
+ useClearOnFailedLock(lockConflict, self);
+
+ return {
+ content,
+ activeMember,
+ locked: optimisticallyLocked,
+ lockedByYou: optimisticallyLockedByYou,
+ editIsNotAllowed,
+ handleSelect,
+ handleContentUpdate,
+ };
+};