Regression: React + Blaze reconciliation (#21567)
parent
d6ff6bddb8
commit
b226da3849
@ -0,0 +1,173 @@ |
||||
import { Emitter } from '@rocket.chat/emitter'; |
||||
import { useEffect, useMemo } from 'react'; |
||||
import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; |
||||
|
||||
import { getConfig } from '../../app/ui-utils/client/config'; |
||||
import { IRoom } from '../../definition/IRoom'; |
||||
import { useUserId, useUserRoom, useUserSubscription } from '../contexts/UserContext'; |
||||
import { useAsyncState } from '../hooks/useAsyncState'; |
||||
import { AsyncState } from './asyncState'; |
||||
|
||||
const debug = !!(getConfig('debug') || getConfig('debug-RoomStore')); |
||||
|
||||
export class RoomStore extends Emitter<{ |
||||
changed: undefined; |
||||
}> { |
||||
lastTime?: Date; |
||||
|
||||
scroll?: number; |
||||
|
||||
constructor(readonly rid: string) { |
||||
super(); |
||||
|
||||
debug && this.on('changed', () => console.log(`RoomStore ${this.rid} changed`, this)); |
||||
} |
||||
|
||||
update({ scroll, lastTime }: { scroll?: number; lastTime?: Date }): void { |
||||
if (scroll !== undefined) { |
||||
this.scroll = scroll; |
||||
} |
||||
if (lastTime !== undefined) { |
||||
this.lastTime = lastTime; |
||||
} |
||||
if (scroll || lastTime) { |
||||
this.emit('changed'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
const debugRoomManager = !!(getConfig('debug') || getConfig('debug-RoomManager')); |
||||
export const RoomManager = new (class RoomManager extends Emitter<{ |
||||
changed: IRoom['_id'] | undefined; |
||||
opened: IRoom['_id']; |
||||
closed: IRoom['_id']; |
||||
back: IRoom['_id']; |
||||
removed: IRoom['_id']; |
||||
}> { |
||||
private rid: IRoom['_id'] | undefined; |
||||
|
||||
private lastRid: IRoom['_id'] | undefined; |
||||
|
||||
private rooms: Map<IRoom['_id'], RoomStore> = new Map(); |
||||
|
||||
constructor() { |
||||
super(); |
||||
debugRoomManager && |
||||
this.on('opened', (rid) => { |
||||
console.log('room opened ->', rid); |
||||
}); |
||||
|
||||
debugRoomManager && |
||||
this.on('back', (rid) => { |
||||
console.log('room moved to back ->', rid); |
||||
}); |
||||
|
||||
debugRoomManager && |
||||
this.on('closed', (rid) => { |
||||
console.log('room close ->', rid); |
||||
}); |
||||
} |
||||
|
||||
get lastOpened(): IRoom['_id'] | undefined { |
||||
return this.lastRid; |
||||
} |
||||
|
||||
get opened(): IRoom['_id'] | undefined { |
||||
return this.rid; |
||||
} |
||||
|
||||
visitedRooms(): IRoom['_id'][] { |
||||
return [...this.rooms.keys()]; |
||||
} |
||||
|
||||
back(rid: IRoom['_id']): void { |
||||
if (rid === this.rid) { |
||||
this.lastRid = rid; |
||||
this.rid = undefined; |
||||
this.emit('back', rid); |
||||
} |
||||
} |
||||
|
||||
close(rid: IRoom['_id']): void { |
||||
if (!this.rooms.has(rid)) { |
||||
this.rooms.delete(rid); |
||||
this.emit('closed', rid); |
||||
} |
||||
this.emit('changed', this.rid); |
||||
} |
||||
|
||||
open(rid: IRoom['_id']): void { |
||||
if (rid === this.rid) { |
||||
return; |
||||
} |
||||
|
||||
this.back(rid); |
||||
if (!this.rooms.has(rid)) { |
||||
this.rooms.set(rid, new RoomStore(rid)); |
||||
} |
||||
this.rid = rid; |
||||
this.emit('opened', rid); |
||||
this.emit('changed', this.rid); |
||||
} |
||||
|
||||
getStore(rid: IRoom['_id']): RoomStore | undefined { |
||||
return this.rooms.get(rid); |
||||
} |
||||
})(); |
||||
|
||||
const subscribeVistedRooms: Subscription<IRoom['_id'][]> = { |
||||
getCurrentValue: () => RoomManager.visitedRooms(), |
||||
subscribe(callback) { |
||||
return RoomManager.on('changed', callback); |
||||
}, |
||||
}; |
||||
|
||||
const subscribeOpenedRoom: Subscription<IRoom['_id'] | undefined> = { |
||||
getCurrentValue: () => RoomManager.opened, |
||||
subscribe(callback) { |
||||
return RoomManager.on('opened', callback); |
||||
}, |
||||
}; |
||||
|
||||
const fields = {}; |
||||
|
||||
export const useHandleRoom = (rid: IRoom['_id']): AsyncState<IRoom> => { |
||||
const { resolve, update, ...state } = useAsyncState<IRoom>(); |
||||
const uid = useUserId(); |
||||
const subscription = (useUserSubscription(rid, fields) as unknown) as IRoom; |
||||
const _room = (useUserRoom(rid, fields) as unknown) as IRoom; |
||||
|
||||
const room = uid ? subscription || _room : _room; |
||||
|
||||
useEffect(() => { |
||||
if (room) { |
||||
update(); |
||||
resolve(room); |
||||
} |
||||
}, [resolve, update, room]); |
||||
|
||||
return state; |
||||
}; |
||||
|
||||
export const useVisitedRooms = (): IRoom['_id'][] => useSubscription(subscribeVistedRooms); |
||||
|
||||
export const useOpenedRoom = (): IRoom['_id'] | undefined => useSubscription(subscribeOpenedRoom); |
||||
|
||||
export const useRoomStore = (rid: IRoom['_id']): RoomStore => { |
||||
const subscribeStore: Subscription<RoomStore | undefined> = useMemo( |
||||
() => ({ |
||||
getCurrentValue: (): RoomStore | undefined => RoomManager.getStore(rid), |
||||
subscribe(callback): Unsubscribe { |
||||
return RoomManager.on('changed', callback); |
||||
}, |
||||
}), |
||||
[rid], |
||||
); |
||||
|
||||
const store = useSubscription(subscribeStore); |
||||
|
||||
if (!store) { |
||||
throw new Error('Something wrong'); |
||||
} |
||||
return store; |
||||
}; |
||||
@ -1,12 +1,16 @@ |
||||
import React from 'react'; |
||||
|
||||
import { useOpenedRoom } from '../../../lib/RoomManager'; |
||||
import RoomProvider from '../providers/RoomProvider'; |
||||
import Room from './Room'; |
||||
|
||||
const RoomWithData = ({ _id }) => ( |
||||
<RoomProvider rid={_id}> |
||||
<Room /> |
||||
</RoomProvider> |
||||
); |
||||
const RoomWithData = () => { |
||||
const rid = useOpenedRoom(); |
||||
return rid ? ( |
||||
<RoomProvider rid={rid}> |
||||
<Room /> |
||||
</RoomProvider> |
||||
) : null; |
||||
}; |
||||
|
||||
export default RoomWithData; |
||||
|
||||
@ -0,0 +1,55 @@ |
||||
import { Skeleton, Box, InputBox } from '@rocket.chat/fuselage'; |
||||
import React, { FC, memo } from 'react'; |
||||
|
||||
import Header from '../../../components/Header'; |
||||
import VerticalBarSkeleton from '../../../components/VerticalBar/VerticalBarSkeleton'; |
||||
import { RoomTemplate } from '../components/RoomTemplate/RoomTemplate'; |
||||
|
||||
const RoomSkeleton: FC = () => ( |
||||
<RoomTemplate> |
||||
<RoomTemplate.Header> |
||||
<Header> |
||||
<Header.Avatar> |
||||
<Skeleton variant='rect' width={36} height={36} /> |
||||
</Header.Avatar> |
||||
<Header.Content> |
||||
<Header.Content.Row> |
||||
<Skeleton width='10%' /> |
||||
</Header.Content.Row> |
||||
<Header.Content.Row> |
||||
<Skeleton width='30%' /> |
||||
</Header.Content.Row> |
||||
</Header.Content> |
||||
</Header> |
||||
</RoomTemplate.Header> |
||||
<RoomTemplate.Body> |
||||
<Box display='flex' height='100%' justifyContent='flex-start' flexDirection='column'> |
||||
<Box pi='x24' pb='x16' display='flex'> |
||||
<Box> |
||||
<Skeleton variant='rect' width={36} height={36} /> |
||||
</Box> |
||||
<Box mis='x8' flexGrow={1}> |
||||
<Skeleton width='100%' /> |
||||
<Skeleton width='69%' /> |
||||
</Box> |
||||
</Box> |
||||
<Box pi='x24' pb='x16' display='flex'> |
||||
<Box> |
||||
<Skeleton variant='rect' width={36} height={36} /> |
||||
</Box> |
||||
<Box mis='x8' flexGrow={1}> |
||||
<Skeleton width='100%' /> |
||||
<Skeleton width='40%' /> |
||||
</Box> |
||||
</Box> |
||||
</Box> |
||||
<Box pi='x24' pb='x16' display='flex'> |
||||
<InputBox.Skeleton /> |
||||
</Box> |
||||
</RoomTemplate.Body> |
||||
<RoomTemplate.Aside> |
||||
<VerticalBarSkeleton /> |
||||
</RoomTemplate.Aside> |
||||
</RoomTemplate> |
||||
); |
||||
export default memo(RoomSkeleton); |
||||
@ -1,43 +0,0 @@ |
||||
import { Box } from '@rocket.chat/fuselage'; |
||||
import React, { memo } from 'react'; |
||||
import flattenChildren from 'react-keyed-flatten-children'; |
||||
|
||||
import VerticalBar from '../../../components/VerticalBar'; |
||||
|
||||
export const RoomTemplate = ({ children, ...props }) => { |
||||
const c = flattenChildren(children); |
||||
const header = c.filter((child) => child.type === RoomTemplate.Header); |
||||
const body = c.filter((child) => child.type === RoomTemplate.Body); |
||||
const footer = c.filter((child) => child.type === RoomTemplate.Footer); |
||||
const aside = c.filter((child) => child.type === RoomTemplate.Aside); |
||||
|
||||
return ( |
||||
<Box is='main' h='full' display='flex' flexDirection='column' {...props}> |
||||
{header.length > 0 && header} |
||||
<Box display='flex' flexGrow='1' overflow='hidden' height='full' position='relative'> |
||||
<Box display='flex' flexDirection='column' flexGrow='1'> |
||||
<Box is='div' display='flex' flexDirection='column' flexGrow='1'> |
||||
{body} |
||||
</Box> |
||||
{footer.length > 0 && <Box is='footer'>{footer}</Box>} |
||||
</Box> |
||||
{aside.length > 0 && <VerticalBar is='aside'>{aside}</VerticalBar>} |
||||
</Box> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
RoomTemplate.Header = function Header({ children }) { |
||||
return children; |
||||
}; |
||||
RoomTemplate.Body = function Body({ children }) { |
||||
return children; |
||||
}; |
||||
RoomTemplate.Footer = function Footer({ children }) { |
||||
return children; |
||||
}; |
||||
RoomTemplate.Aside = function Aside({ children }) { |
||||
return children; |
||||
}; |
||||
|
||||
export default memo(RoomTemplate); |
||||
@ -0,0 +1,44 @@ |
||||
import { Box } from '@rocket.chat/fuselage'; |
||||
import React, { FC } from 'react'; |
||||
import flattenChildren from 'react-keyed-flatten-children'; |
||||
|
||||
import VerticalBar from '../../../../components/VerticalBar/VerticalBar'; |
||||
import { Aside } from './slots/Aside'; |
||||
import { Body } from './slots/Body'; |
||||
import { Footer } from './slots/Footer'; |
||||
import { Header } from './slots/Header'; |
||||
|
||||
export const RoomTemplate: FC & { |
||||
Header: FC; |
||||
Body: FC; |
||||
Footer: FC; |
||||
Aside: FC; |
||||
} = ({ children, ...props }) => { |
||||
const c = flattenChildren(children); |
||||
const header = c.filter((child) => (child as any).type === RoomTemplate.Header); |
||||
const body = c.filter((child) => (child as any).type === RoomTemplate.Body); |
||||
const footer = c.filter((child) => (child as any).type === RoomTemplate.Footer); |
||||
const aside = c.filter((child) => (child as any).type === RoomTemplate.Aside); |
||||
|
||||
return ( |
||||
<Box is='main' h='full' display='flex' flexDirection='column' {...props}> |
||||
{header.length > 0 && header} |
||||
<Box display='flex' flexGrow={1} overflow='hidden' height='full' position='relative'> |
||||
<Box display='flex' flexDirection='column' flexGrow={1}> |
||||
<Box is='div' display='flex' flexDirection='column' flexGrow={1}> |
||||
{body} |
||||
</Box> |
||||
{footer.length > 0 && <Box is='footer'>{footer}</Box>} |
||||
</Box> |
||||
{aside.length > 0 && <VerticalBar is='aside'>{aside}</VerticalBar>} |
||||
</Box> |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
RoomTemplate.Header = Header; |
||||
RoomTemplate.Body = Body; |
||||
RoomTemplate.Footer = Footer; |
||||
RoomTemplate.Aside = Aside; |
||||
|
||||
export default RoomTemplate; |
||||
@ -0,0 +1,5 @@ |
||||
import React, { FC } from 'react'; |
||||
|
||||
export const Aside: FC = function Aside({ children }) { |
||||
return <>{children}</>; |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
import React, { FC } from 'react'; |
||||
|
||||
export const Body: FC = function Body({ children }) { |
||||
return <>{children}</>; |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
import React, { FC } from 'react'; |
||||
|
||||
export const Footer: FC = function Footer({ children }) { |
||||
return <>{children}</>; |
||||
}; |
||||
@ -0,0 +1,5 @@ |
||||
import React, { FC } from 'react'; |
||||
|
||||
export const Header: FC = function Header({ children }) { |
||||
return <>{children}</>; |
||||
}; |
||||
Loading…
Reference in new issue