@fluid-topics/ft-reader-feedback 0.3.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ Feedback forms for a map and its topics in an integrated reader
2
+
3
+ ## Install
4
+
5
+ ```shell
6
+ npm install @fluid-topics/ft-reader-feedback
7
+ yarn add @fluid-topics/ft-reader-feedback
8
+ ```
9
+
10
+ ## Usage
11
+
12
+ See [@fluid-topics/ft-reader-context](https://www.npmjs.com/package/@fluid-topics/ft-reader-context) for more
13
+ information about integrated readers for Fluid Topics.
14
+
15
+ ```typescript
16
+ import {html} from "lit"
17
+ import "@fluid-topics/ft-reader-context"
18
+ import "@fluid-topics/ft-reader-topic-context"
19
+ import "@fluid-topics/ft-reader-feedback"
20
+
21
+ function render() {
22
+ return html`
23
+ <ft-reader-context baseUrl="https://my.fluidtopics.com" mapId="<Map ID>">
24
+ <ft-reader-feedback></ft-reader-feedback>
25
+
26
+ <ft-reader-topic-context tocId="<Topic ToC ID>">
27
+ <ft-reader-feedback></ft-reader-feedback>
28
+ </ft-reader-topic-context>
29
+ </ft-reader-context>
30
+ `
31
+ }
32
+ ```
@@ -0,0 +1,6 @@
1
+ export declare const FtReaderFeedbackCssVariables: {
2
+ feedbackInputMinWidth: import("@fluid-topics/ft-wc-utils").FtCssVariable;
3
+ ratedDateColor: import("@fluid-topics/ft-wc-utils").FtCssVariable;
4
+ };
5
+ export declare const styles: import("lit").CSSResult;
6
+ //# sourceMappingURL=ft-reader-feedback.css.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { css } from "lit";
2
+ import { designSystemVariables, FtCssVariableFactory } from "@fluid-topics/ft-wc-utils";
3
+ export const FtReaderFeedbackCssVariables = {
4
+ feedbackInputMinWidth: FtCssVariableFactory.create("--ft-reader-feedback-input-min-width", "SIZE", "300px"),
5
+ ratedDateColor: FtCssVariableFactory.external(designSystemVariables.colorOnSurfaceMedium, "Design system")
6
+ };
7
+ // language=CSS
8
+ export const styles = css `
9
+ .ft-reader-feedback {
10
+ box-sizing: border-box;
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 16px;
14
+ }
15
+
16
+ .ft-reader-feedback--placeholder {
17
+ display: flex;
18
+ flex-direction: row;
19
+ gap: 8px;
20
+ padding: 8px;
21
+ border: 1px dashed #e1e1e1;
22
+ color: #444444;
23
+ background: #f1f1f1;
24
+ border-radius: 4px;
25
+ align-items: center;
26
+ }
27
+
28
+ .ft-reader-feedback--placeholder ft-typography {
29
+ flex-grow: 1;
30
+ }
31
+
32
+ .ft-reader-feedback--placeholder ft-tooltip {
33
+ flex-grow: 0;
34
+ flex-shrink: 0;
35
+ }
36
+
37
+ .ft-reader-feedback--stars {
38
+ display: flex;
39
+ flex-direction: row;
40
+ align-items: center;
41
+ }
42
+
43
+
44
+ .ft-reader-feedback--rating, .ft-reader-feedback--feedback {
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+
49
+ .ft-reader-feedback--rating .ft-reader-feedback--section-title {
50
+ margin-bottom: 8px;
51
+ }
52
+
53
+ .ft-reader-feedback--feedback {
54
+ gap: 8px;
55
+ }
56
+
57
+ .ft-reader-feedback--rating-date {
58
+ color: ${FtReaderFeedbackCssVariables.ratedDateColor};
59
+ }
60
+
61
+ .ft-reader-feedback--feedback ft-text-area {
62
+ min-width: ${FtReaderFeedbackCssVariables.feedbackInputMinWidth};
63
+ }
64
+
65
+ .ft-reader-feedback--send-feedback, .ft-reader-feedback--feedback-link {
66
+ align-self: flex-end;
67
+ }
68
+ `;
69
+ //# sourceMappingURL=ft-reader-feedback.css.js.map
@@ -0,0 +1,45 @@
1
+ import { nothing } from "lit";
2
+ import { ElementDefinitionsMap, FtLitElement, ParametrizedLabelResolver } from "@fluid-topics/ft-wc-utils";
3
+ import { FtReaderFeedbackProperties } from "./ft-reader-feedback.properties";
4
+ import { FtTopicRatingSummary } from "@fluid-topics/ft-reader-context/build/store/model";
5
+ import type { FtPublicationRatingSummary, FtUserProfile } from "@fluid-topics/public-api";
6
+ import { FtTextArea } from "@fluid-topics/ft-text-area";
7
+ import { FtIconVariants } from "@fluid-topics/ft-icon/build/ft-icon.properties";
8
+ import { FtReaderFeedbackLabels } from "./utils/labels";
9
+ export declare class FtReaderFeedback extends FtLitElement implements FtReaderFeedbackProperties {
10
+ static elementDefinitions: ElementDefinitionsMap;
11
+ static styles: import("lit").CSSResult;
12
+ editorMode: boolean;
13
+ hideTitle: boolean;
14
+ ratingAllowed: boolean;
15
+ labelResolver: ParametrizedLabelResolver<FtReaderFeedbackLabels>;
16
+ ratingSummary?: FtPublicationRatingSummary | FtTopicRatingSummary;
17
+ user?: FtUserProfile;
18
+ feedbackInput: FtTextArea;
19
+ feedbackMessage: string;
20
+ iconVariant: FtIconVariants;
21
+ emptyStarIcon?: string;
22
+ filledStarIcon?: string;
23
+ emptyLikeIcon?: string;
24
+ filledLikeIcon?: string;
25
+ emptyDislikeIcon?: string;
26
+ filledDislikeIcon?: string;
27
+ feedbackMailLinkBuilder: (feedbackMessage: string) => string | undefined;
28
+ get canAccessFeedback(): boolean | undefined;
29
+ get canAccessRating(): boolean | undefined;
30
+ get userHasRatingRole(): boolean | undefined;
31
+ get configuredRatingType(): import("@fluid-topics/public-api").FtRatingType | "NO_RATING";
32
+ get ratingType(): import("@fluid-topics/public-api").FtRatingType | "NO_RATING";
33
+ get ratingValue(): number;
34
+ get ratingDate(): Date | undefined;
35
+ protected render(): typeof nothing | import("lit-html").TemplateResult<1>;
36
+ private renderRatingSection;
37
+ private renderRatingDate;
38
+ private renderRating;
39
+ private rate;
40
+ private renderFeedbackSection;
41
+ private sendFeedback;
42
+ private placeholderInEditor;
43
+ clearFeedbackInput(): void;
44
+ }
45
+ //# sourceMappingURL=ft-reader-feedback.d.ts.map
@@ -0,0 +1,280 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html, nothing } from "lit";
8
+ import { property, query, state } from "lit/decorators.js";
9
+ import { FtLitElement, parseDate, } from "@fluid-topics/ft-wc-utils";
10
+ import { styles } from "./ft-reader-feedback.css";
11
+ import { FtTypography } from "@fluid-topics/ft-typography";
12
+ import { FtIcon } from "@fluid-topics/ft-icon";
13
+ import { FtTooltip } from "@fluid-topics/ft-tooltip";
14
+ import { FtSwitch, FtSwitchOption } from "@fluid-topics/ft-switch";
15
+ import { FtTextArea } from "@fluid-topics/ft-text-area";
16
+ import { repeat } from "lit/directives/repeat.js";
17
+ import { FtButton } from "@fluid-topics/ft-button";
18
+ import { FtIconVariants } from "@fluid-topics/ft-icon/build/ft-icon.properties";
19
+ export class FtReaderFeedback extends FtLitElement {
20
+ constructor() {
21
+ super(...arguments);
22
+ this.editorMode = false;
23
+ this.hideTitle = false;
24
+ this.ratingAllowed = false;
25
+ this.feedbackMessage = "";
26
+ this.iconVariant = FtIconVariants.fluid_topics;
27
+ this.emptyStarIcon = "STAR";
28
+ this.filledStarIcon = "STAR_PLAIN";
29
+ this.emptyLikeIcon = "THUMBS_UP";
30
+ this.filledLikeIcon = "THUMBS_UP_PLAIN";
31
+ this.emptyDislikeIcon = "THUMBS_DOWN";
32
+ this.filledDislikeIcon = "THUMBS_DOWN_PLAIN";
33
+ }
34
+ get canAccessFeedback() {
35
+ return this.user && this.user.roles.includes("FEEDBACK_USER");
36
+ }
37
+ get canAccessRating() {
38
+ return this.userHasRatingRole && this.configuredRatingType !== "NO_RATING";
39
+ }
40
+ get userHasRatingRole() {
41
+ return this.user && this.user.roles.includes("RATING_USER");
42
+ }
43
+ get configuredRatingType() {
44
+ var _a, _b;
45
+ return (_b = (this.ratingAllowed ? (_a = this.ratingSummary) === null || _a === void 0 ? void 0 : _a.type : "NO_RATING")) !== null && _b !== void 0 ? _b : "NO_RATING";
46
+ }
47
+ get ratingType() {
48
+ var _a, _b, _c;
49
+ return (_c = (_b = (_a = this.ratingSummary) === null || _a === void 0 ? void 0 : _a.rating) === null || _b === void 0 ? void 0 : _b.type) !== null && _c !== void 0 ? _c : "NO_RATING";
50
+ }
51
+ get ratingValue() {
52
+ var _a, _b, _c;
53
+ return (_c = (_b = (_a = this.ratingSummary) === null || _a === void 0 ? void 0 : _a.rating) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : -1;
54
+ }
55
+ get ratingDate() {
56
+ var _a, _b;
57
+ const date = (_b = (_a = this.ratingSummary) === null || _a === void 0 ? void 0 : _a.rating) === null || _b === void 0 ? void 0 : _b.date;
58
+ return typeof date === "string" ? parseDate(date) : date;
59
+ }
60
+ render() {
61
+ if (!this.editorMode && !this.canAccessFeedback && !this.canAccessRating) {
62
+ return nothing;
63
+ }
64
+ return html `
65
+ <div class="ft-reader-feedback">
66
+ ${this.hideTitle ? nothing : html `
67
+ <ft-typography variant="title" part="main-title">
68
+ ${this.labelResolver.resolve("feedbackMainTitle")}
69
+ </ft-typography>
70
+ `}
71
+ ${this.renderRatingSection()}
72
+ ${this.renderFeedbackSection()}
73
+ </div>
74
+ `;
75
+ }
76
+ renderRatingSection() {
77
+ if (this.canAccessRating) {
78
+ return html `
79
+ <div class="ft-reader-feedback--rating" part="rating-container">
80
+ <ft-typography variant="title-dense" part="section-title" class="ft-reader-feedback--section-title">
81
+ ${this.ratingSummary.type === "DICHOTOMOUS" ? this.labelResolver.resolve("dichotomousRatingSectionTitle") : this.labelResolver.resolve("ratingSectionTitle")}
82
+ </ft-typography>
83
+ ${this.renderRating()}
84
+ <ft-typography variant="caption" class="ft-reader-feedback--rating-date" part="rating-date">
85
+ ${this.renderRatingDate()}
86
+ </ft-typography>
87
+ </div>
88
+ `;
89
+ }
90
+ return this.placeholderInEditor("Rating unavailable: " + (this.userHasRatingRole ? "no rating configured for this content" : "missing role RATING_USER"));
91
+ }
92
+ renderRatingDate() {
93
+ const ratingDate = this.ratingDate;
94
+ if (ratingDate == null) {
95
+ return this.labelResolver.resolve("notRated");
96
+ }
97
+ if (ratingDate.getTime() > (new Date().getTime() - 60000)) { // Less than 1 min ago
98
+ return this.labelResolver.resolve("ratedNow");
99
+ }
100
+ const today = new Date().toLocaleDateString();
101
+ const formattedDate = ratingDate.toLocaleDateString();
102
+ if (today === formattedDate) {
103
+ return this.labelResolver.resolve("ratedToday");
104
+ }
105
+ return this.labelResolver.resolve("ratedOn", ratingDate);
106
+ }
107
+ renderRating() {
108
+ switch (this.configuredRatingType) {
109
+ case "STARS":
110
+ return html `
111
+ <div class="ft-reader-feedback--stars" part="rating rating-stars">
112
+ ${repeat(new Array(5), (_, index) => index, (_, index) => {
113
+ let active = this.ratingType === "STARS" && this.ratingValue > index;
114
+ const ratingValue = index + 1;
115
+ return html `
116
+ <ft-button round part="rating-stars-${ratingValue}"
117
+ label="${this.labelResolver.resolve("starsRating", ratingValue)}"
118
+ tooltipPosition="top"
119
+ .iconVariant=${this.iconVariant}
120
+ icon="${active ? this.filledStarIcon : this.emptyStarIcon}"
121
+ class="ft-reader-feedback--${active ? "active" : "inactive"}"
122
+ @click=${() => this.rate(this.ratingValue === ratingValue ? undefined : ratingValue)}
123
+ ></ft-button>
124
+ `;
125
+ })}
126
+ </div>
127
+ `;
128
+ case "LIKE":
129
+ const disliked = this.ratingType === "LIKE" && this.ratingValue === 0;
130
+ const liked = this.ratingType === "LIKE" && this.ratingValue === 1;
131
+ return html `
132
+ <ft-switch @change=${(e) => this.rate(e.detail)} part="rating rating-like">
133
+ <ft-switch-option .value=${0}
134
+ ?selected=${disliked}
135
+ .iconVariant=${this.iconVariant}
136
+ icon="${disliked ? this.filledDislikeIcon : this.emptyDislikeIcon}">
137
+ ${this.labelResolver.resolve("dislike")}
138
+ </ft-switch-option>
139
+ <ft-switch-option .value=${1}
140
+ ?selected=${liked}
141
+ .iconVariant=${this.iconVariant}
142
+ icon="${liked ? this.filledLikeIcon : this.emptyLikeIcon}">
143
+ ${this.labelResolver.resolve("like")}
144
+ </ft-switch-option>
145
+ </ft-switch>
146
+ `;
147
+ case "DICHOTOMOUS":
148
+ const no = this.ratingType === "DICHOTOMOUS" && this.ratingValue === 0;
149
+ const yes = this.ratingType === "DICHOTOMOUS" && this.ratingValue === 1;
150
+ return html `
151
+ <ft-switch @change=${(e) => this.rate(e.detail)}
152
+ part="rating rating-dichotomous">
153
+ <ft-switch-option .value=${0}
154
+ ?selected=${no}>
155
+ ${this.labelResolver.resolve("dichotomousNo")}
156
+ </ft-switch-option>
157
+ <ft-switch-option .value=${1}
158
+ ?selected=${yes}>
159
+ ${this.labelResolver.resolve("dichotomousYes")}
160
+ </ft-switch-option>
161
+ </ft-switch>
162
+ `;
163
+ }
164
+ return nothing;
165
+ }
166
+ rate(value) {
167
+ this.dispatchEvent(new CustomEvent("rate", { detail: value }));
168
+ }
169
+ renderFeedbackSection() {
170
+ if (this.canAccessFeedback) {
171
+ const mailLink = this.feedbackMailLinkBuilder(this.feedbackMessage);
172
+ let button = html `
173
+ <ft-button primary
174
+ class="ft-reader-feedback--send-feedback"
175
+ ?disabled=${!this.feedbackMessage}
176
+ @click=${() => mailLink ? this.clearFeedbackInput() : this.sendFeedback()}
177
+ part="send-feedback">
178
+ ${this.labelResolver.resolve("okButton")}
179
+ </ft-button>
180
+ `;
181
+ if (mailLink) {
182
+ button = html `
183
+ <a class="ft-reader-feedback--feedback-link" href="${mailLink}">
184
+ ${button}
185
+ </a>
186
+ `;
187
+ }
188
+ return html `
189
+ <div class="ft-reader-feedback--feedback" part="feedback-container">
190
+ <ft-typography variant="title-dense" part="section-title" class="ft-reader-feedback--section-title">
191
+ ${this.labelResolver.resolve("feedbackSectionTitle")}
192
+ </ft-typography>
193
+ <ft-text-area outlined
194
+ class="ft-reader-feedback--feedback-input" part="feedback-input"
195
+ label="${this.labelResolver.resolve("feedbackInputLabel")}"
196
+ @live-change=${(e) => this.feedbackMessage = e.detail.trim()}
197
+ ></ft-text-area>
198
+ ${button}
199
+ </div>
200
+ `;
201
+ }
202
+ return this.placeholderInEditor("Feedback unavailable: missing role FEEDBACK_USER");
203
+ }
204
+ sendFeedback() {
205
+ this.dispatchEvent(new CustomEvent("send-feedback", { detail: this.feedbackInput.value }));
206
+ }
207
+ placeholderInEditor(message) {
208
+ return this.editorMode ? html `
209
+ <div class="ft-reader-feedback--placeholder">
210
+ <ft-typography variant="caption">${message}</ft-typography>
211
+ <ft-tooltip position="left"
212
+ text="This is a placeholder, it will not appear outside this designer.">
213
+ <ft-icon>INFO</ft-icon>
214
+ </ft-tooltip>
215
+ </div>
216
+ ` : nothing;
217
+ }
218
+ clearFeedbackInput() {
219
+ setTimeout(() => this.feedbackInput.value = "", 0);
220
+ }
221
+ }
222
+ FtReaderFeedback.elementDefinitions = {
223
+ "ft-button": FtButton,
224
+ "ft-typography": FtTypography,
225
+ "ft-icon": FtIcon,
226
+ "ft-tooltip": FtTooltip,
227
+ "ft-switch": FtSwitch,
228
+ "ft-switch-option": FtSwitchOption,
229
+ "ft-text-area": FtTextArea,
230
+ };
231
+ FtReaderFeedback.styles = styles;
232
+ __decorate([
233
+ property({ attribute: true })
234
+ ], FtReaderFeedback.prototype, "editorMode", void 0);
235
+ __decorate([
236
+ property({ type: Boolean })
237
+ ], FtReaderFeedback.prototype, "hideTitle", void 0);
238
+ __decorate([
239
+ property({ type: Boolean })
240
+ ], FtReaderFeedback.prototype, "ratingAllowed", void 0);
241
+ __decorate([
242
+ property({ attribute: false })
243
+ ], FtReaderFeedback.prototype, "labelResolver", void 0);
244
+ __decorate([
245
+ property({ attribute: false })
246
+ ], FtReaderFeedback.prototype, "ratingSummary", void 0);
247
+ __decorate([
248
+ property({ attribute: false })
249
+ ], FtReaderFeedback.prototype, "user", void 0);
250
+ __decorate([
251
+ query(".ft-reader-feedback--feedback-input")
252
+ ], FtReaderFeedback.prototype, "feedbackInput", void 0);
253
+ __decorate([
254
+ state()
255
+ ], FtReaderFeedback.prototype, "feedbackMessage", void 0);
256
+ __decorate([
257
+ property()
258
+ ], FtReaderFeedback.prototype, "iconVariant", void 0);
259
+ __decorate([
260
+ property()
261
+ ], FtReaderFeedback.prototype, "emptyStarIcon", void 0);
262
+ __decorate([
263
+ property()
264
+ ], FtReaderFeedback.prototype, "filledStarIcon", void 0);
265
+ __decorate([
266
+ property()
267
+ ], FtReaderFeedback.prototype, "emptyLikeIcon", void 0);
268
+ __decorate([
269
+ property()
270
+ ], FtReaderFeedback.prototype, "filledLikeIcon", void 0);
271
+ __decorate([
272
+ property()
273
+ ], FtReaderFeedback.prototype, "emptyDislikeIcon", void 0);
274
+ __decorate([
275
+ property()
276
+ ], FtReaderFeedback.prototype, "filledDislikeIcon", void 0);
277
+ __decorate([
278
+ property()
279
+ ], FtReaderFeedback.prototype, "feedbackMailLinkBuilder", void 0);
280
+ //# sourceMappingURL=ft-reader-feedback.js.map