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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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'}}/>&nbsp;{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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
                      &nbsp;
                      {` - 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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
    );
  })
}