forked from MapComplete/MapComplete
UX: add warning for too long reviews, see #1775
This commit is contained in:
parent
f0152ce856
commit
fa9aca0685
3 changed files with 77 additions and 50 deletions
|
@ -676,6 +676,7 @@
|
||||||
"saving_review": "Saving…",
|
"saving_review": "Saving…",
|
||||||
"title": "{count} reviews",
|
"title": "{count} reviews",
|
||||||
"title_singular": "One review",
|
"title_singular": "One review",
|
||||||
|
"too_long": "At most {max} characters are allowed. Your review has {amount} characters.",
|
||||||
"tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>",
|
"tos": "If you create a review, you agree to <a href='https://mangrove.reviews/terms' target='_blank'>the TOS and privacy policy of Mangrove.reviews</a>",
|
||||||
"write_a_comment": "Leave a review…"
|
"write_a_comment": "Leave a review…"
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,6 +52,10 @@ export class MangroveIdentity {
|
||||||
* Tracks all reviews of a given feature, allows to create a new review
|
* Tracks all reviews of a given feature, allows to create a new review
|
||||||
*/
|
*/
|
||||||
export default class FeatureReviews {
|
export default class FeatureReviews {
|
||||||
|
/**
|
||||||
|
* See https://gitlab.com/open-reviews/mangrove/-/blob/master/servers/reviewer/src/review.rs#L269 and https://github.com/pietervdvn/MapComplete/issues/1775
|
||||||
|
*/
|
||||||
|
public static readonly REVIEW_OPINION_MAX_LENGTH = 1000
|
||||||
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}
|
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}
|
||||||
public readonly subjectUri: Store<string>
|
public readonly subjectUri: Store<string>
|
||||||
public readonly average: Store<number | null>
|
public readonly average: Store<number | null>
|
||||||
|
@ -172,6 +176,9 @@ export default class FeatureReviews {
|
||||||
* The given review is uploaded to mangrove.reviews and added to the list of known reviews
|
* The given review is uploaded to mangrove.reviews and added to the list of known reviews
|
||||||
*/
|
*/
|
||||||
public async createReview(review: Omit<Review, "sub">): Promise<void> {
|
public async createReview(review: Omit<Review, "sub">): Promise<void> {
|
||||||
|
if(review.opinion.length > FeatureReviews .REVIEW_OPINION_MAX_LENGTH){
|
||||||
|
throw "Opinion too long, should be at most "+FeatureReviews.REVIEW_OPINION_MAX_LENGTH+" characters long"
|
||||||
|
}
|
||||||
const r: Review = {
|
const r: Review = {
|
||||||
sub: this.subjectUri.data,
|
sub: this.subjectUri.data,
|
||||||
...review,
|
...review,
|
||||||
|
|
|
@ -1,60 +1,72 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FeatureReviews from "../../Logic/Web/MangroveReviews"
|
import FeatureReviews from "../../Logic/Web/MangroveReviews"
|
||||||
import StarsBar from "./StarsBar.svelte"
|
import StarsBar from "./StarsBar.svelte"
|
||||||
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import type { Feature } from "geojson"
|
import type { Feature } from "geojson"
|
||||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Checkbox from "../Base/Checkbox.svelte"
|
import Checkbox from "../Base/Checkbox.svelte"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
import If from "../Base/If.svelte"
|
import If from "../Base/If.svelte"
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import { Review } from "mangrove-reviews-typescript"
|
import { Review } from "mangrove-reviews-typescript"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { placeholder } from "../../Utils/placeholder"
|
import { placeholder } from "../../Utils/placeholder"
|
||||||
|
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
||||||
|
|
||||||
export let state: SpecialVisualizationState
|
export let state: SpecialVisualizationState
|
||||||
export let tags: UIEventSource<Record<string, string>>
|
export let tags: UIEventSource<Record<string, string>>
|
||||||
export let feature: Feature
|
export let feature: Feature
|
||||||
export let layer: LayerConfig
|
export let layer: LayerConfig
|
||||||
/**
|
/**
|
||||||
* The form to create a new review.
|
* The form to create a new review.
|
||||||
* This is multi-stepped.
|
* This is multi-stepped.
|
||||||
*/
|
*/
|
||||||
export let reviews: FeatureReviews
|
export let reviews: FeatureReviews
|
||||||
|
|
||||||
let score = 0
|
let score = 0
|
||||||
let confirmedScore = undefined
|
let confirmedScore = undefined
|
||||||
let isAffiliated = new UIEventSource(false)
|
let isAffiliated = new UIEventSource(false)
|
||||||
let opinion = new UIEventSource<string>(undefined)
|
let opinion = new UIEventSource<string>(undefined)
|
||||||
|
|
||||||
const t = Translations.t.reviews
|
const t = Translations.t.reviews
|
||||||
|
|
||||||
let _state: "ask" | "saving" | "done" = "ask"
|
let _state: "ask" | "saving" | "done" = "ask"
|
||||||
|
|
||||||
const connection = state.osmConnection
|
const connection = state.osmConnection
|
||||||
|
|
||||||
async function save() {
|
const hasError: Store<undefined | "too_long"> = opinion.mapD(op => {
|
||||||
_state = "saving"
|
const tooLong = op.length > FeatureReviews.REVIEW_OPINION_MAX_LENGTH
|
||||||
let nickname = undefined
|
if (tooLong) {
|
||||||
if (connection.isLoggedIn.data) {
|
return "too_long"
|
||||||
nickname = connection.userDetails.data.name
|
}
|
||||||
|
return undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
if (hasError.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_state = "saving"
|
||||||
|
let nickname = undefined
|
||||||
|
if (connection.isLoggedIn.data) {
|
||||||
|
nickname = connection.userDetails.data.name
|
||||||
|
}
|
||||||
|
const review: Omit<Review, "sub"> = {
|
||||||
|
rating: confirmedScore,
|
||||||
|
opinion: opinion.data,
|
||||||
|
metadata: { nickname, is_affiliated: isAffiliated.data },
|
||||||
|
}
|
||||||
|
if (state.featureSwitchIsTesting?.data ?? true) {
|
||||||
|
console.log("Testing - not actually saving review", review)
|
||||||
|
await Utils.waitFor(1000)
|
||||||
|
} else {
|
||||||
|
await reviews.createReview(review)
|
||||||
|
}
|
||||||
|
_state = "done"
|
||||||
}
|
}
|
||||||
const review: Omit<Review, "sub"> = {
|
|
||||||
rating: confirmedScore,
|
|
||||||
opinion: opinion.data,
|
|
||||||
metadata: { nickname, is_affiliated: isAffiliated.data },
|
|
||||||
}
|
|
||||||
if (state.featureSwitchIsTesting?.data ?? true) {
|
|
||||||
console.log("Testing - not actually saving review", review)
|
|
||||||
await Utils.waitFor(1000)
|
|
||||||
} else {
|
|
||||||
await reviews.createReview(review)
|
|
||||||
}
|
|
||||||
_state = "done"
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if _state === "done"}
|
{#if _state === "done"}
|
||||||
|
@ -95,6 +107,12 @@
|
||||||
class="mb-1 w-full"
|
class="mb-1 w-full"
|
||||||
use:placeholder={t.reviewPlaceholder}
|
use:placeholder={t.reviewPlaceholder}
|
||||||
/>
|
/>
|
||||||
|
{#if $hasError === "too_long"}
|
||||||
|
<div class="alert flex items-center px-2">
|
||||||
|
<ExclamationTriangle class="w-12 h-12"/>
|
||||||
|
<Tr t={t.too_long.Subs({max: FeatureReviews.REVIEW_OPINION_MAX_LENGTH, amount: $opinion?.length ?? 0})}> </Tr>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Checkbox selected={isAffiliated}>
|
<Checkbox selected={isAffiliated}>
|
||||||
|
@ -108,7 +126,8 @@
|
||||||
<Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
|
<Tr t={t.reviewing_as.Subs({ nickname: state.osmConnection.userDetails.data.name })} />
|
||||||
<Tr slot="else" t={t.reviewing_as_anonymous} />
|
<Tr slot="else" t={t.reviewing_as_anonymous} />
|
||||||
</If>
|
</If>
|
||||||
<button class="primary" on:click={save}>
|
<button class="primary" class:disabled={$hasError !== undefined}
|
||||||
|
on:click={save}>
|
||||||
<Tr t={t.save} />
|
<Tr t={t.save} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue