diff --git a/api/index.ts b/api/index.ts index 874a883..db3aa2b 100644 --- a/api/index.ts +++ b/api/index.ts @@ -28,7 +28,7 @@ async function start() { events, } = createServices(log); - events.emit(Events.StartupSync); + // events.emit(Events.StartupSync); api.listen(); } diff --git a/api/src/api.ts b/api/src/api.ts index 1779092..510fe6e 100644 --- a/api/src/api.ts +++ b/api/src/api.ts @@ -177,6 +177,8 @@ export default function createApi(log: Log, pg: Pg, discogs, offers, events) { ws.on('close', function onClose() { log.info('ws disconnected', { user: ctx.state.user }); }); + + await rpc.onConnection(); }); return { diff --git a/api/src/rpc.ts b/api/src/rpc.ts index 283a50b..6838c68 100644 --- a/api/src/rpc.ts +++ b/api/src/rpc.ts @@ -23,7 +23,6 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off } async function offerStatusSet(params: OfferStatusSetParams): Promise { - log.info(params); const { id, status } = params; assert(id, 'no id supplied'); assert(Status[status], 'unknown status'); @@ -32,6 +31,16 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off return [RpcMessageType.Offers, []] } + async function wantRm(params: WantRmParams): Promise { + const { id } = params; + assert(id, 'no id supplied'); + + await pg.wantRm(id); + + const wants = await pg.userWants(user); + return [RpcMessageType.Wants, wants]; + } + async function receive(message: any): Promise { const [msgType, params] = message; @@ -44,6 +53,8 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off return ping(); case RpcMessageType.OfferStatusSet: return offerStatusSet(params); + case RpcMessageType.WantRm: + return wantRm(params); default: throw new Error(`Message type ${msgType} is known but unhandled`); } @@ -54,6 +65,12 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off throw new Error(`Invalid parameters for message type ${msgType}: ${JSON.stringify(message)}`); } + async function onConnection() { + const wants = await pg.userWants(user); + const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants]; + send(wantsMsg); + } + function send(msg: RpcMessage) { ws.send(JSON.stringify(msg, (key, value) => typeof value === 'bigint' @@ -65,6 +82,7 @@ export default function createRpc(log: Log, pg: Pg, user: User, ws, discogs, off return { receive, send, + onConnection, } } @@ -78,17 +96,24 @@ export enum RpcMessageType { Error = 'Error', OfferStatusSet = 'OfferStatusSet', + + Wants = 'Wants', + WantRm = 'WantRm', } type ErrorMessage = [RpcMessageType.Error, string]; type PongMessage = [RpcMessageType.Pong, []]; type PingMessage = [RpcMessageType.Ping, []]; +type WantsMessage = [RpcMessageType.Wants, Want[]]; type OffersMessage = [RpcMessageType.Offers, Offer[]]; type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]]; export type RpcMessage = PingMessage | PongMessage | + + WantsMessage | + OffersMessage | OfferStatusSetMessage | ErrorMessage; @@ -98,3 +123,7 @@ type OfferStatusSetParams = { id: UUID, status: Status, }; + +type WantRmParams = { + id: UUID, +}; diff --git a/client/src/components/want.tsx b/client/src/components/want.tsx index fc2b982..671132b 100644 --- a/client/src/components/want.tsx +++ b/client/src/components/want.tsx @@ -1,9 +1,27 @@ import { h } from 'preact'; +import { useState, useCallback } from 'preact/hooks'; + +import { useSelector } from 'react-redux' +import { State } from './../store' + +import http from './../http'; function Want(want) { - console.log(want); const r = want.data; + const ws = useSelector((state: State) => state.ws); + const [confirm, setConfirm] = useState(false); + + function onClick() { + if (confirm) return ws.sendWantRm(want.id); + setTimeout(() => setConfirm(false), 2500); + return setConfirm(true); + }; + + const text = confirm + ? 'confirm' + : 'remove'; + return (
@@ -12,6 +30,7 @@ function Want(want) {
{r.formats[0].descriptions.join(' ')}
+
); } diff --git a/client/src/routes/list.tsx b/client/src/routes/list.tsx index cbf47bb..d050068 100644 --- a/client/src/routes/list.tsx +++ b/client/src/routes/list.tsx @@ -1,6 +1,9 @@ import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; +import { useSelector } from 'react-redux' +import { State } from './../store' + // import style from './style.less'; import http from './../http'; import Want from './../components/want'; @@ -12,17 +15,7 @@ interface Props { } const List = ({ user }: Props) => { - const [wants, setOffers] = useState([]); - - useEffect(() => { - async function getWants() { - const [error, wants] = await http.getWants(); - if (error) return console.error(error); - setOffers(wants); - } - getWants(); - }, []); - + const wants = useSelector((state: State) => state.wants); if (!wants.length) { return
loading...
; diff --git a/client/src/store.tsx b/client/src/store.tsx index c943ddf..87400f5 100644 --- a/client/src/store.tsx +++ b/client/src/store.tsx @@ -17,6 +17,14 @@ export const offersState = createSlice({ }, }); +export const wantsState = createSlice({ + name: 'wants', + initialState: [], + reducers: { + set: (state, action) => action.payload + }, +}); + export const wsState = createSlice({ name: 'ws', initialState: null, @@ -26,9 +34,10 @@ export const wsState = createSlice({ }); const reducer = combineReducers({ - user: userState.reducer, offers: offersState.reducer, + wants: wantsState.reducer, ws: wsState.reducer, + user: userState.reducer, }); const store = createStore( diff --git a/client/src/style/index.less b/client/src/style/index.less index 8a2779e..989c5e4 100644 --- a/client/src/style/index.less +++ b/client/src/style/index.less @@ -232,6 +232,15 @@ article { 1fr 1fr 1fr 1fr; article { + display: grid; + grid-template-areas: + "title img" + "remove remove"; + + button { + grid-area: remove + } + padding: var(--base-spacing); margin: var(--base-spacing-half); } diff --git a/client/src/ws.ts b/client/src/ws.ts index 3763ccf..91ef910 100644 --- a/client/src/ws.ts +++ b/client/src/ws.ts @@ -1,5 +1,7 @@ const SOCKET_URL = `${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`; +import { wantsState, wsState } from './store' + export default function createSocket(store) { let ws; @@ -28,6 +30,10 @@ export default function createSocket(store) { send(['OfferStatusSet', { id, status }]); } + function sendWantRm(id) { + send(['WantRm', { id }]); + } + let pongTimeout; function onPong() { pongTimeout = setTimeout(sendPing, 5000); @@ -36,8 +42,8 @@ export default function createSocket(store) { // ------------- // Incoming // ------------- - function onSourcesAvailable(clouds) { - // store.dispatch(http.LIBRARY_RECEIVE_POINTCLOUDS, clouds); + function onWants(wants) { + store.dispatch(wantsState.actions.set(wants)); } // ------------- @@ -46,7 +52,7 @@ export default function createSocket(store) { // when the server sends a reply it will have one of these message types // this object wraps the reply types to a function const handlers = { - SourcesAvailable: onSourcesAvailable, + Wants: onWants, Pong: onPong, Error: errHandler, @@ -109,5 +115,6 @@ export default function createSocket(store) { connect, sendOfferStatusSet, + sendWantRm, }; }