mobx-react-lite#useObserver TypeScript Examples
The following examples show how to use
mobx-react-lite#useObserver.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: StereoAudioSwitch.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
SoundLocalizationSetting: React.FC<{}> = () => {
const soundLocalizationBase = useObserver(() => participants.local.soundLocalizationBase)
const {t} = useTranslation()
return <Container>
<Grid component="label" container={true} alignItems="center" spacing={1}>
<Grid item={true}>{t('slUser')}</Grid>
<Grid item={true}>
<Switch checked={soundLocalizationBase === 'avatar'} onChange={() => {
participants.local.soundLocalizationBase = soundLocalizationBase === 'avatar' ? 'user' : 'avatar'
participants.local.saveMediaSettingsToStorage()
}} name="soundLoc" />
</Grid>
<Grid item={true}>{t('slAvatar')}</Grid>
</Grid>
</Container>
}
Example #2
Source File: index.tsx From sphinx-win-linux-desktop with MIT License | 6 votes |
export default function Modals() {
const { ui } = useStores();
return useObserver(() => {
let display = "none";
if (ui.imgViewerParams || ui.confirmInvoiceMsg || ui.sendRequestModal)
display = "flex";
return (
<Wrap style={{ display }}>
{ui.imgViewerParams && <ViewImg params={ui.imgViewerParams} />}
{ui.confirmInvoiceMsg && (
<ConfirmInvoice params={ui.confirmInvoiceMsg} />
)}
{ui.sendRequestModal && <SendRequest />}
{ui.startJitsiParams && <StartJitsi />}
{ui.viewContact && <ViewContact />}
{ui.showProfile && <Profile />}
{ui.newContact && <NewContact />}
{ui.shareInviteModal && <ShareInvite />}
{ui.viewTribe && <ViewTribe />}
{ui.onchain && <Onchain />}
{ui.showVersionDialog && <VersionModal />}
{ui.tribeInfo && <TribeInfo />}
{ui.tribesAuthParams && (
<TribesAuthModal params={ui.tribesAuthParams} />
)}
{ui.tribesSaveParams && (
<TribesSaveModal params={ui.tribesSaveParams} />
)}
{ui.personParams && <PersonModal params={ui.personParams} />}
{ui.newGroupModal && <NewTribeModal />}
</Wrap>
);
});
}
Example #3
Source File: chatList.tsx From sphinx-win-linux-desktop with MIT License | 6 votes |
function ChatList(){
const {msg,ui,contacts,chats, user} = useStores()
const maxWidth = 350
const [width,setWidth] = useState(maxWidth)
return useObserver(()=>{
const theChats = useChats()
const scid = ui.selectedChat&&ui.selectedChat.id
const scname = ui.selectedChat&&ui.selectedChat.name
return <Section style={{width,maxWidth:width,minWidth:width}}>
<Inner>
<Head setWidth={setWidth} width={width}/>
<Chats>
{theChats.map((c,i)=> {
const contact = contactForConversation(c, contacts.contacts, user.myid)
let showInvite = false
if (c.invite && c.invite.status !== 4) showInvite = true
if (showInvite){
return <InviteRow key={i} {...c}/>
}
return <ChatRow
key={i} {...c} contact_photo={contact&&contact.photo_url}
selected={c.id===scid&&c.name===scname}
onClick={async ()=> {
msg.seeChat(c.id)
ui.setSelectedChat(c)
ui.toggleBots(false)
chats.checkRoute(c.id, user.myid)
ui.setImgViewerParams(null)
}}
/>
})}
</Chats>
</Inner>
<Dragger setWidth={setWidth} maxWidth={maxWidth} />
</Section>
})
}
Example #4
Source File: App.tsx From sphinx-win-linux-desktop with MIT License | 6 votes |
function Wrap(){
const {ui} = useStores()
return useObserver(()=>{
if(ui.ready) return <ThemeProvider theme={ createMuiTheme({palette}) }><App /></ThemeProvider>
return <Loading>
<CircularProgress style={{color:'white'}} />
</Loading>
})
}
Example #5
Source File: ChannelRow.stories.tsx From lightning-terminal with MIT License | 6 votes |
renderStory = (
channel: Channel,
options?: {
ratio?: number;
active?: boolean;
},
) => {
if (options && options.ratio) {
channel.localBalance = channel.capacity.mul(options.ratio);
channel.remoteBalance = channel.capacity.mul(1 - options.ratio);
}
return useObserver(() => (
<div style={{ paddingTop: 50 }}>
<ChannelRowHeader />
<ChannelRow channel={channel} />
</div>
));
}
Example #6
Source File: PDF.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
PDF: React.FC<ContentProps> = (props:ContentProps) => {
assert(props.content.type === 'pdf')
const memberRef = useRef<Member>(new Member())
const member = memberRef.current
member.newProps = props
const refCanvas = useRef<HTMLCanvasElement>(null)
const refTextDiv = useRef<HTMLDivElement>(null)
const refAnnotationDiv = useRef<HTMLDivElement>(null)
const editing = useObserver(() => props.stores.contents.editing === props.content.id)
useEffect(()=>{
member.canvas = refCanvas.current
member.textDiv = refTextDiv.current
member.annotationDiv = refAnnotationDiv.current
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [refCanvas.current])
useEffect(()=>{
member.updateProps()
})
return <div style={{overflow: 'hidden', pointerEvents: 'auto', userSelect: editing? 'text':'none'}}
onDoubleClick = {(ev) => { if (!editing) {
ev.stopPropagation()
ev.preventDefault()
props.stores.contents.setEditing(props.content.id)
} }} >
<canvas style={{ width:`${CANVAS_SCALE*100}%`, height:`${CANVAS_SCALE*100}%`,
transformOrigin:'top left', transform:`scale(${1/CANVAS_SCALE})`}} ref={refCanvas} />
<div ref={refTextDiv} style={{position:'absolute', left:0, top:0,
width:`${CANVAS_SCALE*100}%`, height:`${CANVAS_SCALE*100}%`,
transformOrigin:'top left', transform:`scale(${1/CANVAS_SCALE})`, lineHeight: 1,
overflow:'hidden'}} />
<div ref={refAnnotationDiv} />
<div style={{position:'absolute', top:0, left:0, width:'100%', height:40}}
onPointerEnter={()=>{member.showTop = true}} onPointerLeave={()=>{member.showTop = false}}>
<Observer>{()=>
<Collapse in={member.showTop} style={{position:'absolute', top:0, left:0, width:'100%'}}>
<Grid container alignItems="center">
<Grid item >
<IconButton size="small" color={member.pageNum>0?'primary':'default'} {...stopper}
onClick={(ev) => { ev.stopPropagation(); member.updateUrl(member.pageNum - 1) }}
onDoubleClick={(ev) => {ev.stopPropagation() }} >
<NavigateBeforeIcon />
</IconButton>
</Grid>
<Grid item xs={1}>
<TextField value={member.pageText} {...stopper}
inputProps={{min: 0, style: { textAlign: 'center' }}}
onChange={(ev)=> { member.pageText = ev.target.value}}
onBlur={(ev) => {
const num = Number(member.pageText)
if (num > 0) { member.updateUrl(num-1) }
}}
onKeyPress={(ev)=>{
if (ev.key === 'Enter'){
const num = Number(member.pageText)
if (num > 0) { member.updateUrl(num-1) }
}
}}
/>
</Grid>
<Grid item style={{fontSize:15}}>/ {member.numPages}</Grid>
<Grid item >
<IconButton size="small" color={member.pageNum<member.numPages-1?'primary':'default'} {...stopper}
onClick={(ev) => { ev.stopPropagation(); member.updateUrl(member.pageNum + 1) }}
onDoubleClick={(ev) => {ev.stopPropagation() }} >
<NavigateNextIcon />
</IconButton>
</Grid>
</Grid>
</Collapse>
}</Observer>
</div>
</div>
}
Example #7
Source File: Content.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
RawContent: React.FC<ContentProps> = (props:ContentProps) => {
const classes = useStyles()
const editing = useObserver(() => props.stores.contents.editing === props.content.id)
let rv
if (props.content.type === 'img') {
rv = <img className={classes.img} src={props.content.url} alt={props.content.name}/>
}else if (props.content.type === 'iframe' || props.content.type === 'whiteboard') {
rv = <div className={classes.div}>
<iframe className={editing ? classes.iframeEdit : classes.iframe}
style={props.content.type==='whiteboard'?{backgroundColor: props.content.noFrame?'rgba(0,0,0,0)':'white'}:{}}
src={props.content.url} key={props.content.name} title={props.content.name}/>
</div>
}else if (props.content.type === 'youtube') {
rv = <YouTube {...props} />
}else if (props.content.type === 'gdrive') {
rv = <GDrive {...props} />
}else if (props.content.type === 'pdf') {
rv = <PDF {...props} />
}else if (props.content.type === 'text') {
rv = <Text {...props} />
}else if (props.content.type === 'screen' || props.content.type === 'camera') {
rv = <ScreenContent {...props} />
}else if (props.content.type === 'playbackScreen' || props.content.type === 'playbackCamera') {
rv = <PlaybackScreenContent {...props} />
}else {
rv = <div>Unknown type:{props.content.type} for {props.content.url}</div>
}
return rv
}
Example #8
Source File: MouseCursor.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
MouseCursor: React.FC<MouseCursorProps> = (props:MouseCursorProps) => {
const participants = props.stores.participants
const participant = participants.find(props.participantId) as ParticipantBase
const position = useObserver(() => participant.mouse.position)
const name = useObserver(() => participant.information.name)
const [color] = participant.getColor()
if (!position) {
return <div />
}
const isLocal = props.participantId === participants.localId
const cursor = <div style={{width:18, height:30, left:position[0], top:position[1], position:'absolute',
pointerEvents: isLocal ? 'none' : 'auto',
}}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 100">
<polygon points="32,100 53,92 36,57 60,56 0,0 0,81 16,65 32,100" stroke="black" fill={color} strokeWidth="6" />
</svg>
</div>
return isLocal ? cursor
:<Tooltip title={name}>{cursor}</Tooltip>
}
Example #9
Source File: Background.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
Background: React.FC<MapProps> = (props) => {
const roomInfo = props.stores.roomInfo
const styleProps = {
color: [0,0,0],
fill: [0,0,0],
...props
}
useObserver(()=>{
styleProps.color = roomInfo.backgroundColor
styleProps.fill = roomInfo.backgroundFill
})
const classes = useStyles(styleProps)
return <Observer>{() => {
return <div className={classes.img}>
<img className={classes.logo} src={jitsiIcon} alt="jitsi icon"/>
</div>
}}</Observer>
}
Example #10
Source File: ParticipantList.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
RawParticipantList: React.FC<BMProps&TextLineStyle&{localId: string, remoteIds: string[]}> = (props) => {
const [showStat, setShowStat] = React.useState(false)
const participants = props.stores.participants
const roomInfo = props.stores.roomInfo
const classes = styleForList({height: props.lineHeight, fontSize: props.fontSize})
const {localId, remoteIds, lineHeight, fontSize, ...statusProps} = props
const lineProps = {lineHeight, fontSize, ...statusProps}
const textColor = useObserver(() => isDarkColor(roomInfo.backgroundFill) ? 'white' : 'black')
remoteIds.sort((a, b) => {
const pa = participants.remote.get(a)
const pb = participants.remote.get(b)
let rv = pa!.information.name.localeCompare(pb!.information.name, undefined, {sensitivity: 'accent'})
if (rv === 0) {
rv = (pa!.information.avatarSrc || '').localeCompare(pb!.information.avatarSrc || '', undefined, {sensitivity: 'accent'})
}
return rv
})
const remoteElements = remoteIds.map(id =>
<ParticipantLine key={id}
participant={participants.remote.get(id)!}
{...lineProps} />)
const localElement = (<ParticipantLine key={localId} participant={participants.local} {...lineProps} />)
const ref = React.useRef<HTMLDivElement>(null)
return (
<div className={classes.container} >
<div className={classes.title} style={{color:textColor}} ref={ref}
onClick={()=>{setShowStat(true)}}
>{(participants.remote.size + 1).toString()} in {connection.conference.name}</div>
<StatusDialog open={showStat}
close={()=>{setShowStat(false)}} {...statusProps} anchorEl={ref.current}/>
{localElement}{remoteElements}
</div>
)
}
Example #11
Source File: RemoteTrackLimitControl.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
RemoteTrackLimitControl: React.FC<Stores> = (props:Stores) => {
const roomInfo = props.roomInfo
const local = props.participants.local
const videoLimit = useObserver(() => local.remoteVideoLimit)
const audioLimit = useObserver(() => local.remoteAudioLimit)
const videoSlider = <MySlider value={videoLimit >= 0 ? videoLimit : MAX}
setValue={(v) => {
local.remoteVideoLimit = v === MAX ? -1 : v
} } />
const audioSlider = <MySlider value={audioLimit >= 0 ? audioLimit : MAX}
setValue={(v) => {
local.remoteAudioLimit = v === MAX ? -1 : v
} } />
return <>
<FormControlLabel
control={videoSlider}
label={t('videoLimit')}
/>
<FormControlLabel
control={audioSlider}
label={t('audioLimit')}
/><br />
<Button variant="contained" color={roomInfo.passMatched ? 'primary' : 'default'}
style={{textTransform:'none'}} disabled={!roomInfo.passMatched}
onClick = { () => {
if (roomInfo.passMatched){
connection.conference.sync.sendTrackLimits('', [local.remoteVideoLimit, local.remoteAudioLimit])
}
}}
>Sync limits</Button>
</>
}
Example #12
Source File: ContentList.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
ContentList: React.FC<BMProps&TextLineStyle> = (props) => {
// console.log('Render RawContentList')
const roomInfo = props.stores.roomInfo
const contents = props.stores.contents
const all = useObserver(() => {
const all:SharedContentInfo[] =
Array.from(contents.roomContentsInfo.size ? contents.roomContentsInfo.values() : contents.all)
all.sort((a,b) => {
let rv = a.name.localeCompare(b.name)
if (rv === 0){ rv = a.ownerName.localeCompare(b.ownerName) }
if (rv === 0){ rv = a.type.localeCompare(b.type) }
if (rv === 0){ rv = a.id.localeCompare(b.id) }
return rv
})
return all
})
const editing = useObserver(() => contents.editing)
const classes = styleForList({height:props.lineHeight, fontSize:props.fontSize})
const participants = props.stores.participants
const elements = all.map(c =>
<ContentLine key={c.id} content = {c} {...props}
participant={participants.find(contents.owner.get(c.id) as string) as ParticipantBase} />)
const {t} = useTranslation()
const textColor = useObserver(() => isDarkColor(roomInfo.backgroundFill) ? 'white' : 'black')
return <div className={classes.container} >
<div className={classes.title} style={{color:textColor}}>{t('Contents')}
{editing ? <Button variant="contained" size="small" color="primary"
style={{marginLeft:4, padding:3, height:'1.4em', fontSize:'0.8'}}
onClick={()=>{ contents.setEditing('')}}>
<DoneIcon style={{fontSize:'1em'}}/> {t('shareEditEnd')}</Button>: undefined}
</div>
{elements}
</div>
}
Example #13
Source File: ShareButton.tsx From binaural-meet with GNU General Public License v3.0 | 6 votes |
ShareButton: React.FC<ShareButtonProps> = (props) => {
const classes = useStyles()
const store = props.stores.contents
const sharing = useObserver(() => store.tracks.localMains.size + store.tracks.localContents.size)
const {t} = useTranslation()
return (
<div className={classes.root}>
<FabWithTooltip size={props.size} color={sharing ? 'secondary' : 'primary'}
title = {acceleratorText2El(t('ttCreateAndshare'))}
aria-label="share" onClick={() => props.setShowDialog(true)}>
<Icon icon={windowArrowUp} style={{width:props.iconSize, height:props.iconSize}} />
</FabWithTooltip>
<ShareDialog {...props} open={props.showDialog} onClose={() => props.setShowDialog(false)} />
</div>
)
}
Example #14
Source File: RemoteParticipant.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
RemoteParticipant: React.FC<ParticipantProps> = (props) => {
const member = React.useRef<RemoteParticipantMember>({} as RemoteParticipantMember).current
const [showMore, setShowMore] = React.useState(false)
const moreControl = moreButtonControl(setShowMore, member)
const [showForm, setShowForm] = React.useState(false)
const [color] = props.participant.getColor()
const styleProps = useObserver(() => ({
position: props.participant.pose.position,
size: props.size,
}))
const classes = useStyles(styleProps)
function switchYarnPhone(ev:React.MouseEvent<HTMLDivElement>, id:string){
if (showForm){ return }
if (participants.yarnPhones.has(id)) {
participants.yarnPhones.delete(id)
}else {
participants.yarnPhones.add(id)
}
participants.yarnPhoneUpdated = true
}
function onClose() {
props.stores.map.keyInputUsers.delete('remoteForm')
setShowForm(false)
}
function openForm() {
props.stores.map.keyInputUsers.add('remoteForm')
setShowForm(true)
}
const buttonRef=React.useRef<HTMLButtonElement>(null)
return (
<div {...moreControl}
onClick = {(ev)=>switchYarnPhone(ev, props.participant.id)}
onContextMenu={(ev) => {ev.preventDefault(); openForm()}}
>
<Participant {...props} isLocal={false}/>
<MoreButton show={showMore} className={classes.more} htmlColor={color} {...moreControl}
buttonRef={buttonRef}
onClickMore = {(ev)=>{
ev.stopPropagation()
openForm()
}} />
<RemoteParticipantForm open={showForm} close={onClose} stores={props.stores}
participant={props.participant as RemoteParticipantStore}
anchorEl={buttonRef.current} anchorOrigin={{vertical:'top', horizontal:'left'}}
anchorReference = "anchorEl"
/>
</div>
)
}
Example #15
Source File: StereoConfig.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
StereoConfig: React.FunctionComponent<StereoConfigProp> = (props: StereoConfigProp) => {
const classes = useStyles()
const hearableRange = useObserver(() => Math.round(stereoParametersStore.hearableRange))
// 1 / ( 1 + rolloff/refDist * (Max(dist/refDist, 1) - 1) )
const handleChange = (event: React.ChangeEvent<{}>, value: number | number[]) => {
assert(typeof value === 'number')
stereoParametersStore.setHearableRange(value)
console.log(`slider: ${value}`)
}
return <Popover
open={props.anchorEl !== null} onClose={props.onClose}
anchorReference={'anchorEl'}
anchorEl={props.anchorEl}
>
<div className={classes.margin}>
<h3>Audio attenuation setting</h3>
<Grid container={true} spacing={2}>
<Grid item={true}>
Hearable range:
</Grid>
<Grid item={true}>
<RolloffNearIcon />
</Grid>
<Grid item={true} xs={true}>
<Slider className={classes.slider} value={hearableRange}
onChange={handleChange}
track={/*'normal'*/ false}
valueLabelDisplay="auto"
getAriaValueText={valuetext} // marks={marks}
aria-labelledby="continuous-slider" />
</Grid>
<Grid item={true}>
<RolloffFarIcon />
</Grid>
</Grid>
</div>
</Popover>
}
Example #16
Source File: bots.tsx From sphinx-win-linux-desktop with MIT License | 5 votes |
export default function Bots() {
const { user, bots } = useStores();
const [loading, setLoading] = useState(true);
const [showCreate, setShowCreate] = useState(false);
const [creating, setCreating] = useState(false);
useEffect(() => {
(async () => {
await bots.getBots();
setLoading(false);
})();
}, []);
async function createBot(n, w) {
setCreating(true);
await bots.createBot(n, w);
setCreating(false);
setShowCreate(false);
}
const noBots = bots.bots.length === 0 ? true : false;
return useObserver(() => {
return (
<Wrap>
<H2>SPHINX BOTS</H2>
<p>[for developers]</p>
{!showCreate && (
<>
{loading && <CircularProgress style={{ color: "white" }} />}
{!loading && noBots && <Zero>No Bots</Zero>}
{/*!loading && <Webhook style={{background:theme.bg}}>
<b>Webhook URL:</b>
<WebhookInput readOnly type="text" value={user.currentIP+'/action'} />
</Webhook>*/}
{!noBots &&
bots.bots.map((b, i) => {
return <Bot key={i} bot={b} ip={user.currentIP} />;
})}
<CreateNew>
<Button onClick={() => setShowCreate(true)} color="primary">
New Bot
</Button>
</CreateNew>
</>
)}
{showCreate && (
<ShowCreate style={{ background: theme.bg }}>
<NewContent creating={creating} createBot={createBot} />
</ShowCreate>
)}
<br />
<br />
<br />
<GitBot />
</Wrap>
);
});
}
Example #17
Source File: CameraSelector.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
CameraSelector: React.FC<CameraSelectorProps> = (props) => {
const {setStep} = props
const {contents, map, participants} = props.stores
const videoMenuItems = useObserver(() =>
props.cameras.videos.map((info, idx) => makeMenuItem(info, closeVideoMenu, idx)))
function makeMenuItem(info: MediaDeviceInfo, close:(did:string) => void, key:number):JSX.Element {
let selected = false
selected = info.deviceId === participants.local.devicePreference.videoInputDevice
const keyStr = String.fromCharCode(65 + key)
return <MenuItem key={info.deviceId} onClick={() => { close(info.deviceId) }} >
{ `${keyStr}\u00A0${(selected ? '?\u00A0' : '\u00A0\u00A0\u2003')}${info.label}` }
</MenuItem> // \u00A0: NBSP, u2003: EM space.
}
function closeVideoMenu(did:string) {
setStep('none')
if (did) {
JitsiMeetJS.createLocalTracks({devices:['video'],
cameraDeviceId: did}).then((tracks: JitsiLocalTrack[]) => {
if (tracks.length) {
const content = createContentOfVideo(tracks, map, 'camera')
contents.shareContent(content)
assert(content.id)
contents.tracks.addLocalContent(content.id, tracks)
}
},
)
}
}
// keyboard shortcut
useEffect(() => {
const onKeyPress = (e: KeyboardEvent) => {
if (e.code.substr(0, 3) === 'Key') {
const keyNum = e.code.charCodeAt(3) - 65
closeVideoMenu(props.cameras.videos[keyNum]?.deviceId)
}
}
window.addEventListener('keypress', onKeyPress)
return () => {
window.removeEventListener('keypress', onKeyPress)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
return <>
{videoMenuItems}
</>
}
Example #18
Source File: StereoAudioSwitch.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
StereoAudioSwitch: React.FC<BMProps&{size?: number, iconSize:number}> = (props) => {
const participants = props.stores.participants
const stereo = useObserver(() => participants.local.useStereoAudio)
const [anchor, setAnchor] = React.useState<Element|null>(null)
const [showStereoBase, setShowSteraoBase] = React.useState(false)
const [showConfirmation, setShowConfirmation] = React.useState(false)
const switchStereo = () => {
if (stereo || participants.local.headphoneConfirmed){
participants.local.headphoneConfirmed = true
participants.local.useStereoAudio = !stereo
participants.local.saveMediaSettingsToStorage()
}else{
setShowConfirmation(true)
}
}
const {t} = useTranslation()
return <>
<FabWithTooltip size={props.size} title={
<>
{isChromium ? t('headphoneL1Chrome') : t('headphoneL1')} <br />
{t('headphoneL2')}
</>}
color = {stereo ? 'secondary' : 'primary'}
onClick={(ev)=>{setAnchor(ev.currentTarget); switchStereo()}}
onClickMore = {stereo ? (ev) => { setShowSteraoBase(true); setAnchor(ev.currentTarget) } : undefined} >
{stereo ? <HeadsetIcon style={{width:props.iconSize, height:props.iconSize}} /> :
<SpeakerIcon style={{width:props.iconSize, height:props.iconSize}} /> }
</FabWithTooltip>
<Popover open={showConfirmation} onClose={() => setShowConfirmation(false)}
anchorEl={anchor} anchorOrigin={{vertical:'top', horizontal:'left'}}
anchorReference = "anchorEl" >
<div style={{padding:20, width:'20em'}}>
<strong>{t('stereoNote')}</strong> <br /><br />
{t('stereoNoteDesc')} <br />
<br />
<Button variant="contained" color="secondary" style={{textTransform:'none'}}
onClick={() => {
participants.local.headphoneConfirmed = true
switchStereo()
setShowConfirmation(false) }} >
{t('stereoConfirmed')}
</Button> <br />
<br />
<Button variant="contained" color="primary" style={{textTransform:'none'}}
onClick={() => { setShowConfirmation(false) }} >
{t('stereoCancel')}
</Button>
</div>
</Popover>
<Popover open={showStereoBase} onClose={() => setShowSteraoBase(false)}
anchorEl={anchor} anchorOrigin={{vertical:'top', horizontal:'left'}}
anchorReference = "anchorEl" >
<div style={{padding:20}}>
{t('soundLocalizationBasedOn')} <br />
<SoundLocalizationSetting />
</div>
</Popover>
</>
}
Example #19
Source File: Participants.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
ParticipantsLayer: React.FC<MapProps> = (props) => {
const store = props.stores.participants
const ids = useObserver(() => Array.from(store.remote.keys()).filter((id) => {
const remote = store.find(id)!
return remote.physics.located
}))
const localId = useObserver(() => store.localId)
const remoteElements = ids.map(id => <RemoteParticipant key={id} stores={props.stores}
participant={store.remote.get(id)!} size={PARTICIPANT_SIZE} />)
const localElement = (<LocalParticipant key={'local'} participant={store.local}
size={PARTICIPANT_SIZE} stores={props.stores}/>)
const lines = useObserver(
() => Array.from(store.yarnPhones).map((rid) => {
const start = store.local.pose.position
const remote = store.remote.get(rid)
if (!remote) { return undefined }
const end = remote.pose.position
return <Line start={start} end={end} key={rid} remote={rid} stores={props.stores}/>
}),
)
const playIds = useObserver(()=> Array.from(store.playback.keys()))
const playbackElements = playIds.map(id => <PlaybackParticipant key={id} stores={props.stores}
participant={store.playback.get(id)!} size={PARTICIPANT_SIZE} />)
const mouseIds = useObserver(() => Array.from(store.remote.keys()).filter(id => (store.find(id)!.mouse.show)))
const remoteMouseCursors = mouseIds.map(
id => <MouseCursor key={`M_${id}`} participantId={id} stores={props.stores} />)
const showLocalMouse = useObserver(() => store.local.mouse.show)
const localMouseCursor = showLocalMouse
? <MouseCursor key={'M_local'} participantId={localId} stores={props.stores} /> : undefined
if (urlParameters.testBot !== null) { return <div /> }
// zIndex is needed to show the participants over the share layer.
return(
<div style={{position:'absolute', zIndex:0x7FFF}}>
{lines}
{playbackElements}
{remoteElements}
{localElement}
{remoteMouseCursors}
{localMouseCursor}
</div>
)
}
Example #20
Source File: ParticipantList.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
ParticipantLine: React.FC<TextLineStyle&BMProps&{participant: ParticipantBase}> = (props) => {
const map = props.stores.map
const name = useObserver(() => (props.participant.information.name))
const avatarSrc = useObserver(() => (props.participant.information.avatarSrc))
const colors = useObserver(() => getColorOfParticipant(props.participant.information))
const size = useObserver(() => props.lineHeight)
const classes = styleForList({height:props.lineHeight, fontSize:props.fontSize})
const [showForm, setShowForm] = React.useState(false)
const ref = React.useRef<HTMLButtonElement>(null)
const {lineHeight, ...propsForForm} = props
// console.log(`PColor pid:${props.participant.id} colors:${colors}`, props.participant)
function onClick(){
if (props.participant.physics.located){
map.focusOn(props.participant)
}else{
if(config.bmRelayServer){
connection.conference.pushOrUpdateMessageViaRelay(
MessageType.REQUEST_PARTICIPANT_STATES, [props.participant.id])
}
const disposer = autorun(()=>{
if (props.participant.physics.located){
map.focusOn(props.participant)
disposer()
}
})
}
}
function onContextMenu(){
if (props.participant.physics.located){
setShowForm(true)
map.keyInputUsers.add('participantList')
}else{
if(config.bmRelayServer){
connection.conference.pushOrUpdateMessageViaRelay(
MessageType.REQUEST_PARTICIPANT_STATES, [props.participant.id])
}
const disposer = autorun(()=>{
if (props.participant.physics.located){
setShowForm(true)
map.keyInputUsers.add('participantList')
disposer()
}
})
}
}
return <>
<Tooltip title={`${props.participant.information.name} (${props.participant.id})`} placement="right">
<div className={classes.outer} style={{margin:'1px 0 1px 0'}}>
<IconButton style={{margin:0, padding:0}} onClick={onClick} onContextMenu={onContextMenu}>
<ImageAvatar border={true} colors={colors} size={size} name={name} avatarSrc={avatarSrc} />
</IconButton>
<Button variant="contained" className={classes.line} ref={ref}
style={{backgroundColor:colors[0], color:colors[1], textTransform:'none'}}
onClick={onClick} onContextMenu={onContextMenu}>
{name}
</Button>
</div>
</Tooltip>
{props.participant.id === props.stores.participants.localId ?
<LocalParticipantForm stores={props.stores} open={showForm} close={()=>{
setShowForm(false)
map.keyInputUsers.delete('participantList')
}} anchorEl={ref.current} anchorOrigin={{vertical:'top', horizontal:'right'}} /> :
<RemoteParticipantForm {...propsForForm} open={showForm} close={()=>{
setShowForm(false)
map.keyInputUsers.delete('participantList')
}} participant={props.stores.participants.remote.get(props.participant.id)}
anchorEl={ref.current} anchorOrigin={{vertical:'top', horizontal:'right'}} />
}
</>
}
Example #21
Source File: ParticipantList.tsx From binaural-meet with GNU General Public License v3.0 | 5 votes |
ParticipantList = React.memo<BMProps&TextLineStyle>(
(props) => {
const localId = useObserver(() => props.stores.participants.localId)
const ids = useObserver(() => Array.from(props.stores.participants.remote.keys()))
return <RawParticipantList {...props} localId={localId} remoteIds = {ids} />
},
)
Example #22
Source File: index.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
function Onboard(props) {
const { user, contacts, chats } = useStores();
const [text, setText] = useState("");
const [showPin, setShowPin] = useState(false);
const [checking, setChecking] = useState(false);
const [alias, setAlias] = useState("");
const [showAliasInput, setShowAliasInput] = useState(false);
return useObserver(()=>{
async function checkCode() {
if (!text || checking) return;
setChecking(true);
try {
const codeString = atob(text);
if (codeString.startsWith("keys::")) {
setShowPin(true);
return;
}
if (codeString.startsWith("ip::")) {
signupWithIP(codeString);
return;
}
} catch (e) {}
const r = await user.signupWithCode(text);
if (!r) {
setChecking(false);
return;
}
const { ip, password } = r;
await sleep(200);
if (ip) {
const token = await user.generateToken(password || "");
if (token) setShowAliasInput(true);
}
setChecking(false);
}
// from relay QR code
async function signupWithIP(s) {
const a = s.split("::");
if (a.length === 1) return;
setChecking(true);
const ip = a[1];
const pwd = a.length > 2 ? a[2] : "";
await user.signupWithIP(ip);
await sleep(200);
const token = await user.generateToken(pwd);
if (token) {
setShowAliasInput(true);
}
setChecking(false);
}
async function aliasEntered(alias) {
if (checking || !alias) return;
setChecking(true);
const publicKey = await rsa.genKeys();
await contacts.updateContact(user.myid, {
alias,
contact_key: publicKey, // set my pubkey in relay
});
user.setAlias(alias);
await Promise.all([
contacts.addContact({
alias: user.invite.inviterNickname,
public_key: user.invite.inviterPubkey,
status: constants.contact_statuses.confirmed,
route_hint: user.invite.inviterRouteHint,
}),
actions(user.invite.action),
user.finishInvite(),
chats.joinDefaultTribe(),
]);
setChecking(false);
props.onRestore();
}
async function pinEntered(pin) {
try {
const restoreString = atob(text);
if (restoreString.startsWith("keys::")) {
const enc = restoreString.substr(6);
const dec = await aes.decrypt(enc, pin);
if (dec) {
await setPinCode(pin);
const priv = await user.restore(dec);
if (priv) {
rsa.setPrivateKey(priv);
return props.onRestore();
}
}
}
} catch (e) {}
// wrong PIN
setShowPin(false);
setChecking(false);
}
if (showPin) {
return (
<main className="onboard">
<PIN forceEnterMode onFinish={pinEntered} />
</main>
);
}
if (showAliasInput && user.invite) {
const inv = user.invite;
return (
<main className="onboard">
<Logo src="static/sphinx-white-logo.png" />
<Title>{inv.welcomeMessage}</Title>
<InputWrap>
<Input
value={alias}
onChange={(e) => setAlias(e.target.value)}
placeholder="Choose a username"
style={{ maxWidth: 300 }}
/>
<CheckCircleIcon
onClick={() => aliasEntered(alias)}
style={{
color: alias ? "#6A8FFF" : "#A5A5A5",
fontSize: 32,
position: "absolute",
right: 14,
top: 16,
cursor: "pointer",
}}
/>
</InputWrap>
<div style={{ height: 80 }}>
{checking && <CircularProgress style={{ color: "white" }} />}
</div>
</main>
);
}
return (
<main className="onboard">
<Logo src="static/sphinx-white-logo.png" />
{props.welcome && (
<>
<Title>Welcome</Title>
<Msg data-testid={"onboardtext"}>
Paste the invitation text or scan the QR code
</Msg>
<InputWrap>
<Input
id={"onboard-enter-code"}
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Enter Code..."
/>
<SendIcon
id={"onboard-send-button"}
onClick={checkCode}
style={{
color: text ? "#6A8FFF" : "#A5A5A5",
fontSize: 24,
position: "absolute",
right: 16,
top: 20,
cursor: "pointer",
}}
/>
</InputWrap>
<div style={{ height: 80 }}>
{checking && <CircularProgress style={{ color: "white" }} />}
</div>
</>
)}
</main>
);
})
}
Example #23
Source File: head.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
export default function Head({ setWidth, width }) {
const { contacts, details, msg, ui, chats, user } = useStores()
const [refreshing, setRefreshing] = useState(false)
// const [snap, setSnap] = useState(false) =
const snap = width < 250
async function refresh(forceMore?:boolean) {
setRefreshing(true)
const conts = await contacts.getContacts()
createPrivateKeyIfNotExists(contacts, user)
await Promise.all([
details.getBalance(),
msg.getMessages(forceMore?true:false),
// chats.getChats(),
])
setRefreshing(false)
}
function startResetIP(){
setRefreshing(true)
}
useEffect(()=>{
refresh()
EE.on(RESET_IP, startResetIP)
EE.on(RESET_IP_FINISHED, refresh)
return ()=>{
EE.removeListener(RESET_IP, startResetIP)
EE.removeListener(RESET_IP_FINISHED, refresh)
}
},[])
function setWidthHandler(width: number) {
setWidth(width)
}
return useObserver(() => <Wrap>
{snap ? <div></div> :
<Top style={{ overflow: 'hidden' }}>
<Tooltip title="Refresh" placement="right">
<CachedButton style={{ cursor: 'pointer', marginLeft: 20, marginRight: 20 }} onClick={() => refresh(true)} >
</CachedButton>
</Tooltip>
<div></div>
<Balance>
{details.balance}
<span>sat</span>
</Balance>
<div></div>
<div
onClick={() => ui.setOnchain(true)}
style={{cursor: 'pointer', height: 20, width: 40, borderRadius: 20, backgroundColor: theme.primary, display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around'}}>
<p style={{marginLeft: 5}}>+</p><img src={whiteBitcoinIcon} style={{height: 15, width: 15}}/>
</div>
<Tooltip title={ui.connected ? 'Connected' : 'Not Connected'} placement="left">
{refreshing ?
<CircularProgress size={18} style={{ marginRight: 20, marginLeft: -20, color: 'white' }} /> :
<FlashOnButton style={{
marginRight: 20, marginLeft: -20, height: 20,
color: ui.connected ? '#49ca97' : '#febd59'
}} />
}
</Tooltip>
</Top>}
{snap ? <div></div> :
<Searcher>
<SearchIcon style={{ color: 'grey', fontSize: 18, position: 'absolute', top: 12, left: 19 }} />
<Search placeholder="Search" value={ui.searchTerm}
onChange={e => ui.setSearchTerm(e.target.value)}
style={{ background: theme.deep, marginRight: 5 }}
/>
<PersonAddIcon onClick={()=>ui.setNewContact({})} style={{color: '#8e9da9', position: 'absolute', right: '24px', marginTop: '9px', cursor: 'pointer'}}/>
<ArrowBackIosButton onClick={() => setWidthHandler(11)}
style={{
fontWeight: 'bold', color: '#618af8', fontSize: 'medium', cursor: 'pointer',
position: 'absolute', right: '0px', marginTop: 13
}}>
</ArrowBackIosButton>
</Searcher>}
{snap ?
<ArrowForwardIosButton onClick={() => setWidthHandler(350)}
style={{
cursor: 'pointer', borderTopRightRadius: '5px', borderBottomRightRadius: '5px', backgroundColor: '#618af8',
position: 'absolute', right: '-26px', top: '73px', paddingTop: 5, paddingBottom: 5, width: 16
}} />
: <div></div>}
</Wrap>)
}
Example #24
Source File: head.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
export default function Head({
height,
appMode,
appURL,
setAppMode,
messagePrice,
status,
tribeParams,
}) {
const [showURL, setShowURL] = useState(false);
const [URL, setURL] = useState("");
const [exit, setExit] = useState(false);
const { contacts, ui, msg, chats, user } = useStores();
return useObserver(() => {
const myid = user.myid;
const chat = ui.selectedChat;
const ownerPubkey = (chat && chat.owner_pubkey) || "";
const owner = contacts.contacts.find((c) => c.id === myid);
const isTribeOwner = owner && owner.public_key === ownerPubkey;
function goToURL() {
ui.setApplicationURL(URL);
}
function clearURL() {
setURL("");
ui.setApplicationURL("");
}
function openJitsi() {
ui.setStartJitsiParams({ messagePrice });
}
useEffect(() => {
if (chat) {
setURL("");
setShowURL(false);
}
}, [chat]);
let photoURL = chat && chat.photo_url;
if (chat && chat.type === constants.chat_types.conversation) {
const cid = chat.contact_ids.find((id) => id !== myid);
const contact = contacts.contacts.find((c) => c.id === cid);
if (contact && contact.photo_url) {
photoURL = contact.photo_url;
}
}
function viewContact() {
if (chat && chat.type === constants.chat_types.conversation) {
const cid = chat.contact_ids.find((id) => id !== myid);
const contact = contacts.contacts.find((c) => c.id === cid);
ui.setViewContact(contact);
}
}
// async function exitGroup(){
// setExit(true)
// }
// async function actuallyExitGroup(){
// const id = chat && chat.id
// if(!id) return
// await chats.exitGroup(id)
// setExit(false)
// }
function openTribeInfo() {
if (chat && chat.status === constants.chat_statuses.pending) return;
ui.setTribeInfo(chat, tribeParams);
}
return (
<Wrap style={{ background: theme.bg, height }}>
{!chat && !showURL && (
<Placeholder>Open a conversation to start using Sphinx</Placeholder>
)}
{chat && (
<Inner>
<Left>
<AvatarWrap>
<Avatar big photo={photoURL} alias={chat.name} />
</AvatarWrap>
<ChatInfo>
<NameWrap>
<Name
onClick={
chat && chat.type === constants.chat_types.conversation
? viewContact
: openTribeInfo
}
style={{ cursor: "pointer" }}
>
{chat.name}
</Name>
{status && chat.type !== constants.chat_types.group && (
<Tooltip
title={
status === "active"
? "Route Confirmed"
: "Cant Find Route"
}
placement="right"
>
<LockIcon
style={{
color: status === "active" ? "#49ca97" : "#febd59",
fontSize: 12,
marginLeft: 8,
marginBottom: 2,
}}
/>
</Tooltip>
)}
</NameWrap>
<SatWrap>
{(messagePrice ? true : false) && (
<Price>{`Price per Message: ${messagePrice} sat `}</Price>
)}
{(tribeParams && tribeParams.escrow_amount
? true
: false) && (
<Price>
{` - Amount to Stake: ${tribeParams.escrow_amount} sat`}
</Price>
)}
</SatWrap>
</ChatInfo>
</Left>
</Inner>
)}
{showURL && (
<Left>
<Input
value={URL}
onChange={(e) => setURL(e.target.value)}
placeholder="Application URL"
style={{ background: theme.extraDeep }}
onKeyPress={(e) => {
if (e.key === "Enter") goToURL();
}}
/>
<IconButton
style={{
position: "absolute",
top: 5,
right: 15,
zIndex: 101,
background: theme.bg,
width: 32,
height: 32,
}}
disabled={!URL}
onClick={goToURL}
>
<NavigateNextIcon style={{ color: "white", fontSize: 17 }} />
</IconButton>
</Left>
)}
<Right>
{/*apps*/}
{appURL && (
<>
{" "}
{appMode ? (
<ChatIcon
style={{
color: "white",
fontSize: 27,
marginRight: 15,
cursor: "pointer",
}}
onClick={() => setAppMode(false)}
/>
) : (
<OpenInBrowserIcon
style={{
color: "white",
fontSize: 27,
marginRight: 15,
cursor: "pointer",
}}
onClick={() => setAppMode(true)}
/>
)}{" "}
</>
)}
{/*browser/bots*/}
{!appURL && (
<>
{" "}
{showURL ? (
<HighlightOffIcon
style={{
color: "white",
fontSize: 27,
marginRight: 15,
cursor: "pointer",
}}
onClick={() => {
setShowURL(false);
clearURL();
}}
/>
) : (
<IconzWrap>
{false && (
<PublicIcon
style={{
color: "white",
fontSize: 27,
marginRight: 15,
cursor: "pointer",
}}
onClick={() => {
setShowURL(true);
ui.setSelectedChat(null);
}}
/>
)}
{!chat && (
<Btn
onClick={() => ui.toggleBots(ui.showBots ? false : true)}
>
<BotIcon />
</Btn>
)}
</IconzWrap>
)}
</>
)}
{/*jitsi*/}
{chat && (
<PhoneIcon
style={{
color: "white",
fontSize: 27,
marginRight: 15,
cursor: "pointer",
}}
onClick={openJitsi}
/>
)}
</Right>
{/* <Dialog onClose={()=>setExit(false)} open={exit}>
<DialogContent>
<div style={{marginBottom:10}}>Exit the Group?</div>
<IconButton onClick={()=>setExit(false)}>Cancel</IconButton>
<IconButton onClick={()=>actuallyExitGroup()}>Yes</IconButton>
</DialogContent>
</Dialog> */}
</Wrap>
);
});
}
Example #25
Source File: Footer.tsx From binaural-meet with GNU General Public License v3.0 | 4 votes |
Footer: React.FC<BMProps&{height?:number}> = (props) => {
const {map, participants} = props.stores
// showor not
const [show, setShow] = React.useState<boolean>(true)
const [showAdmin, setShowAdmin] = React.useState<boolean>(false)
const [showShare, setShowShareRaw] = React.useState<boolean>(false)
function setShowShare(flag: boolean) {
if (flag) {
map.keyInputUsers.add('shareDialog')
}else {
map.keyInputUsers.delete('shareDialog')
}
setShowShareRaw(flag)
}
const memberRef = useRef<Member>(new Member())
const member = memberRef.current
const containerRef = useRef<HTMLDivElement>(null)
const adminButton = useRef<HTMLDivElement>(null)
// observers
const mute = useObserver(() => ({
muteA: participants.local.muteAudio, // mic
muteS: participants.local.muteSpeaker, // speaker
muteV: participants.local.muteVideo, // camera
onStage: participants.local.physics.onStage
}))
// Fab state and menu
const [deviceInfos, setDeviceInfos] = React.useState<MediaDeviceInfo[]>([])
const [micMenuEl, setMicMenuEl] = React.useState<Element|null>(null)
const [speakerMenuEl, setSpeakerMenuEl] = React.useState<Element|null>(null)
const [videoMenuEl, setVideoMenuEl] = React.useState<Element|null>(null)
const {t} = useTranslation()
const classes = useStyles()
// Footer collapse conrtrol
function checkMouseOnBottom() {
return map.screenSize[1] - (map.mouse[1] - map.offset[1]) < 90
}
const mouseOnBottom = useObserver(checkMouseOnBottom)
useEffect(() => {
if (checkMouseOnBottom()) { member.touched = true }
setShowFooter(mouseOnBottom || !member.touched)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mouseOnBottom, member.touched])
function setShowFooter(show: boolean) {
if (show) {
setShow(true)
if (member.timeoutOut) {
clearTimeout(member.timeoutOut)
member.timeoutOut = undefined
}
containerRef.current?.focus()
}else {
if (!member.timeoutOut) {
member.timeoutOut = setTimeout(() => {
setShow(false)
member.timeoutOut = undefined
}, 500)
}
}
}
// keyboard shortcut
useEffect(() => {
const onKeyDown = (e: KeyboardEvent) => {
// console.log(`onKeyDown: code: ${e.code}`)
if (map.keyInputUsers.size === 0) {
if (!e.ctrlKey && !e.metaKey && !e.altKey){
if (e.code === 'KeyM') { // mute/unmute audio
participants.local.muteAudio = !participants.local.muteAudio
setShowFooter(true)
}
if (e.code === 'KeyC') { // Create share dialog
setShowFooter(true)
setShowShare(true)
e.preventDefault()
e.stopPropagation()
}
if (e.code === 'KeyL' || e.code === 'Escape') { // Leave from keyboard
participants.local.physics.awayFromKeyboard = true
}
}
}
}
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
// render footer
return React.useMemo(() => {
// Create menu list for device selection
function makeMenuItem(info: MediaDeviceInfo, close:(did:string) => void):JSX.Element {
let selected = false
if (info.kind === 'audioinput') {
selected = info.deviceId === participants.local.devicePreference.audioInputDevice
}else if (info.kind === 'audiooutput') {
selected = info.deviceId === participants.local.devicePreference.audioOutputDevice
}else if (info.kind === 'videoinput') {
selected = info.deviceId === participants.local.devicePreference.videoInputDevice
}
return <MenuItem key={info.deviceId}
onClick={() => { close(info.deviceId) }}
> { (selected ? '✔\u00A0' : '\u2003') + info.label }</MenuItem> // \u00A0: NBSP, u2003: EM space.
}
const micMenuItems:JSX.Element[] = [
<MenuItem key = {'broadcast'} ><BroadcastControl {...props} /></MenuItem>]
const speakerMenuItems:JSX.Element[] = []
const videoMenuItems:JSX.Element[] = [
<MenuItem key = {'faceTrack'} ><FaceControl {...props} /></MenuItem>]
deviceInfos.forEach((info) => {
if (info.kind === 'audioinput') {
const broadcastControl = micMenuItems.pop() as JSX.Element
micMenuItems.push(makeMenuItem(info, closeMicMenu))
micMenuItems.push(broadcastControl)
}
if (info.kind === 'audiooutput') {
speakerMenuItems.push(makeMenuItem(info, closeSpeakerMenu))
}
if (info.kind === 'videoinput') {
const faceControl = videoMenuItems.pop() as JSX.Element
videoMenuItems.push(makeMenuItem(info, closeVideoMenu))
videoMenuItems.push(faceControl)
}
})
function closeMicMenu(did:string) {
if (did) {
participants.local.devicePreference.audioInputDevice = did
participants.local.saveMediaSettingsToStorage()
}
setMicMenuEl(null)
}
function closeSpeakerMenu(did:string) {
if (did) {
participants.local.devicePreference.audioOutputDevice = did
participants.local.saveMediaSettingsToStorage()
}
setSpeakerMenuEl(null)
}
function closeVideoMenu(did:string) {
if (did) {
participants.local.devicePreference.videoInputDevice = did
participants.local.saveMediaSettingsToStorage()
}
setVideoMenuEl(null)
}
// Device list update when the user clicks to show the menu
const fabSize = props.height
const iconSize = props.height ? props.height * 0.7 : 36
function updateDevices(ev:React.PointerEvent | React.MouseEvent | React.TouchEvent) {
navigator.mediaDevices.enumerateDevices()
.then(setDeviceInfos)
.catch(() => { console.log('Device enumeration error') })
}
function openAdmin(){
map.keyInputUsers.add('adminForm')
setShowAdmin(true)
}
function closeAdmin(){
map.keyInputUsers.delete('adminForm')
setShowAdmin(false)
}
return <div ref={containerRef} className={classes.root}>
<Collapse in={show} classes={classes}>
<StereoAudioSwitch size={fabSize} iconSize={iconSize} {...props}/>
<FabMain size={fabSize} color={mute.muteS ? 'primary' : 'secondary' }
aria-label="speaker" onClick={() => {
participants.local.muteSpeaker = !mute.muteS
if (participants.local.muteSpeaker) {
participants.local.muteAudio = true
}
participants.local.saveMediaSettingsToStorage()
}}
onClickMore = { (ev) => {
updateDevices(ev)
setSpeakerMenuEl(ev.currentTarget)
}}
>
{mute.muteS ? <SpeakerOffIcon style={{width:iconSize, height:iconSize}} />
: <SpeakerOnIcon style={{width:iconSize, height:iconSize}} /> }
</FabMain>
<Menu anchorEl={speakerMenuEl} keepMounted={true}
open={Boolean(speakerMenuEl)} onClose={() => { closeSpeakerMenu('') }}>
{speakerMenuItems}
</Menu>
<FabWithTooltip size={fabSize} color={mute.muteA ? 'primary' : 'secondary' } aria-label="mic"
title = {acceleratorText2El(t('ttMicMute'))}
onClick = { () => {
participants.local.muteAudio = !mute.muteA
if (!participants.local.muteAudio) {
participants.local.muteSpeaker = false
}
participants.local.saveMediaSettingsToStorage()
}}
onClickMore = { (ev) => {
updateDevices(ev)
setMicMenuEl(ev.currentTarget)
} }
>
{mute.muteA ? <MicOffIcon style={{width:iconSize, height:iconSize}} /> :
mute.onStage ?
<Icon icon={megaphoneIcon} style={{width:iconSize, height:iconSize}} color="gold" />
: <MicIcon style={{width:iconSize, height:iconSize}} /> }
</FabWithTooltip>
<Menu anchorEl={micMenuEl} keepMounted={true}
open={Boolean(micMenuEl)} onClose={() => { closeMicMenu('') }}>
{micMenuItems}
</Menu>
<FabMain size={fabSize} color={mute.muteV ? 'primary' : 'secondary'} aria-label="camera"
onClick = { () => {
participants.local.muteVideo = !mute.muteV
participants.local.saveMediaSettingsToStorage()
}}
onClickMore = { (ev) => {
updateDevices(ev)
setVideoMenuEl(ev.currentTarget)
} }
>
{mute.muteV ? <VideoOffIcon style={{width:iconSize, height:iconSize}} />
: <VideoIcon style={{width:iconSize, height:iconSize}} /> }
</FabMain>
<Menu anchorEl={videoMenuEl} keepMounted={true}
open={Boolean(videoMenuEl)} onClose={() => { closeVideoMenu('') }}>
{videoMenuItems}
</Menu>
<ShareButton {...props} size={fabSize} iconSize={iconSize} showDialog={showShare}
setShowDialog={setShowShare} />
<ErrorDialog {...props}/>
<FabMain size={fabSize} onClick={openAdmin} divRef={adminButton}
style={{marginLeft:'auto', marginRight:10, opacity:0.1}}>
<SettingsIcon style={{width:iconSize, height:iconSize}} />
</FabMain>
<Popover open={showAdmin} onClose={closeAdmin}
anchorEl={adminButton.current} anchorOrigin={{vertical:'top', horizontal:'left'}}
anchorReference = "anchorEl" >
<AdminConfigForm close={closeAdmin} stores={props.stores}/>
</Popover>
</Collapse>
</div >
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[mute.muteA, mute.muteS, mute.muteV, mute.onStage,
show, showAdmin, showShare, micMenuEl, speakerMenuEl, videoMenuEl, deviceInfos]
)
}
Example #26
Source File: foot.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
export default function Foot({
height,
messagePrice,
tribeBots,
msgPrice,
setMsgPrice,
}) {
const { ui, msg, meme, details, user } = useStores();
const [recording, setRecording] = useState(false);
const [record, setRecord] = useState(false);
const [uploading, setUploading] = useState(false);
return useObserver(() => {
const myid = user.myid;
const chat = ui.selectedChat;
let text = (chat ? ui.tribeText[chat.id] : "") || "";
useEffect(() => {
if (recording) {
setRecord(true);
}
}, [recording]);
const msgInputRef = useRef();
// if height change, maybe clicked reply
const oldHeight = useRef(height);
useEffect(() => {
if (oldHeight.current < height) {
if (msgInputRef && msgInputRef.current) {
(msgInputRef.current as any).focus();
}
}
}, [height]);
async function sendGif(amount: number) {
const params = ui.imgViewerParams;
const gifJSON = JSON.stringify({
id: params.id,
url: params.data,
aspect_ratio: params.aspect_ratio,
text: text,
});
const b64 = btoa(gifJSON);
let contact_id = chat.contact_ids.find((cid) => cid !== myid);
await msg.sendMessage({
contact_id,
chat_id: chat.id,
text: "giphy::" + b64,
reply_uuid: "",
amount: amount || 0,
});
ui.setImgViewerParams(null);
ui.setTribeText(chat.id, "");
}
async function sendPaidMsg() {
setUploading(true);
const server = meme.getDefaultServer();
const file = new File([text], "message.txt", {
type: "text/plain;charset=utf-8",
});
const r = await uploadFile(
file,
"sphinx/text",
server.host,
server.token,
"message.txt"
);
await msg.sendAttachment({
contact_id: null,
chat_id: chat.id,
muid: r.muid,
media_key: r.media_key,
media_type: "sphinx/text",
text: "",
price: parseInt(msgPrice) || 0,
amount: messagePrice || 0,
});
ui.setTribeText(chat.id, "");
setMsgPrice("");
setUploading(false);
}
function sendMessage() {
if (!text) return;
if (msgPrice) {
return sendPaidMsg();
}
let contact_id = chat.contact_ids.find((cid) => cid !== myid);
let { price, failureMessage } = calcBotPrice(tribeBots, text);
if (failureMessage) {
return alert(failureMessage);
}
if (price > details.balance) {
return alert("Not enough balance");
}
if (ui.imgViewerParams && ui.imgViewerParams.type === "image/gif") {
return sendGif(messagePrice + price);
}
let txt = text;
if (ui.extraTextContent) {
const { type, ...rest } = ui.extraTextContent;
txt = type + "::" + JSON.stringify({ ...rest, text });
}
msg.sendMessage({
contact_id,
text: txt,
chat_id: chat.id || null,
amount: messagePrice + price || 0, // 5, // CHANGE THIS
reply_uuid: ui.replyUUID || "",
});
ui.setTribeText(chat.id, "");
if (ui.replyUUID) ui.setReplyUUID("");
if (ui.extraTextContent) ui.setExtraTextContent(null);
}
let [count, setCount] = useState(0);
useInterval(
() => {
// Your custom logic here
setCount(count + 1);
},
recording ? 1000 : null
);
function duration(seconds) {
var start = moment(0);
var end = moment(seconds * 1000);
let diff = end.diff(start);
return moment.utc(diff).format("m:ss");
}
async function onStop(res) {
const blob = res.blob;
const file = new File([blob], "Audio.wav", { type: blob.type });
const server = meme.getDefaultServer();
setUploading(true);
const r = await uploadFile(
file,
blob.type,
server.host,
server.token,
"Audio.wav"
);
await msg.sendAttachment({
contact_id: null,
chat_id: chat.id,
muid: r.muid,
media_key: r.media_key,
media_type: blob.type,
text: "",
price: 0,
amount: 0,
});
setUploading(false);
setRecording(false);
setCount(0);
}
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
const msgs = chat && msg.messages[chat.id];
const { replyMessageSenderAlias, replyMessageContent, replyColor } =
useReplyContent(msgs, ui.replyUUID, ui.extraTextContent);
const [showGiphy, setShowGiphy] = useState(false);
function handleGiphy() {
setShowGiphy(true);
}
function handlePriceChange(e) {
let numRegex = /^\d+$/;
if (numRegex.test(e.target.value) || e.target.value === "") {
setMsgPrice(e.target.value);
}
}
if (ui.showBots) {
return <></>;
}
if (recording) {
return (
<MicWrap style={{ background: theme.bg, height }}>
<Blinker>
<BlinkingButton
style={{
height: 10,
padding: 7,
backgroundColor: "#ea7574",
marginTop: -1,
}}
/>
</Blinker>
<WaveWrap>
<ReactMic
className="sound-wave"
record={record}
backgroundColor={theme.bg}
onStop={onStop}
// onStart={onStart}
strokeColor="#ffffff"
/>
</WaveWrap>
<div
style={{
color: "white",
height: 25,
marginTop: 8,
marginRight: 10,
}}
>
{duration(count)}
</div>
<IconButton
style={{
width: 39,
height: 39,
marginRight: 17,
backgroundColor: "#ea7574",
opacity: uploading ? 0.8 : 1,
}}
onClick={() => {
setRecord(false), setRecording(false), setCount(0);
}}
disabled={uploading}
>
<Close
style={{ color: "white", fontSize: 30, borderRadius: "50%" }}
/>
</IconButton>
<IconButton
style={{
width: 39,
height: 39,
marginRight: 17,
backgroundColor: "#47ca97",
opacity: uploading ? 0.8 : 1,
}}
onClick={() => setRecord(false)}
disabled={uploading}
>
<Check
style={{ color: "white", fontSize: 30, borderRadius: "50%" }}
/>
</IconButton>
</MicWrap>
);
}
return (
<Wrap style={{ background: theme.bg, height }}>
{replyMessageContent && replyMessageSenderAlias && (
<ReplyMsg color={replyColor || "grey"}>
<ReplyMsgText>
<span style={{ color: "white" }}>{replyMessageSenderAlias}</span>
<span style={{ color: "#809ab7", marginTop: 5 }}>
{replyMessageContent}
</span>
</ReplyMsgText>
<CloseButton
style={{ cursor: "pointer" }}
onClick={() => {
ui.setReplyUUID(null);
ui.setExtraTextContent(null);
}}
/>
</ReplyMsg>
)}
{showGiphy && (
<GiphyWrap>
<ReactGiphySearchbox
style={{ position: "absolute" }}
apiKey="cnc84wQZqQn2vsWeg4sYK3RQJSrYPAl7"
onSelect={(item) => {
const data = item.images.original.url;
const height = parseInt(item.images.original.height) || 200;
const width = parseInt(item.images.original.width) || 200;
ui.setImgViewerParams({
data,
aspect_ratio: width / height,
id: item.id,
type: "image/gif",
});
setShowGiphy(false);
}}
/>
<CloseWrap onClick={() => setShowGiphy(false)}>
CLOSE <CloseButton />
</CloseWrap>
</GiphyWrap>
)}
<InnerWrap>
<IconButton
style={{
pointerEvents:
chat && chat.type === constants.chat_types.conversation
? "auto"
: "none",
cursor: "pointer",
height: 30,
width: 30,
marginLeft: 10,
backgroundColor: "#618af8",
}}
onClick={() => ui.setSendRequestModal(chat)}
>
<AddIcon
style={{ color: chat ? "#ffffff" : "#b0c4ff", fontSize: 22 }}
/>
</IconButton>
<img
src={giphyIcon}
onClick={chat && handleGiphy}
style={{
cursor: chat ? "pointer" : "auto",
marginLeft: "15px",
filter: chat ? "grayscale(0%)" : "grayscale(75%)",
}}
/>
<InsertEmoticonButton
style={{
pointerEvents: chat ? "auto" : "none",
cursor: "pointer",
marginLeft: 10,
color: chat ? "#8f9ca9" : "#2a3540",
fontSize: 30,
}}
aria-describedby={id}
onClick={handleClick}
/>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
anchorOrigin={{
vertical: "top",
horizontal: "right",
}}
transformOrigin={{
vertical: "bottom",
horizontal: "left",
}}
>
<Picker
showPreview={false}
showSkinTones={false}
onSelect={(emoji) =>
ui.setTribeText(chat.id, text + emoji.native)
}
/>
</Popover>
<Input
value={text}
onChange={(e) => ui.setTribeText(chat.id, e.target.value)}
placeholder="Message"
ref={msgInputRef}
style={{
background: theme.extraDeep,
fontSize: 18,
textAlign: "left",
}}
disabled={!chat || chat.status === constants.chat_statuses.pending}
onKeyPress={(e) => {
if (e.key === "Enter") {
e.preventDefault(), sendMessage();
}
}}
></Input>
<PriceInput theme={theme}>
<div>Price:</div>
<PriceAmount
onChange={handlePriceChange}
value={msgPrice}
theme={theme}
disabled={!chat}
/>
</PriceInput>
<IconButton
style={{
width: 39,
height: 39,
marginRight: 10,
marginLeft: 10,
backgroundColor: "#618af8",
}}
disabled={!chat || !text || uploading}
onClick={sendMessage}
>
<SendIcon
style={{ color: chat ? "#ffffff" : "#b0c4ff", fontSize: 22 }}
/>
</IconButton>
<IconButton
style={{
width: 39,
height: 39,
marginRight: 10,
backgroundColor: "transparent",
}}
disabled={!chat}
onClick={() => setRecording(true)}
>
<MicIcon
style={{ color: chat ? "#8f9ca9" : "#2a3540", fontSize: 30 }}
/>
</IconButton>
</InnerWrap>
</Wrap>
);
});
}
Example #27
Source File: chat.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
function ChatContent({
appMode,
appURL,
footHeight,
msgPrice,
setMsgPrice,
messagePrice,
}) {
const { contacts, ui, chats, meme, msg, user, details } = useStores();
const chat = ui.selectedChat;
const [alert, setAlert] = useState(``);
const [anchorEl, setAnchorEl] = React.useState(null);
const [menuMessage, setMenuMessage] = useState(null);
const [uploading, setUploading] = useState(false);
const [msgCount, setMsgCount] = useState(20);
const [customBoost, setCustomBoost] = useState(user.tipAmount || 100);
const [insfBalance, setInsfBalance] = useState(false);
const myid = user.myid;
async function dropzoneUpload(files) {
const file = files[0];
const server = meme.getDefaultServer();
setUploading(true);
const typ = file.type || "text/plain";
// console.log("type === ", typ)
// console.log("host === ", server.host)
// console.log("token === ", server.token)
// console.log("filename === ", file.name)
// console.log("file === ", file)
const r = await uploadFile(
file,
typ,
server.host,
server.token,
file.name || "Image.jpg"
);
await msg.sendAttachment({
contact_id: null,
chat_id: chat.id,
muid: r.muid,
media_key: r.media_key,
media_type: typ,
text: "",
price: parseInt(msgPrice) || 0,
amount: messagePrice || 0,
});
setMsgPrice("");
setUploading(false);
}
// boost an existing message
function onMessageBoost(uuid) {
if (!uuid) return;
const amount = (customBoost || user.tipAmount || 100) + messagePrice;
if (amount > details.balance) {
setInsfBalance(true);
setTimeout(() => {
setInsfBalance(false);
}, 3500);
return;
}
msg.sendMessage({
boost: true,
contact_id: null,
text: "",
amount,
chat_id: chat.id || null,
reply_uuid: uuid,
message_price: messagePrice,
});
setCustomBoost(user.tipAmount || 100);
}
const handleMenuClick = (event, m) => {
setAnchorEl(event.currentTarget);
setMenuMessage(m);
};
const handleMenuClose = () => {
setAnchorEl(null);
setMenuMessage(null);
setCustomBoost(user.tipAmount || 100);
};
function onCopy(word) {
setAlert(`${word} copied to clipboard`);
setTimeout(() => {
setAlert(``);
}, 2000);
}
async function onApproveOrDenyMember(contactId, status, msgId) {
await msg.approveOrRejectMember(contactId, status, msgId);
}
return useObserver(() => {
const chat = ui.selectedChat;
const msgs = useMsgs(chat) || [];
// console.log(msgs);
const isTribe = chat && chat.type === constants.chat_types.tribe;
const h = `calc(100% - ${headHeight + footHeight}px)`;
useEffect(() => {
setMsgCount(20);
}, [chat && chat.id]);
if (ui.loadingChat) {
return (
<LoadingWrap style={{ maxHeight: h, minHeight: h }}>
<CircularProgress size={32} style={{ color: "white" }} />
</LoadingWrap>
);
}
if (ui.showBots) {
return <Bots />;
}
const shownMsgs = msgs.slice(0, msgCount);
function handleScroll(e) {
if (e.target.scrollTop === 0) {
setMsgCount((c) => c + 20);
}
}
async function joinTribe(tribeParams) {
if (tribeParams) ui.setViewTribe(tribeParams);
}
if (chat && chat.status === constants.chat_statuses.pending) {
return (
<Wrap h={h} style={{ alignItems: "center", justifyContent: "center" }}>
Waiting for admin approval
</Wrap>
);
}
return (
<Wrap h={h}>
<Dropzone
disabled={!chat}
noClick={true}
multiple={false}
onDrop={dropzoneUpload}
>
{({ getRootProps, getInputProps, isDragActive }) => (
<div style={{ flex: 1 }} {...getRootProps()}>
<input {...getInputProps()} />
{(isDragActive || uploading) && (
<DropZoneContainer h={h}>
<DropZoneInner>
{uploading
? "File Uploading..."
: "Drag Image or Video here"}
</DropZoneInner>
</DropZoneContainer>
)}
<Layer show={!appMode} style={{ background: theme.deep }}>
<MsgList
className="msg-list"
onScroll={handleScroll}
id="chat-content"
>
{shownMsgs.map((m, i) => {
const { senderAlias, senderPic } = useMsgSender(
m,
contacts.contacts,
isTribe
);
if (m.dateLine) {
return (
<DateLine key={"date" + i} dateString={m.dateLine} />
);
}
if (!m.chat) m.chat = chat;
return (
<Msg
joinTribe={joinTribe}
key={m.id}
{...m}
senderAlias={senderAlias}
senderPic={senderPic}
handleClick={(e) => handleMenuClick(e, m)}
handleClose={handleMenuClose}
onCopy={onCopy}
myid={myid}
onApproveOrDenyMember={onApproveOrDenyMember}
/>
);
})}
</MsgList>
{alert && (
<Alert
style={{
position: "absolute",
bottom: 20,
left: "calc(50% - 90px)",
opacity: 0.7,
height: 35,
padding: `0px 8px 4px 8px`,
}}
icon={false}
>
{alert}
</Alert>
)}
{insfBalance && (
<Alert
style={{
position: "absolute",
bottom: "50%",
left: "calc(50% - 105px)",
opacity: 0.9,
}}
icon={false}
>
Insufficient Balance
</Alert>
)}
<MsgMenu
anchorEl={anchorEl}
menuMessage={menuMessage}
isMe={menuMessage && menuMessage.sender === myid}
handleMenuClose={handleMenuClose}
onCopy={onCopy}
onBoost={onMessageBoost}
customBoost={customBoost}
setCustomBoost={setCustomBoost}
/>
</Layer>
{appURL && (
<Layer
show={appMode}
style={{
background: theme.deep,
height: "calc(100% + 63px)",
}}
>
<Frame url={appURL} />
</Layer>
)}
</div>
)}
</Dropzone>
<Anim />
</Wrap>
);
});
}
Example #28
Source File: chat.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
function Chat() {
const { chats, ui, msg, details, user } = useStores();
const [appMode, setAppMode] = useState(true);
const [status, setStatus] = useState<RouteStatus>(null);
const [tribeParams, setTribeParams] = useState(null);
const [msgPrice, setMsgPrice] = useState("");
let footHeight = 65;
// function joinEvanTest(){
// chats.joinTribe({
// name:'Evan Test',
// uuid:'XyyNsiAM4pbbX4vjtYz2kcFye-h4dd9Nd2twi2Az8gGDQdIbM3HU1WV3XoASXLedCaVpl0YrAvjvBpAPt9ZB0-rpV4Y1',
// group_key:'MIIBCgKCAQEA8oGCKreUM09hDXKDoe3laNZY9fzyNMUUZMt+yC5WhoUIzvW1PtRJ6AWH+xwAK3nD+sUK8LP6y9nNSK1z5SNvFem0fmEq1JBPGEUMlqIA4CUeCbJB7cUan1s4DWDosEQBY/fiQNslNKWko97dEyjGEEi0KJkE2kNTgsmpEPfH4+V886Ei4/NP7qTR/3H4ohC5MlUiXyv/Ah1GuhmAM8Hu57fdVe26AJ1jXFkMikC/+84ysseycoQZmCLDvLd6R0nnQ/LOafV2vCC36HChSzylU7qkFHkdbUg6GXO0nxk6dzGFrJpjppJzhrRxmfrL+9RcsuMXkDAQFUZg8wAipPXmrwIDAQAB',
// host:'tribes.sphinx.chat',
// amount:10,
// img:'',
// owner_alias:'Evan',
// owner_pubkey:'02290714deafd0cb33d2be3b634fc977a98a9c9fa1dd6c53cf17d99b350c08c67b',
// is_private:true,
// })
// }
return useObserver(() => {
if (useHasReplyContent()) footHeight = 120;
const chat = ui.selectedChat;
// console.log("CHAT",chat)
// this the boost MESSAGE (doesnt actually include the boost amount),
// the actual boost amount is sent by feed.sendPayments by the podcast XML
function onBoostPod(sp: StreamPayment) {
if (!(chat && chat.id)) return;
msg.sendMessage({
contact_id: null,
text: `${JSON.stringify(sp)}`,
chat_id: chat.id || null,
amount: messagePrice,
reply_uuid: "", // not replying
boost: true,
});
}
useEffect(() => {
setStatus(null);
setTribeParams(null);
if (!chat) return;
(async () => {
setAppMode(true);
const isTribe = chat.type === constants.chat_types.tribe;
if (isTribe) {
ui.setLoadingChat(true);
const params = await chats.getTribeDetails(chat.host, chat.uuid);
if (params) {
setTribeParams(params);
}
ui.setLoadingChat(false);
}
const r = await chats.checkRoute(chat.id, user.myid);
if (r && r.success_prob && r.success_prob > 0.01) {
setStatus("active");
} else {
setStatus("inactive");
}
})();
}, [chat]);
const feedURL = tribeParams && tribeParams.feed_url;
const appURL = tribeParams && tribeParams.app_url;
const tribeBots = tribeParams && tribeParams.bots;
let messagePrice = 0;
if (tribeParams) {
messagePrice = tribeParams.price_per_message + tribeParams.escrow_amount;
}
return (
<Section style={{ background: theme.deep }}>
<Inner>
<Head
height={headHeight}
appURL={appURL}
setAppMode={setAppMode}
appMode={appMode}
messagePrice={messagePrice}
status={status}
tribeParams={tribeParams}
/>
<ChatContent
msgPrice={msgPrice}
setMsgPrice={setMsgPrice}
appMode={appMode}
appURL={appURL}
footHeight={footHeight}
messagePrice={messagePrice}
/>
<Foot
msgPrice={msgPrice}
setMsgPrice={setMsgPrice}
height={footHeight}
tribeBots={tribeBots}
messagePrice={messagePrice}
/>
</Inner>
<Pod url={feedURL} chat={chat} onBoost={onBoostPod} />
</Section>
);
});
}
Example #29
Source File: profile.tsx From sphinx-win-linux-desktop with MIT License | 4 votes |
export default function Profile() {
const { ui, contacts, details, user } = useStores();
const [advanced, setAdvanced] = useState(false);
const [loading, setLoading] = useState(false);
const [copied, setCopied] = useState(false);
return useObserver(()=>{
let me:any = contacts.contacts.find((c) => c.id === user.myid);
if(!me) me = {route_hint:'',alias:'',photo_url:''}
if(!me.route_hint) me.route_hint=''
async function updateMe(v) {
setLoading(true);
await contacts.updateContact(user.myid, { alias: v.alias });
setLoading(false);
}
function handleCloseModal() {
ui.setShowProfile(false);
}
async function exportKeys() {
if (copied) return;
const priv = await rsa.getPrivateKey();
const me = contacts.contacts.find((c) => c.id === user.myid);
const pub = me && me.contact_key;
const ip = user.currentIP;
const token = user.authToken;
if (!priv || !pub || !ip || !token) return;
const str = `${priv}::${pub}::${ip}::${token}`;
const pin = await userPinCode();
const enc = await aes.encryptSymmetric(str, pin);
const final = btoa(`keys::${enc}`);
navigator.clipboard.writeText(final);
setCopied(true);
await sleep(4000);
setCopied(false);
}
return (
<Modal
open={true}
onClose={handleCloseModal}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
{me ?
<Content bg={theme.bg}>
<TopWrapper>
<Avatar
xx-large
photo={me.photo_url}
alias={me.alias}
style={{ size: "50px" }}
/>
<InnerTopWrapper>
<Name>{me.alias && me.alias.toUpperCase()}</Name>
<Sat>
{details.balance} <span style={{ color: "#6b7b8d" }}>sat</span>
</Sat>
</InnerTopWrapper>
</TopWrapper>
<Toggle
onChange={(e) => setAdvanced(e === "Advanced")}
items={["Basic", "Advanced"]}
value={advanced ? "Advanced" : "Basic"}
/>
{advanced ? (
<Form
key={"adv"}
onSubmit={(v) => user.setCurrentIP(v.currentIP)}
schema={advSchema}
initialValues={{ currentIP: user.currentIP }}
/>
) : (
<Form
key={"basic"}
onSubmit={updateMe}
schema={schema}
initialValues={me}
loading={loading}
/>
)}
<ExportKeysWrap style={{ color: theme.primary }} onClick={exportKeys}>
{copied ? "Keys Copied!" : "Export Keys"}
</ExportKeysWrap>
</Content>
: <Content bg={theme.bg}></Content>}
</Modal>
);
})
}