From dbb97f8317e8c3c71e21300a510346f11d64d25d Mon Sep 17 00:00:00 2001 From: Kaede Fujisaki Date: Fri, 23 Jul 2021 11:14:40 +0900 Subject: [PATCH] Implement random search --- client/src/omote/layers/Index.ts | 27 +++++------ server/src/Server.ts | 9 +++- .../omote/RandomSelectionController.ts | 45 +++++++++++++++++++ .../controller/ura/MomentListController.ts | 2 +- server/src/repo/Repo.ts | 23 ++++++++-- server/src/shelf/Shelf.ts | 7 ++- 6 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 server/src/controller/omote/RandomSelectionController.ts diff --git a/client/src/omote/layers/Index.ts b/client/src/omote/layers/Index.ts index 52d4cf7..d9d1e7a 100644 --- a/client/src/omote/layers/Index.ts +++ b/client/src/omote/layers/Index.ts @@ -75,10 +75,8 @@ export default class Index extends Layer { this.fixTooltipPosition_(); } } - /** - * @private - */ - fixTooltipPosition_() { + + private fixTooltipPosition_() { const tooltip = this.tooltip_; const m = this.selected_; if(m == null) { @@ -93,8 +91,7 @@ export default class Index extends Layer { } } - /** @override */ - onAttached() { + override onAttached() { this.world.cursor = false; this.world.canvas.addEventListener('wheel', this.wheelEventListener_, false); this.world.canvas.addEventListener('mousemove', this.mouseMoveListener_, false); @@ -105,8 +102,8 @@ export default class Index extends Layer { this.mouseX_ = NaN; this.mouseY_ = NaN; } - /** @override */ - onDetached() { + + override onDetached() { this.mouseX_ = NaN; this.mouseY_ = NaN; this.world.cursor = false; @@ -115,7 +112,7 @@ export default class Index extends Layer { this.world.canvas.removeEventListener('mouseup', this.mouseUpListener_, false); } - onMouseMove_(ev: MouseEvent) { + private onMouseMove_(ev: MouseEvent) { ev.preventDefault(); const canvas = this.world.canvas; const hw = canvas.width/2; @@ -125,7 +122,7 @@ export default class Index extends Layer { this.mouseY_ = -(ev.clientY - hh) / hh; } - onMouseUp_(ev: MouseEvent) { + private onMouseUp_(ev: MouseEvent) { ev.preventDefault(); const m = this.selected_; if(!m) { @@ -135,11 +132,7 @@ export default class Index extends Layer { this.world.pushLayer(new Page(this.world, m.path, content)); } - /** - * - * @param {WheelEvent} event - */ - onWheelEvent_(event: WheelEvent) { + private onWheelEvent_(event: WheelEvent) { event.preventDefault(); const world = this.world; let dx = event.deltaX; @@ -166,7 +159,7 @@ export default class Index extends Layer { } } - onLoadMoments_(moments: protocol.Moment.Search.Response[]) { + private onLoadMoments_(moments: protocol.Moment.Search.Response[]) { const world = this.world; const models: Moment[] = []; for(let m of moments) { @@ -179,7 +172,7 @@ export default class Index extends Layer { } fetch(size: number) { - fetch(`/moment/search?size=${size}`) + fetch(`/moments/random?size=${size}`) .then(resp => resp.json()) .then(this.onLoadMoments_.bind(this)); } diff --git a/server/src/Server.ts b/server/src/Server.ts index a242c23..51303ce 100644 --- a/server/src/Server.ts +++ b/server/src/Server.ts @@ -13,6 +13,7 @@ import Shelf from './shelf/Shelf'; // Omote Controllers import OmoteIndexController from './controller/omote/IndexController'; import MomentController from './controller/omote/MomentController'; +import RandomSelectionController, {RandomSelectionControllerInterface} from './controller/omote/RandomSelectionController'; // Ura Controllers import UraIndexController from './controller/ura/IndexController'; import MomentListController, {MomentListControllerInterface} from './controller/ura/MomentListController'; @@ -114,6 +115,12 @@ class Server { }, }); } + { // (omote)/moments/random + const omote = await RandomSelectionController.create(this.shelf); + this.omote.get('/moments/random', async (req, reply) => { + await omote.handle(req, reply); + }); + } { // (both)/moment/year/month/day/HH:mm:ss/ const c = await MomentBodyController.create(this.shelf); this.both.get('/moment/:year(^[0-9]{4}$)/:month(^[0-9]{2}$)/:day(^[0-9]{2}$)/:time(^[0-9]{2}:[0-9]{2}:[0-9]{2}$)/', async (req, reply) => { @@ -165,7 +172,7 @@ class Server { await ura.handle(req, reply); }); } - { // file uploads + { // (ura)/upload const ura = await UploadController.create(this.shelf); this.ura.post('/upload', async(req, reply) => { await ura.handle(req, reply); diff --git a/server/src/controller/omote/RandomSelectionController.ts b/server/src/controller/omote/RandomSelectionController.ts new file mode 100644 index 0000000..9c1ec04 --- /dev/null +++ b/server/src/controller/omote/RandomSelectionController.ts @@ -0,0 +1,45 @@ +import {FastifyReply, FastifyRequest, RequestGenericInterface} from 'fastify'; + +import * as protocol from 'lib/protocol'; + +import Shelf from '../../shelf/Shelf'; +import {formatMomentPath, formatMomentTime, MomentSummary} from '../../shelf/Moment'; + +export interface RandomSelectionControllerInterface extends RequestGenericInterface { + Querystring: { + size: string, + } +} + +export default class RandomSelectionController { + private readonly shelf: Shelf; + private constructor(shelf: Shelf) { + this.shelf = shelf; + } + static async create(shelf: Shelf): Promise { + return new RandomSelectionController(shelf); + } + async handle(req: FastifyRequest, reply: FastifyReply) { + const size = parseInt(req.query.size, 10); + const moments: MomentSummary[] = await this.shelf.findMomentSummariesByRandom(size); + const results: protocol.Moment.Search.Response[] = []; + for (const m of moments) { + if(m.iconID === undefined) { + continue; + } + const p = formatMomentPath(m.timestamp!!); + results.push({ + angle: 0.0, + date: formatMomentTime(m.timestamp!!), + title: m.title, + path: p, + imageURL: `/entity/${m.iconID}/icon`, + bodyURL: `/moment${p}`, + }); + } + reply + .type('application/json') + .code(200) + .send(results); + } +} diff --git a/server/src/controller/ura/MomentListController.ts b/server/src/controller/ura/MomentListController.ts index bf2b5ca..724845a 100644 --- a/server/src/controller/ura/MomentListController.ts +++ b/server/src/controller/ura/MomentListController.ts @@ -24,7 +24,7 @@ export default class MomentListController { } async handle(req: FastifyRequest, reply: FastifyReply) { const year = parseInt(req.params.year, 10) || dayjs().year(); - const moments = (await this.shelf.findMomentsInYear(year)).map((it) => { + const moments = (await this.shelf.findMomentSummariesInYear(year)).map((it) => { return { iconID: it.iconID || '', path: formatMomentPath(it.timestamp!!), diff --git a/server/src/repo/Repo.ts b/server/src/repo/Repo.ts index 01a6589..60ad8b5 100644 --- a/server/src/repo/Repo.ts +++ b/server/src/repo/Repo.ts @@ -165,7 +165,7 @@ where "timestamp" = $6; ]); } - async findMomentsInYear(year: number): Promise { + async findMomentSummariesInYear(year: number): Promise { // language=PostgreSQL const q=` select timestamp, title, icon_id from moments @@ -180,6 +180,21 @@ where return moments; } + async findMomentSummariesByRandom(size: number): Promise { + // https://stackoverflow.com/a/41337788 + // language=PostgreSQL + const q=` +select timestamp, title, icon_id from moments +TABLESAMPLE SYSTEM_ROWS($1); +`; + const moments: MomentSummary[] = []; + const rows = await this.pool.query(q, [size]); + for await (const row of rows) { + moments.push(decodeMomentSummary(row)); + } + return moments; + } + async findMoment(timestamp: dayjs.Dayjs): Promise { // language=PostgreSQL const q=` @@ -209,15 +224,15 @@ function decodeMoment(row: ResultRow): Moment { title: row.get('title') as string || '', author: row.get('author') as string || '', text: row.get('text') as string || '', - iconID: row.get('icon_id') as (string | undefined), + iconID: row.get('icon_id') as (string | null) || undefined, }; } function decodeMomentSummary(row: ResultRow): MomentSummary { return { timestamp: dayjs(row.get('timestamp') as Date), - title: row.get('title') as string, - iconID: row.get('icon_id') as (string | undefined), + title: row.get('title') as string || '', + iconID: row.get('icon_id') as (string | null) || undefined, }; } diff --git a/server/src/shelf/Shelf.ts b/server/src/shelf/Shelf.ts index 6b16239..2cbdbda 100644 --- a/server/src/shelf/Shelf.ts +++ b/server/src/shelf/Shelf.ts @@ -126,12 +126,15 @@ class Shelf { } return m; } - async findMomentsInYear(year: number): Promise { - return await this.repo.findMomentsInYear(year); + async findMomentSummariesInYear(year: number): Promise { + return await this.repo.findMomentSummariesInYear(year); } async findMoment(timestamp: dayjs.Dayjs): Promise { return await this.repo.findMoment(timestamp); } + async findMomentSummariesByRandom(size: number): Promise { + return await this.repo.findMomentSummariesByRandom(size); + } async deleteMoment(timestamp: dayjs.Dayjs): Promise { return await this.repo.deleteMoment(timestamp); }