~vesto/grooveboat

1707ae997d8a952b058970a3658cd551d83d3070 — Steve Gattuso 5 years ago e736633
add rejoining
M src/js/pages/room-selector/data.js => src/js/pages/room-selector/data.js +3 -1
@@ 42,6 42,7 @@ function* joinBuoy({inviteCode, callback}) {

  // Fetch the rooms in the buoy
  yield put(RoomActions.fetchAll());
  yield put(RoomActions.fetchStored());
  if (callback) {
    yield call(callback);
  }


@@ 53,7 54,7 @@ function* init() {
  const {buoys} = yield take(BuoyActionTypes.FETCH_BUOYS_SUCCESS);
  if (process.env.DEFAULT_INVITE) {
    // Do we have a default buoy to go with?
    yield* joinBuoy({inviteCode: process.env.DEFAULT_INVITE});
    yield call(joinBuoy, {inviteCode: process.env.DEFAULT_INVITE});
    return;
  }
  if (buoys.count() === 0 && !process.env.DEFAULT_INVITE) {


@@ 70,6 71,7 @@ function* init() {
    return;
  }

  yield put(RoomActions.fetchStored());
  yield put(RoomActions.fetchAll());
}


M src/js/pages/room-selector/index.js => src/js/pages/room-selector/index.js +52 -20
@@ 78,6 78,7 @@ const RoomSelectorPage = ({history}) => {
  // Hooks
  //
  const rooms = useSelector(RoomSelectors.rooms);
  const storedRooms = useSelector(RoomSelectors.storedRooms);
  const isConnecting = useSelector(BuoySelectors.isConnecting);
  const connectedBuoy = useSelector(BuoySelectors.connectedBuoy);
  const dispatch = useDispatch();


@@ 145,28 146,59 @@ const RoomSelectorPage = ({history}) => {
              you're connected to {connectedBuoy.get('name')}
            </p>
            {(rooms.count() > 0) && (
              <ul className="selector--rooms">
                {rooms.map((r) => {
                  const filename = r.getIn(['nowPlaying', 'track', 'filename']);
                  let nowPlaying = 'nothing playing';
                  if (filename) {
                    nowPlaying = `🎶 ${filename}`;
                  }
              <Fragment>
                <ul className="selector--rooms">
                  {rooms.map((r) => {
                    const filename = r.getIn(['nowPlaying', 'track', 'filename']);
                    let nowPlaying = 'nothing playing';
                    if (filename) {
                      nowPlaying = `🎶 ${filename}`;
                    }

                  return <li
                    key={r.get('id')}
                    onClick={() => history.push(`/rooms/${r.get('id')}`)}
                    return <li
                      key={r.get('id')}
                      onClick={() => history.push(`/rooms/${r.get('id')}`)}
                    >
                      <div className="room--name">{r.get('name')}</div>
                      <div className="room--nowplaying">{nowPlaying}</div>
                      <div className="room--meta">
                        👥 {r.get('peerCount')}
                      </div>
                    </li>;
                  })}
                </ul>
                <div className="selector--actions">
                  <button
                    type="button"
                    onClick={() => setScreen(SCREEN_CREATE)}
                  >
                    <div className="room--name">{r.get('name')}</div>
                    <div className="room--nowplaying">{nowPlaying}</div>
                    <div className="room--meta">
                      👥 {r.get('peerCount')}
                    </div>
                  </li>;
                })}
              </ul>
                    create room
                  </button>
                </div>
              </Fragment>
            )}

            {(storedRooms.count() > 0) && (
              <Fragment>
                <h2>inactive rooms</h2>
                <p>
                  the following are rooms you own but are currently inactive
                </p>
                <ul className="selector--rooms">
                  {storedRooms.map((r) => {
                    return <li
                      key={r.get('_id')}
                      onClick={() => history.push(r.get('_id'))}
                    >
                      <div className="room--name">{r.get('name')}</div>
                      <div className="room--nowplaying">Inactive</div>
                    </li>;
                  })}
                </ul>
              </Fragment>
            )}
            {(rooms.count() === 0) && (

            {(rooms.count() + storedRooms.count() === 0) && (
              <div className="selector--rooms-empty">
                <div className="icon">🙀</div>
                <div className="text">


@@ 190,7 222,7 @@ const RoomSelectorPage = ({history}) => {
              <input
                type="text"
                placeholder="da club"
                onInput={e => setRoom({...room, name: e.target.value})}
                onChange={e => setRoom({...room, name: e.target.value})}
                value={room.name}
              />
              <button type="button" onClick={createRoom}>create room</button>

M src/js/pages/room/data.js => src/js/pages/room/data.js +25 -13
@@ 29,28 29,40 @@ function* init({roomId, failureCallback}) {
  const connectedBuoy = yield select(BuoySelectors.connectedBuoy);
  if (!connectedBuoy) {
    yield put(BuoyActions.fetchBuoys());

    const {buoys} = yield take(BuoyActionTypes.FETCH_BUOYS_SUCCESS);

    // No buoys stored and there isn't a default to fallback to. Stop here.
    if (buoys.count() === 0 && !process.env.DEFAULT_INVITE) {
      return;
    }

    // No buoys stored but we have a fallback. Let's try to connect!
    if (buoys.count() === 0 && process.env.DEFAULT_INVITE) {
      // Do we have a default buoy to go with?
      yield put(BuoyActions.joinBuoy({
      yield put(BuoyActions.join({
        inviteCode: process.env.DEFAULT_INVITE,
      }));
      return;
    }

    yield put(BuoyActions.connect({buoy: buoys.get(0)}));
    const {type, message} = yield take([
      BuoyActionTypes.CONNECT_SUCCESS,
      BuoyActionTypes.CONNECT_FAILURE,
    ]);
      const {type, message} = yield take([
        BuoyActionTypes.JOIN_SUCCESS,
        BuoyActionTypes.JOIN_FAILURE,
      ]);

    if (type === BuoyActionTypes.CONNECT_FAILURE) {
      failureCallback({message});
      return;
      if (type === BuoyActionTypes.JOIN_FAILURE) {
        failureCallback({message});
        return;
      }
    } else {
      // There's a buoy stored, let's go ahead and try to connect to it
      yield put(BuoyActions.connect({buoy: buoys.get(0)}));
      const {type, message} = yield take([
        BuoyActionTypes.CONNECT_SUCCESS,
        BuoyActionTypes.CONNECT_FAILURE,
      ]);

      if (type === BuoyActionTypes.CONNECT_FAILURE) {
        failureCallback({message});
        return;
      }
    }
  }


M src/js/pages/room/index.js => src/js/pages/room/index.js +1 -10
@@ 678,14 678,12 @@ const Settings = withRouter(({open, onClose, history}) => {
  );
});

const RoomPage = ({history, match}) => {
const RoomPage = ({match}) => {
  ////
  // Hooks
  //
  const isConnecting = useSelector(BuoySelectors.isConnecting);
  const connectedBuoy = useSelector(BuoySelectors.connectedBuoy);
  const isFetchingBuoys = useSelector(BuoySelectors.isFetching);
  const buoys = useSelector(BuoySelectors.buoys);
  const muted = useSelector(JukeboxSelectors.mute);

  const dispatch = useDispatch();


@@ 710,13 708,6 @@ const RoomPage = ({history, match}) => {
    };
  }, []);

  useEffect(() => {
    // Redirect to index if we didn't find any buoys
    if (isFetchingBuoys === false && buoys.count() === 0) {
      history.push('/');
    }
  }, [isFetchingBuoys]);

  ////
  // Rendering
  //

M src/js/services/buoys.js => src/js/services/buoys.js +2 -3
@@ 245,9 245,8 @@ function* join({inviteCode}) {
}

function* connect({buoy}) {
  const token = JWT.decode(buoy.get('token'));
  try {
    yield call(openSocket, {url: token.u});
    yield call(openSocket, {url: buoy.get('url')});
  } catch (e) {
    yield put(Actions.connectFailure({message: e.message, buoy}));
    return;


@@ 258,7 257,7 @@ function* connect({buoy}) {
    params: {jwt: buoy.get('token')},
  });

  if (!resp.success) {
  if (resp.error) {
    yield put(Actions.connectFailure({...resp, buoy}));
  }


M src/js/services/library.js => src/js/services/library.js +1 -1
@@ 219,7 219,7 @@ function* init() {
  } catch (e) {
    if (e.status !== 404) {
      // eslint-disable-next-line no-console
      console.log('something went wront fetching the queue', e);
      console.log('something went wrong fetching the queue', e);
      return;
    }


M src/js/services/rooms.js => src/js/services/rooms.js +166 -3
@@ 14,9 14,28 @@ import {
import {Actions as ToasterActions} from './toaster';

////
// Helpers
//
const removeStoredRoom = async ({storedRoom}) => {
  const knownRooms = await db.get('rooms');
  const index = knownRooms.get('roomIds').indexOf(storedRoom.get('_id'));
  if (index === -1) {
    return;
  }

  const updatedIds = knownRooms.get('roomIds').splice(index, 1);
  await db.put(knownRooms.merge({roomIds: updatedIds}));
  await db.remove(storedRoom);
};

////
// Actions
//
export const ActionTypes = {
  FETCH_STORED: 'services/room/fetch_stored',
  FETCH_STORED_SUCCESS: 'services/room/fetch_stored_success',
  FETCH_STORED_FAILURE: 'services/room/fetch_stored_failure',

  FETCH_ALL: 'services/rooms/fetch_all',
  FETCH_ALL_SUCCESS: 'services/rooms/fetch_all_success',



@@ 32,7 51,7 @@ export const ActionTypes = {

  LEAVE_ROOM: 'services/rooms/leave_room',
  LEAVE_ROOM_SUCCESS: 'services/rooms/leave_room_success',
  LEAVE_ROOM_ERROR: 'services/rooms/leave_room_error',
  LEAVE_ROOM_FAILURE: 'services/rooms/leave_room_failure',

  SET_PEERS: 'services/rooms/set_peers',



@@ 73,6 92,10 @@ export const Actions = {
  fetchAll: createAction(ActionTypes.FETCH_ALL),
  fetchAllSuccess: createAction(ActionTypes.FETCH_ALL_SUCCESS, 'rooms'),

  fetchStored: createAction(ActionTypes.FETCH_STORED),
  fetchStoredFailure: createAction(ActionTypes.FETCH_STORED_FAILURE, 'message'),
  fetchStoredSuccess: createAction(ActionTypes.FETCH_STORED_SUCCESS, 'rooms'),

  setRooms: createAction(ActionTypes.SET_ROOMS, 'rooms'),

  createRoom: createAction(ActionTypes.CREATE_ROOM, 'name'),


@@ 127,6 150,7 @@ export const Actions = {
const initialState = Immutable.fromJS({
  loading: false,
  rooms: [],
  storedRooms: [],
  chat: {
    messages: [],
    sendingMessage: false,


@@ 138,6 162,10 @@ const initialState = Immutable.fromJS({

const callbacks = [
  {
    actionType: ActionTypes.FETCH_STORED_SUCCESS,
    callback: (s, {rooms}) => s.set('storedRooms', rooms),
  },
  {
    actionType: ActionTypes.FETCH_ALL,
    callback: s => s.set('loading', true),
  },


@@ 322,7 350,6 @@ const chatMessagesWithPeers = (s) => {
  return store(s).getIn(['chat', 'messages']).map((msg) => {
    const profile = profiles.get(msg.get('fromPeerId')) || new Immutable.Map();

    //console.log(profile.toJS());
    return msg
      .set('peer', new Immutable.Map({
        id: msg.get('fromPeerId'),


@@ 332,6 359,19 @@ const chatMessagesWithPeers = (s) => {
  });
};

const _storedRooms = (s) => {
  // Filter out rooms that already exist
  const existingRooms = store(s)
    .get('rooms')
    .reduce((set, r) => {
      return set.add(r.get('id'));
    }, new Immutable.Set());

  return store(s)
    .get('storedRooms')
    .filter(r => !existingRooms.has(r.get('_id').replace('rooms/', '')));
};

export const Selectors = {
  store,
  rooms,


@@ 344,6 384,7 @@ export const Selectors = {
  sendingMessage,
  chatMessages,
  chatMessagesWithPeers,
  storedRooms: _storedRooms,
};

////


@@ 368,7 409,91 @@ function* createRoom({name}) {
    return;
  }

  yield put(Actions.createRoomSuccess({room: Immutable.fromJS(resp)}));
  // Store our room in the local database so we can recreate it later if it
  // disappears
  const {adminToken} = resp;
  const room = Immutable.fromJS(resp.room);

  const storedRoom = Immutable.fromJS({
    _id: `rooms/${room.get('id')}`,
    name: room.get('name'),
    adminToken,
  });

  try {
    yield call(db.put, storedRoom);
  } catch (e) {
    yield put(Actions.createRoomFailure({message: e.message}));
    return;
  }

  // Add it to the list of stored rooms
  let knownRooms;
  try {
    knownRooms = yield call(db.get, 'rooms');
  } catch (e) {
    if (e.status !== 404) {
      // eslint-disable-next-line no-console
      console.log('something went wrong fetching the owned rooms list', e);
      yield put(Actions.createRoomFailure({message: e.message}));
      return;
    }

    knownRooms = Immutable.fromJS({
      _id: 'rooms',
      roomIds: [],
    });
  }

  knownRooms = knownRooms.merge({
    roomIds: knownRooms.get('roomIds').push(storedRoom.get('_id')),
  });

  try {
    yield call(db.put, knownRooms);
  } catch (e) {
    yield put(Actions.createRoomFailure({message: e.message}));
    return;
  }

  yield put(Actions.createRoomSuccess({room}));
}

function* restoreRoom({id}) {
  let storedRoom;
  try {
    storedRoom = yield call(db.get, `rooms/${id}`);
  } catch (e) {
    if (e.status !== 404) {
      yield put(Actions.joinRoomFailure({message: e.message}));
      return;
    }

    yield put(Actions.joinRoomFailure({message: 'stored room not found'}));
    return;
  }

  const resp = yield call(send, {
    name: 'restoreRoom',
    params: {adminToken: storedRoom.get('adminToken')},
  });

  if (resp.error && resp.message === 'bad admin id') {
    yield call(removeStoredRoom, {storedRoom});

    yield put(Actions.joinRoomFailure({
      message: 'you are not authenticated as the admin of this room',
    }));
    return;
  }

  if (resp.error) {
    yield put(Actions.joinRoomFailure({message: resp.message}));
    return;
  }

  const room = Immutable.fromJS(resp.room);
  yield put(Actions.joinRoomSuccess({room}));
}

function* joinRoom({id}) {


@@ 377,6 502,12 @@ function* joinRoom({id}) {
    params: {id},
  });

  if (resp.error && resp.message === 'room not found') {
    // Can we restore this room
    yield call(restoreRoom, {id});
    return;
  }

  if (resp.error) {
    yield put(Actions.joinRoomFailure({message: resp.message}));
    return;


@@ 531,7 662,39 @@ function* leaveRoom() {
  yield put(Actions.leaveRoomSuccess());
}

function* fetchStored() {
  let knownRooms;
  try {
    knownRooms = yield call(db.get, 'rooms');
  } catch (e) {
    if (e.status === 404) {
      yield put(Actions.fetchStoredSuccess({rooms: new Immutable.List()}));
      return;
    }

    yield put(Actions.fetchStoredFailure({message: e.message}));
    return;
  }

  const storedRooms = [];
  for (const roomId of knownRooms.get('roomIds').toJS()) {
    let storedRoom;
    try {
      storedRoom = yield call(db.get, roomId);
    } catch (e) {
      continue;
    }

    storedRooms.push(storedRoom);
  }

  yield put(Actions.fetchStoredSuccess({
    rooms: Immutable.fromJS(storedRooms),
  }));
}

export function* Saga() {
  yield takeEvery(ActionTypes.FETCH_STORED, fetchStored);
  yield takeEvery(ActionTypes.FETCH_ALL, fetchAll);
  yield takeEvery(ActionTypes.CREATE_ROOM, createRoom);
  yield takeEvery(ActionTypes.JOIN_ROOM, joinRoom);

M src/scss/pages/room-selector.scss => src/scss/pages/room-selector.scss +9 -0
@@ 86,6 86,15 @@
    padding: 25px;
    z-index: 1000;
  }

  h2 {
    margin-top: 15px;
    margin-bottom: 0px;
  }
}

.selector--actions {
  padding: 15px 0px;
}

.selector--connecting {