forked from MapComplete/MapComplete
Refactoring: port reviews to svelte
This commit is contained in:
parent
6edcd7d73c
commit
6d5f5d54f8
41 changed files with 683 additions and 462 deletions
|
@ -1,34 +1,35 @@
|
|||
import { ImmutableStore, Store, UIEventSource } from "../UIEventSource"
|
||||
import { MangroveReviews, Review } from "mangrove-reviews-typescript"
|
||||
import { Utils } from "../../Utils"
|
||||
import { Feature, Position } from "geojson"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
import { ImmutableStore, Store, UIEventSource } from "../UIEventSource";
|
||||
import { MangroveReviews, Review } from "mangrove-reviews-typescript";
|
||||
import { Utils } from "../../Utils";
|
||||
import { Feature, Position } from "geojson";
|
||||
import { GeoOperations } from "../GeoOperations";
|
||||
|
||||
export class MangroveIdentity {
|
||||
public readonly keypair: Store<CryptoKeyPair>
|
||||
public readonly key_id: Store<string>
|
||||
public readonly keypair: Store<CryptoKeyPair>;
|
||||
public readonly key_id: Store<string>;
|
||||
|
||||
constructor(mangroveIdentity: UIEventSource<string>) {
|
||||
const key_id = new UIEventSource<string>(undefined)
|
||||
this.key_id = key_id
|
||||
const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined)
|
||||
this.keypair = keypairEventSource
|
||||
const key_id = new UIEventSource<string>(undefined);
|
||||
this.key_id = key_id;
|
||||
const keypairEventSource = new UIEventSource<CryptoKeyPair>(undefined);
|
||||
this.keypair = keypairEventSource;
|
||||
mangroveIdentity.addCallbackAndRunD(async (data) => {
|
||||
if (data === "") {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data))
|
||||
keypairEventSource.setData(keypair)
|
||||
const pem = await MangroveReviews.publicToPem(keypair.publicKey)
|
||||
key_id.setData(pem)
|
||||
})
|
||||
const keypair = await MangroveReviews.jwkToKeypair(JSON.parse(data));
|
||||
keypairEventSource.setData(keypair);
|
||||
const pem = await MangroveReviews.publicToPem(keypair.publicKey);
|
||||
key_id.setData(pem);
|
||||
});
|
||||
|
||||
try {
|
||||
if (!Utils.runningFromConsole && (mangroveIdentity.data ?? "") === "") {
|
||||
MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {})
|
||||
MangroveIdentity.CreateIdentity(mangroveIdentity).then((_) => {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Could not create identity: ", e)
|
||||
console.error("Could not create identity: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,13 +39,13 @@ export class MangroveIdentity {
|
|||
* @constructor
|
||||
*/
|
||||
private static async CreateIdentity(identity: UIEventSource<string>): Promise<void> {
|
||||
const keypair = await MangroveReviews.generateKeypair()
|
||||
const jwk = await MangroveReviews.keypairToJwk(keypair)
|
||||
const keypair = await MangroveReviews.generateKeypair();
|
||||
const jwk = await MangroveReviews.keypairToJwk(keypair);
|
||||
if ((identity.data ?? "") !== "") {
|
||||
// Identity has been loaded via osmPreferences by now - we don't overwrite
|
||||
return
|
||||
return;
|
||||
}
|
||||
identity.setData(JSON.stringify(jwk))
|
||||
identity.setData(JSON.stringify(jwk));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,17 +53,18 @@ export class MangroveIdentity {
|
|||
* Tracks all reviews of a given feature, allows to create a new review
|
||||
*/
|
||||
export default class FeatureReviews {
|
||||
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {}
|
||||
public readonly subjectUri: Store<string>
|
||||
private static readonly _featureReviewsCache: Record<string, FeatureReviews> = {};
|
||||
public readonly subjectUri: Store<string>;
|
||||
public readonly average: Store<number | null>;
|
||||
private readonly _reviews: UIEventSource<(Review & { madeByLoggedInUser: Store<boolean> })[]> =
|
||||
new UIEventSource([])
|
||||
new UIEventSource([]);
|
||||
public readonly reviews: Store<(Review & { madeByLoggedInUser: Store<boolean> })[]> =
|
||||
this._reviews
|
||||
private readonly _lat: number
|
||||
private readonly _lon: number
|
||||
private readonly _uncertainty: number
|
||||
private readonly _name: Store<string>
|
||||
private readonly _identity: MangroveIdentity
|
||||
this._reviews;
|
||||
private readonly _lat: number;
|
||||
private readonly _lon: number;
|
||||
private readonly _uncertainty: number;
|
||||
private readonly _name: Store<string>;
|
||||
private readonly _identity: MangroveIdentity;
|
||||
|
||||
private constructor(
|
||||
feature: Feature,
|
||||
|
@ -75,55 +77,72 @@ export default class FeatureReviews {
|
|||
}
|
||||
) {
|
||||
const centerLonLat = GeoOperations.centerpointCoordinates(feature)
|
||||
;[this._lon, this._lat] = centerLonLat
|
||||
;[this._lon, this._lat] = centerLonLat;
|
||||
this._identity =
|
||||
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined))
|
||||
const nameKey = options?.nameKey ?? "name"
|
||||
mangroveIdentity ?? new MangroveIdentity(new UIEventSource<string>(undefined));
|
||||
const nameKey = options?.nameKey ?? "name";
|
||||
|
||||
if (feature.geometry.type === "Point") {
|
||||
this._uncertainty = options?.uncertaintyRadius ?? 10
|
||||
this._uncertainty = options?.uncertaintyRadius ?? 10;
|
||||
} else {
|
||||
let coordss: Position[][]
|
||||
let coordss: Position[][];
|
||||
if (feature.geometry.type === "LineString") {
|
||||
coordss = [feature.geometry.coordinates]
|
||||
coordss = [feature.geometry.coordinates];
|
||||
} else if (
|
||||
feature.geometry.type === "MultiLineString" ||
|
||||
feature.geometry.type === "Polygon"
|
||||
) {
|
||||
coordss = feature.geometry.coordinates
|
||||
coordss = feature.geometry.coordinates;
|
||||
}
|
||||
let maxDistance = 0
|
||||
let maxDistance = 0;
|
||||
for (const coords of coordss) {
|
||||
for (const coord of coords) {
|
||||
maxDistance = Math.max(
|
||||
maxDistance,
|
||||
GeoOperations.distanceBetween(centerLonLat, coord)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this._uncertainty = options?.uncertaintyRadius ?? maxDistance
|
||||
this._uncertainty = options?.uncertaintyRadius ?? maxDistance;
|
||||
}
|
||||
this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName)
|
||||
this._name = tagsSource.map((tags) => tags[nameKey] ?? options?.fallbackName);
|
||||
|
||||
this.subjectUri = this.ConstructSubjectUri()
|
||||
this.subjectUri = this.ConstructSubjectUri();
|
||||
|
||||
const self = this
|
||||
const self = this;
|
||||
this.subjectUri.addCallbackAndRunD(async (sub) => {
|
||||
const reviews = await MangroveReviews.getReviews({ sub })
|
||||
self.addReviews(reviews.reviews)
|
||||
})
|
||||
const reviews = await MangroveReviews.getReviews({ sub });
|
||||
self.addReviews(reviews.reviews);
|
||||
});
|
||||
/* We also construct all subject queries _without_ encoding the name to work around a previous bug
|
||||
* See https://github.com/giggls/opencampsitemap/issues/30
|
||||
*/
|
||||
this.ConstructSubjectUri(true).addCallbackAndRunD(async (sub) => {
|
||||
try {
|
||||
const reviews = await MangroveReviews.getReviews({ sub })
|
||||
self.addReviews(reviews.reviews)
|
||||
const reviews = await MangroveReviews.getReviews({ sub });
|
||||
self.addReviews(reviews.reviews);
|
||||
} catch (e) {
|
||||
console.log("Could not fetch reviews for partially incorrect query ", sub)
|
||||
console.log("Could not fetch reviews for partially incorrect query ", sub);
|
||||
}
|
||||
})
|
||||
});
|
||||
this.average = this._reviews.map(reviews => {
|
||||
if (!reviews) {
|
||||
return null;
|
||||
}
|
||||
if(reviews.length === 0){
|
||||
return null
|
||||
}
|
||||
let sum = 0;
|
||||
let count = 0;
|
||||
for (const review of reviews) {
|
||||
if (review.rating !== undefined) {
|
||||
count++;
|
||||
sum += review.rating;
|
||||
}
|
||||
}
|
||||
return Math.round(sum / count)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,14 +158,14 @@ export default class FeatureReviews {
|
|||
uncertaintyRadius?: number
|
||||
}
|
||||
) {
|
||||
const key = feature.properties.id
|
||||
const cached = FeatureReviews._featureReviewsCache[key]
|
||||
const key = feature.properties.id;
|
||||
const cached = FeatureReviews._featureReviewsCache[key];
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
return cached;
|
||||
}
|
||||
const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options)
|
||||
FeatureReviews._featureReviewsCache[key] = featureReviews
|
||||
return featureReviews
|
||||
const featureReviews = new FeatureReviews(feature, tagsSource, mangroveIdentity, options);
|
||||
FeatureReviews._featureReviewsCache[key] = featureReviews;
|
||||
return featureReviews;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,15 +174,15 @@ export default class FeatureReviews {
|
|||
public async createReview(review: Omit<Review, "sub">): Promise<void> {
|
||||
const r: Review = {
|
||||
sub: this.subjectUri.data,
|
||||
...review,
|
||||
}
|
||||
const keypair: CryptoKeyPair = this._identity.keypair.data
|
||||
console.log(r)
|
||||
const jwt = await MangroveReviews.signReview(keypair, r)
|
||||
console.log("Signed:", jwt)
|
||||
await MangroveReviews.submitReview(jwt)
|
||||
this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) })
|
||||
this._reviews.ping()
|
||||
...review
|
||||
};
|
||||
const keypair: CryptoKeyPair = this._identity.keypair.data;
|
||||
console.log(r);
|
||||
const jwt = await MangroveReviews.signReview(keypair, r);
|
||||
console.log("Signed:", jwt);
|
||||
await MangroveReviews.submitReview(jwt);
|
||||
this._reviews.data.push({ ...r, madeByLoggedInUser: new ImmutableStore(true) });
|
||||
this._reviews.ping();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -172,46 +191,48 @@ export default class FeatureReviews {
|
|||
* @private
|
||||
*/
|
||||
private addReviews(reviews: { payload: Review; kid: string }[]) {
|
||||
const self = this
|
||||
const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion))
|
||||
const self = this;
|
||||
const alreadyKnown = new Set(self._reviews.data.map((r) => r.rating + " " + r.opinion));
|
||||
|
||||
let hasNew = false
|
||||
let hasNew = false;
|
||||
for (const reviewData of reviews) {
|
||||
const review = reviewData.payload
|
||||
const review = reviewData.payload;
|
||||
|
||||
try {
|
||||
const url = new URL(review.sub)
|
||||
console.log("URL is", url)
|
||||
const url = new URL(review.sub);
|
||||
console.log("URL is", url);
|
||||
if (url.protocol === "geo:") {
|
||||
const coordinate = <[number, number]>(
|
||||
url.pathname.split(",").map((n) => Number(n))
|
||||
)
|
||||
);
|
||||
const distance = GeoOperations.distanceBetween(
|
||||
[this._lat, this._lon],
|
||||
coordinate
|
||||
)
|
||||
);
|
||||
if (distance > this._uncertainty) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
const key = review.rating + " " + review.opinion
|
||||
const key = review.rating + " " + review.opinion;
|
||||
if (alreadyKnown.has(key)) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
self._reviews.data.push({
|
||||
...review,
|
||||
madeByLoggedInUser: this._identity.key_id.map((user_key_id) => {
|
||||
return reviewData.kid === user_key_id
|
||||
}),
|
||||
})
|
||||
hasNew = true
|
||||
return reviewData.kid === user_key_id;
|
||||
})
|
||||
});
|
||||
hasNew = true;
|
||||
}
|
||||
if (hasNew) {
|
||||
self._reviews.ping()
|
||||
self._reviews.data.sort((a, b) => b.iat - a.iat) // Sort with most recent first
|
||||
|
||||
self._reviews.ping();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,13 +245,13 @@ export default class FeatureReviews {
|
|||
private ConstructSubjectUri(dontEncodeName: boolean = false): Store<string> {
|
||||
// https://www.rfc-editor.org/rfc/rfc5870#section-3.4.2
|
||||
// `u` stands for `uncertainty`, https://www.rfc-editor.org/rfc/rfc5870#section-3.4.3
|
||||
const self = this
|
||||
return this._name.map(function (name) {
|
||||
let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`
|
||||
const self = this;
|
||||
return this._name.map(function(name) {
|
||||
let uri = `geo:${self._lat},${self._lon}?u=${self._uncertainty}`;
|
||||
if (name) {
|
||||
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name))
|
||||
uri += "&q=" + (dontEncodeName ? name : encodeURIComponent(name));
|
||||
}
|
||||
return uri
|
||||
})
|
||||
return uri;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue