diff --git a/langs/en.json b/langs/en.json index b2a5760c3..213f7fff7 100644 --- a/langs/en.json +++ b/langs/en.json @@ -671,6 +671,7 @@ "reviewPlaceholder": "Describe your experience…", "reviewing_as": "Reviewing as {nickname}", "reviewing_as_anonymous": "Reviewing as anonymous", + "reviews_bug": "Expected more reviews? Some reviews are not displayed due to a bug.", "save": "Save review", "saved": "Review saved. Thanks for sharing!", "saving_review": "Saving…", @@ -678,7 +679,9 @@ "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 the TOS and privacy policy of Mangrove.reviews", - "write_a_comment": "Leave a review…" + "write_a_comment": "Leave a review…", + "your_reviews": "Your previous reviews", + "your_reviews_empty": "We couldn't find any of your previous reviews" }, "split": { "cancel": "Cancel", diff --git a/src/Logic/State/UserRelatedState.ts b/src/Logic/State/UserRelatedState.ts index 5cfe3416d..307f5e104 100644 --- a/src/Logic/State/UserRelatedState.ts +++ b/src/Logic/State/UserRelatedState.ts @@ -73,7 +73,6 @@ export default class UserRelatedState { constructor( osmConnection: OsmConnection, - availableLanguages?: string[], layout?: LayoutConfig, featureSwitches?: FeatureSwitchState, mapProperties?: MapProperties @@ -365,6 +364,11 @@ export default class UserRelatedState { [translationMode] ) + this.mangroveIdentity.getKeyId().addCallbackAndRun(kid => { + amendedPrefs.data["mangrove_kid"] = kid + amendedPrefs.ping() + }) + const usersettingMetaTagging = new ThemeMetaTagging() osmConnection.userDetails.addCallback((userDetails) => { for (const k in userDetails) { diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index ed48130ff..70aa5da87 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -306,7 +306,8 @@ export abstract class Store implements Readable { export class ImmutableStore extends Store { public readonly data: T - + static FALSE = new ImmutableStore(false) + static TRUE = new ImmutableStore(true) constructor(data: T) { super() this.data = data diff --git a/src/Logic/Web/MangroveReviews.ts b/src/Logic/Web/MangroveReviews.ts index 3aa9582f6..426257bac 100644 --- a/src/Logic/Web/MangroveReviews.ts +++ b/src/Logic/Web/MangroveReviews.ts @@ -5,10 +5,12 @@ import { Feature, Position } from "geojson" import { GeoOperations } from "../GeoOperations" export class MangroveIdentity { - public readonly keypair: Store - public readonly key_id: Store + private readonly keypair: Store + private readonly mangroveIdentity: UIEventSource + private readonly key_id: Store constructor(mangroveIdentity: UIEventSource) { + this.mangroveIdentity = mangroveIdentity const key_id = new UIEventSource(undefined) this.key_id = key_id const keypairEventSource = new UIEventSource(undefined) @@ -23,13 +25,7 @@ export class MangroveIdentity { key_id.setData(pem) }) - try { - if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") { - MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {}) - } - } catch (e) { - console.error("Could not create identity: ", e) - } + } /** @@ -44,8 +40,61 @@ export class MangroveIdentity { // Identity has been loaded via osmPreferences by now - we don't overwrite return } + console.log("Creating a new Mangrove identity!") identity.setData(JSON.stringify(jwk)) } + + /** + * Only called to create a review. + */ + async getKeypair(): Promise { + if(this.keypair.data ?? "" === ""){ + // We want to create a review, but it seems like no key has been setup at this moment + // We create the key + try { + if (!Utils.runningFromConsole && (this.mangroveIdentity.data ?? "") === "") { + await MangroveIdentity.CreateIdentity(this.mangroveIdentity) + } + } catch (e) { + console.error("Could not create identity: ", e) + } + } + return this.keypair.data + } + + getKeyId(): Store { + return this.key_id + } + + private allReviewsById : UIEventSource<(Review & {kid: string, signature: string})[]>= undefined + + + /** + * Gets all reviews that are made for the current identity. + */ + public getAllReviews(): Store<(Review & {kid: string, signature: string})[]>{ + if(this.allReviewsById !== undefined){ + return this.allReviewsById + } + this.allReviewsById = new UIEventSource( []) + this.key_id.map(pem => { + if(pem === undefined){ + return [] + } + MangroveReviews.getReviews({ + kid: pem + }).then(allReviews => { + this.allReviewsById.setData(allReviews.reviews.map(r => ({ + ...r, ...r.payload + }))) + }) + }) + return this.allReviewsById + } + + addReview(review: Review & {kid, signature}) { + this.allReviewsById?.setData(this.allReviewsById?.data?.concat([review])) + } } /** @@ -176,26 +225,30 @@ export default class FeatureReviews { * The given review is uploaded to mangrove.reviews and added to the list of known reviews */ public async createReview(review: Omit): Promise { - if(review.opinion.length > FeatureReviews .REVIEW_OPINION_MAX_LENGTH){ + if(review.opinion !== undefined && 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 = { sub: this.subjectUri.data, ...review, } - const keypair: CryptoKeyPair = this._identity.keypair.data + const keypair: CryptoKeyPair = await this._identity.getKeypair() const jwt = await MangroveReviews.signReview(keypair, r) const kid = await MangroveReviews.publicToPem(keypair.publicKey) await MangroveReviews.submitReview(jwt) - this._reviews.data.push({ + const reviewWithKid = { ...r, kid, signature: jwt, madeByLoggedInUser: new ImmutableStore(true), - }) + } + this._reviews.data.push( reviewWithKid) this._reviews.ping() + this._identity.addReview(reviewWithKid) } + + /** * Adds given reviews to the 'reviews'-UI-eventsource * @param reviews @@ -235,7 +288,7 @@ export default class FeatureReviews { ...review, kid: reviewData.kid, signature: reviewData.signature, - madeByLoggedInUser: this._identity.key_id.map((user_key_id) => { + madeByLoggedInUser: this._identity.getKeyId().map((user_key_id) => { return reviewData.kid === user_key_id }), }) diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index 4895ee1aa..f5c576281 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -171,7 +171,6 @@ export default class ThemeViewState implements SpecialVisualizationState { }) this.userRelatedState = new UserRelatedState( this.osmConnection, - layout?.language, layout, this.featureSwitches, this.mapProperties diff --git a/src/UI/Favourites/FavouriteSummary.svelte b/src/UI/Favourites/FavouriteSummary.svelte index 4a2bd7fb7..a505419f4 100644 --- a/src/UI/Favourites/FavouriteSummary.svelte +++ b/src/UI/Favourites/FavouriteSummary.svelte @@ -29,7 +29,7 @@ center() } - const titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"] + let titleIconBlacklist = ["osmlink", "sharelink", "favourite_title_icon"] {#if favLayer !== undefined} diff --git a/src/UI/Favourites/Favourites.svelte b/src/UI/Favourites/Favourites.svelte index 3996e6eb3..406e216d0 100644 --- a/src/UI/Favourites/Favourites.svelte +++ b/src/UI/Favourites/Favourites.svelte @@ -48,7 +48,7 @@
console.log("Got keypress", e)}> - + {#each $favourites as feature (feature.properties.id)} diff --git a/src/UI/Reviews/ReviewForm.svelte b/src/UI/Reviews/ReviewForm.svelte index d6f06e40a..f37722d89 100644 --- a/src/UI/Reviews/ReviewForm.svelte +++ b/src/UI/Reviews/ReviewForm.svelte @@ -35,9 +35,9 @@ let _state: "ask" | "saving" | "done" = "ask" - const connection = state.osmConnection + let connection = state.osmConnection - const hasError: Store = opinion.mapD(op => { + let hasError: Store = opinion.mapD(op => { const tooLong = op.length > FeatureReviews.REVIEW_OPINION_MAX_LENGTH if (tooLong) { return "too_long" @@ -45,6 +45,8 @@ return undefined }) + let uploadFailed: string = undefined + async function save() { if (hasError.data) { return @@ -63,13 +65,24 @@ console.log("Testing - not actually saving review", review) await Utils.waitFor(1000) } else { - await reviews.createReview(review) + try { + + await reviews.createReview(review) + } catch (e) { + console.error("Could not create review due to", e) + uploadFailed = "" + e + } } _state = "done" } - -{#if _state === "done"} +{#if uploadFailed} +
+ + + {uploadFailed} +
+{:else if _state === "done"} {:else if _state === "saving"} @@ -109,8 +122,9 @@ /> {#if $hasError === "too_long"}
- - + +
{/if} diff --git a/src/UI/Reviews/ReviewsOverview.svelte b/src/UI/Reviews/ReviewsOverview.svelte new file mode 100644 index 000000000..bd1de598b --- /dev/null +++ b/src/UI/Reviews/ReviewsOverview.svelte @@ -0,0 +1,40 @@ + + + +
+ + + +
+ + + {#if $reviews?.length > 0} +
console.log("Got keypress", e)}> + {#each $reviews as review (review.sub)} + + {/each} +
+ {:else} + + {/if} + +
+ + +
+
diff --git a/src/UI/Reviews/SingleReview.svelte b/src/UI/Reviews/SingleReview.svelte index 104a58fef..4e66a2edd 100644 --- a/src/UI/Reviews/SingleReview.svelte +++ b/src/UI/Reviews/SingleReview.svelte @@ -1,22 +1,43 @@ -
-
+
+ {#if showSub} + + {/if} +
+

+ +

+