start implementing reports

This commit is contained in:
Joffrey JAFFEUX 2024-12-20 18:56:02 +01:00
parent bc80558e2d
commit 3af6a12a55
20 changed files with 418 additions and 42 deletions

View File

@ -24,6 +24,7 @@ module DiscourseRewind
.where(posts: { user_id: user.id }) .where(posts: { user_id: user.id })
.where(created_at: date) .where(created_at: date)
.group(:reaction_value) .group(:reaction_value)
.limit(5)
.count .count
end end

View File

@ -0,0 +1,61 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import concatClass from "discourse/helpers/concat-class";
const ROWS = 7;
const COLS = 53;
export default class ActivityCalendar extends Component {
get rowsArray() {
const data = this.args.report.data;
let rowsArray = [];
for (let r = 0; r < ROWS; r++) {
let rowData = [];
for (let c = 0; c < COLS; c++) {
const index = c * ROWS + r;
rowData.push(data[index] ? data[index] : "");
}
rowsArray.push(rowData);
}
return rowsArray;
}
@action
computeClass(count) {
if (!count) {
return "-empty";
} else if (count < 10) {
return "-low";
} else if (count < 20) {
return "-medium";
} else {
return "-high";
}
}
<template>
<div class="rewind-report-page -activity-calendar">
<div class="rewind-card">
<table class="rewind-calendar">
<tbody>
{{#each this.rowsArray as |row|}}
<tr>
{{#each row as |cell|}}
<td
data-date={{cell.date}}
class={{concatClass
"rewind-calendar-cell"
(this.computeClass cell.post_count)
}}
></td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class BestPosts extends Component {
<template>
<div class="rewind-report-page">
Best posts
</div>
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class BestTopics extends Component {
<template>
<div class="rewind-report-page">
BestTopics
</div>
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class FavoriteCategories extends Component {
<template>
<div class="rewind-report-page">
FavoriteCategories
</div>
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class FavoriteTags extends Component {
<template>
<div class="rewind-report-page">
FavoriteTags
</div>
</template>
}

View File

@ -0,0 +1,13 @@
import Component from "@glimmer/component";
export default class FBFF extends Component {
<template>
<div class="rewind-report-page">
FBFF
</div>
<div class="rewind-report-page">
page 2
</div>
</template>
}

View File

@ -1,13 +1,60 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { concat } from "@ember/helper";
import { action } from "@ember/object";
import { htmlSafe } from "@ember/template";
import replaceEmoji from "discourse/helpers/replace-emoji";
export default class Reactions extends Component { export default class Reactions extends Component {
@action
cleanEmoji(emojiName) {
return emojiName.replaceAll(/_/g, " ");
}
get totalPostUsedReactions() {
return Object.values(
this.args.report.data.post_used_reactions ?? {}
).reduce((acc, count) => acc + count, 0);
}
@action
computePercentage(count) {
return `${((count / this.totalPostUsedReactions) * 100).toFixed(2)}%`;
}
@action
computePercentageStyle(count) {
return htmlSafe(`width: ${this.computePercentage(count)}`);
}
<template> <template>
<div class="rewind-report-page"> <div class="rewind-report-page -post-received-reactions">
Let's take a look at the reactions you've used this year! {{#each-in @report.data.post_received_reactions as |emojiName count|}}
<div class="rewind-card">
<span>{{replaceEmoji (concat ":" emojiName ":")}}</span>
<span>{{this.cleanEmoji emojiName}}</span>
<span>{{count}} times</span>
</div>
{{/each-in}}
</div> </div>
<div class="rewind-report-page"> <div class="rewind-report-page -post-used-reactions">
page 2 <div class="rewind-card">
<div class="rewind-reactions-chart">
{{#each-in @report.data.post_used_reactions as |emojiName count|}}
<div class="rewind-reactions-row">
<span>{{replaceEmoji (concat ":" emojiName ":")}}</span>
<span>{{this.computePercentage count}}</span>
<div
class="rewind-reactions-bar"
style={{this.computePercentageStyle count}}
></div>
</div>
{{/each-in}}
<span class="rewind-total-reactions">Total number of reactions:
{{this.totalPostUsedReactions}}</span>
</div>
</div>
</div> </div>
</template> </template>
} }

View File

@ -0,0 +1,13 @@
import Component from "@glimmer/component";
export default class ReadingTime extends Component {
<template>
<div class="rewind-report-page">
Reading time
</div>
<div class="rewind-report-page">
page 2
</div>
</template>
}

View File

@ -0,0 +1,9 @@
import Component from "@glimmer/component";
export default class WordCloud extends Component {
<template>
<div class="rewind-report-page">
Word cloud
</div>
</template>
}

View File

@ -1,25 +1,44 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert"; import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { schedule } from "@ember/runloop";
import { eq } from "truth-helpers"; import { eq } from "truth-helpers";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import concatClass from "discourse/helpers/concat-class"; import concatClass from "discourse/helpers/concat-class";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import ActivityCalendar from "discourse/plugins/discourse-rewind/discourse/components/reports/activity-calendar";
import BestPosts from "discourse/plugins/discourse-rewind/discourse/components/reports/best-posts";
import BestTopics from "discourse/plugins/discourse-rewind/discourse/components/reports/best-topics";
import FavoriteCategories from "discourse/plugins/discourse-rewind/discourse/components/reports/favorite-categories";
import FavoriteTags from "discourse/plugins/discourse-rewind/discourse/components/reports/favorite-tags";
import FBFF from "discourse/plugins/discourse-rewind/discourse/components/reports/fbff";
import Reactions from "discourse/plugins/discourse-rewind/discourse/components/reports/reactions"; import Reactions from "discourse/plugins/discourse-rewind/discourse/components/reports/reactions";
import ReadingTime from "discourse/plugins/discourse-rewind/discourse/components/reports/reading-time";
import WordCloud from "discourse/plugins/discourse-rewind/discourse/components/reports/word-cloud";
export default class Rewind extends Component { export default class Rewind extends Component {
@tracked rewind = []; @tracked rewind = [];
@tracked fullScreen = true; @tracked fullScreen = true;
@tracked loadingRewind = false;
@action
registerScrollWrapper(element) {
this.scrollWrapper = element;
}
@action @action
async loadRewind() { async loadRewind() {
try { try {
this.loadingRewind = true;
this.rewind = await ajax("/rewinds"); this.rewind = await ajax("/rewinds");
} catch (e) { } catch (e) {
popupAjaxError(e); popupAjaxError(e);
} finally {
this.loadingRewind = false;
} }
} }
@ -28,30 +47,86 @@ export default class Rewind extends Component {
this.fullScreen = !this.fullScreen; this.fullScreen = !this.fullScreen;
} }
reportComponentForIdentifier(identifier) { @action
if (identifier === "reactions") { handleEscape(event) {
return Reactions; if (this.fullScreen && event.key === "Escape") {
this.fullScreen = false;
} }
} }
<template> <template>
<div <div
class={{concatClass "rewind" (if this.fullScreen "-fullscreen")}} class={{concatClass
"rewind-container"
(if this.fullScreen "-fullscreen")
}}
{{didInsert this.loadRewind}} {{didInsert this.loadRewind}}
{{on "keydown" this.handleEscape}}
tabindex="0"
> >
<div class="rewind">
{{#if this.loadingRewind}}
<div class="rewind-loader">
<div class="spinner small"></div>
<div class="rewind-loader__text">Crunching your data...</div>
</div>
{{else}}
<DButton <DButton
class="rewind__exit-fullscreen-btn" class="rewind__exit-fullscreen-btn"
@icon={{if this.fullScreen "discourse-compress" "discourse-expand"}} @icon={{if this.fullScreen "discourse-compress" "discourse-expand"}}
title="Toggle fullscreen"
@action={{this.toggleFullScreen}} @action={{this.toggleFullScreen}}
/> />
<div
class="rewind__scroll-wrapper"
{{didInsert this.registerScrollWrapper}}
>
{{#each this.rewind as |report|}} {{#each this.rewind as |report|}}
{{#if (eq report.identifier "reactions")}} {{log report.identifier}}
<div class="rewind-report"> <div class="rewind-report">
{{#if (eq report.identifier "reactions")}}
<Reactions @report={{report}} /> <Reactions @report={{report}} />
</div> {{else if (eq report.identifier "activity-calendar")}}
<ActivityCalendar @report={{report}} />
{{/if}} {{/if}}
{{!-- {{else if (eq report.identifier "fbff")}}
<FBFF @report={{report}} />
{{else if (eq report.identifier "word-cloud")}}
<WordCloud @report={{report}} />
{{else if (eq report.identifier "activity-calendar")}}
<ActivityCalendar @report={{report}} />
{{else if (eq report.identifier "best-posts")}}
<BestPosts @report={{report}} />
{{else if (eq report.identifier "best-topics")}}
<BestTopics @report={{report}} />
{{else if (eq report.identifier "favorite-tags")}}
<FavoriteTags @report={{report}} />
{{else if (eq report.identifier "favorite-categories")}}
<FavoriteCategories @report={{report}} />
{{else if (eq report.identifier "reading-time")}}
<ReadingTime @report={{report}} />
{{/if}} --}}
</div>
{{/each}} {{/each}}
</div> </div>
{{#if this.showPrev}}
<DButton
class="rewind__prev-btn"
@icon="chevron-left"
@action={{this.prev}}
/>
{{/if}}
{{#if this.showNext}}
<DButton
class="rewind__next-btn"
@icon="chevron-right"
@action={{this.next}}
/>
{{/if}}
{{/if}}
</div>
</div>
</template> </template>
} }

View File

@ -0,0 +1,25 @@
.rewind-report-page.-activity-calendar {
.rewind-calendar {
tr {
border: none;
}
}
.rewind-calendar-cell {
height: 10px;
width: 10px;
&.-empty {
background: var(--tertiary-very-low);
}
&.-low {
background: var(--tertiary-low);
}
&.-medium {
background: var(--tertiary-medium);
}
&.-high {
background: var(--tertiary-hig);
}
}
}

View File

@ -1,2 +1,11 @@
.rewind-card { .rewind-card {
box-shadow: 0 0 0 1px var(--primary-300), 0 0 0 4px var(--primary-100);
border-radius: calc(var(--d-border-radius) / 2);
display: flex;
flex-direction: column;
text-align: center;
padding: 1em;
box-sizing: border-box;
align-items: center;
justify-content: center;
} }

View File

@ -1,3 +1,6 @@
@import "rewind"; @import "rewind";
@import "report"; @import "report";
@import "card"; @import "card";
@import "post-received-reactions";
@import "post-used-reactions";
@import "activity-calendar";

View File

@ -0,0 +1,16 @@
.rewind-report-page.-post-received-reactions {
.rewind-reactions-chart {
display: flex;
gap: 1em;
}
.rewind-card {
width: 177px;
height: 190px;
.emoji {
width: 72px;
height: 72px;
}
}
}

View File

@ -0,0 +1,20 @@
.rewind-report-page.-post-used-reactions {
display: flex;
gap: 1em;
.rewind-reactions-chart {
display: flex;
flex-direction: column;
gap: 1em;
}
.rewind-reactions-row {
display: flex;
gap: 1em;
}
.rewind-reactions-bar {
background: var(--tertiary);
height: 50px;
}
}

View File

@ -9,6 +9,4 @@
box-sizing: border-box; box-sizing: border-box;
font-weight: 700; font-weight: 700;
font-size: var(--font-up-2); font-size: var(--font-up-2);
height: 100cqh;
width: 100cqw;
} }

View File

@ -1,14 +1,11 @@
.rewind { .rewind-container {
width: 280px;
height: 400px;
background-color: var(--secondary);
border-radius: var(--d-border-radius); border-radius: var(--d-border-radius);
overflow-y: auto;
overflow-x: hidden;
border: 1px solid var(--primary-very-low);
box-sizing: border-box; box-sizing: border-box;
position: relative; position: relative;
container-type: size; display: flex;
align-items: center;
justify-content: center;
padding: 2em 4em;
&.-fullscreen { &.-fullscreen {
top: 0; top: 0;
@ -16,14 +13,69 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 9999; z-index: 9999;
width: 100vw; width: calc(100vw - (100vw - 100%) + 2px);
height: 100vh; height: 100vh;
position: fixed; position: fixed;
background: rgba(255, 255, 255, 0.5);
border-radius: 16px;
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
backdrop-filter: blur(4.9px);
-webkit-backdrop-filter: blur(4.9px);
} }
} }
.rewind__exit-fullscreen-btn { .rewind {
position: sticky; width: 100vw;
top: 5px; height: 100vh;
left: calc(100% - 45px); max-height: 100%;
max-width: 100%;
background-color: var(--secondary);
box-shadow: 0 0 0 1px var(--primary-300), 0 0 0 4px var(--primary-100);
border-radius: calc(var(--d-border-radius) / 2);
container-type: size;
position: relative;
}
.rewind__scroll-wrapper {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
width: 100%;
position: relative;
}
.rewind__exit-fullscreen-btn {
position: absolute;
top: 5px;
right: 20px;
z-index: 1;
}
.rewind__prev-btn {
position: absolute;
bottom: 5px;
left: 5px;
z-index: 1;
}
.rewind__next-btn {
position: absolute;
bottom: 5px;
right: 5px;
z-index: 1;
}
.rewind-loader {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100cqh;
gap: 1em;
box-sizing: border-box;
&__text {
font-weight: 700;
font-size: var(--font-up-2);
}
} }

View File

@ -1,4 +1,2 @@
.report-page { .report-page {
width: 100vw;
height: 100vh;
} }

View File

@ -1,7 +1,7 @@
.rewind { .rewind-container {
background-color: var(--secondary); padding: 1em;
border-radius: var(--d-border-radius); box-sizing: border-box;
overflow-y: auto; }
overflow-x: hidden;
border: 1px solid var(--primary-very-low); .rewind {
} }