mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-16 03:40:37 +08:00
Convert cob actions into customized desktop actions
This commit allows us to e.g. convert the Set of DIDs we obtain from assignee actions on cobs into a Set of Authors with their Aliases. Also it introduces the `FromRadicleAction` trait which can be implemented for any type of enum we get from the `radicle` create to convert into a type which is better suited for us. Removes also any calling the `alias` command in the `PatchTimeline` or `IssueTimeline` since we already get the alias from the backend the first time now.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use radicle::git;
|
||||
use radicle::identity;
|
||||
|
||||
use radicle::issue::{Action, TYPENAME};
|
||||
use radicle::issue::TYPENAME;
|
||||
use radicle_types as types;
|
||||
use radicle_types::error::Error;
|
||||
use radicle_types::traits::cobs::Cobs;
|
||||
@@ -63,6 +63,6 @@ pub fn activity_by_issue(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Vec<types::cobs::Operation<Action>>, Error> {
|
||||
) -> Result<Vec<types::cobs::Operation<types::cobs::issue::Action>>, Error> {
|
||||
ctx.activity_by_id(rid, &TYPENAME, id)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use radicle::patch::{Action, TYPENAME};
|
||||
use radicle::patch::TYPENAME;
|
||||
use radicle::{cob, git, identity};
|
||||
|
||||
use radicle_types as types;
|
||||
@@ -105,6 +105,6 @@ pub fn activity_by_patch(
|
||||
ctx: tauri::State<AppState>,
|
||||
rid: identity::RepoId,
|
||||
id: git::Oid,
|
||||
) -> Result<Vec<types::cobs::Operation<Action>>, Error> {
|
||||
) -> Result<Vec<types::cobs::Operation<models::patch::Action>>, Error> {
|
||||
ctx.activity_by_id(rid, &TYPENAME, id)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "../Author";
|
||||
import type { Embed } from "../thread/Embed";
|
||||
import type { State } from "./State";
|
||||
|
||||
export type Action =
|
||||
| { "type": "assign"; assignees: Array<string> }
|
||||
| { "type": "assign"; assignees: Array<Author> }
|
||||
| { "type": "edit"; title: string }
|
||||
| { "type": "lifecycle"; state: State }
|
||||
| { "type": "label"; labels: Array<string> }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Author } from "../Author";
|
||||
import type { CodeLocation } from "../thread/CodeLocation";
|
||||
import type { Embed } from "../thread/Embed";
|
||||
import type { Verdict } from "./Verdict";
|
||||
@@ -7,7 +8,7 @@ export type Action =
|
||||
| { "type": "edit"; title: string; target: string }
|
||||
| { "type": "label"; labels: Array<string> }
|
||||
| { "type": "lifecycle"; state: { status: "draft" | "open" | "archived" } }
|
||||
| { "type": "assign"; assignees: Array<string> }
|
||||
| { "type": "assign"; assignees: Array<Author> }
|
||||
| { "type": "merge"; revision: string; commit: string }
|
||||
| {
|
||||
"type": "review";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use radicle::profile::Aliases;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -11,7 +12,7 @@ pub mod repo;
|
||||
pub mod stream;
|
||||
pub mod thread;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, TS, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, TS, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/")]
|
||||
@@ -30,6 +31,14 @@ impl Author {
|
||||
alias: aliases.alias(did),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn did(&self) -> &identity::Did {
|
||||
&self.did
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FromRadicleAction<A> {
|
||||
fn from_radicle_action(value: A, aliases: &Aliases) -> Self;
|
||||
}
|
||||
|
||||
/// Everything that can be done in the system is represented by an `Op`.
|
||||
|
||||
@@ -10,6 +10,9 @@ use radicle::issue;
|
||||
|
||||
use crate::cobs;
|
||||
|
||||
use super::Author;
|
||||
use super::FromRadicleAction;
|
||||
|
||||
#[derive(TS, Serialize)]
|
||||
#[ts(export)]
|
||||
#[ts(export_to = "cob/issue/")]
|
||||
@@ -136,10 +139,7 @@ pub struct NewIssue {
|
||||
#[ts(export_to = "cob/issue/")]
|
||||
pub enum Action {
|
||||
#[serde(rename = "assign")]
|
||||
Assign {
|
||||
#[ts(as = "Vec<String>")]
|
||||
assignees: BTreeSet<identity::Did>,
|
||||
},
|
||||
Assign { assignees: BTreeSet<Author> },
|
||||
|
||||
#[serde(rename = "edit")]
|
||||
Edit { title: String },
|
||||
@@ -189,3 +189,48 @@ pub enum Action {
|
||||
active: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl FromRadicleAction<radicle::issue::Action> for Action {
|
||||
fn from_radicle_action(
|
||||
value: radicle::issue::Action,
|
||||
aliases: &radicle::profile::Aliases,
|
||||
) -> Self {
|
||||
match value {
|
||||
radicle::issue::Action::Assign { assignees } => Self::Assign {
|
||||
assignees: assignees
|
||||
.iter()
|
||||
.map(|a| Author::new(a, aliases))
|
||||
.collect::<BTreeSet<_>>(),
|
||||
},
|
||||
radicle::issue::Action::Comment {
|
||||
body,
|
||||
reply_to,
|
||||
embeds,
|
||||
} => Self::Comment {
|
||||
body,
|
||||
reply_to,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::issue::Action::CommentEdit { id, body, embeds } => Self::CommentEdit {
|
||||
id,
|
||||
body,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::issue::Action::CommentReact {
|
||||
id,
|
||||
reaction,
|
||||
active,
|
||||
} => Self::CommentReact {
|
||||
id,
|
||||
reaction,
|
||||
active,
|
||||
},
|
||||
radicle::issue::Action::CommentRedact { id } => Self::CommentRedact { id },
|
||||
radicle::issue::Action::Label { labels } => Self::Label { labels },
|
||||
radicle::issue::Action::Lifecycle { state } => Self::Lifecycle {
|
||||
state: state.into(),
|
||||
},
|
||||
radicle::issue::Action::Edit { title } => Self::Edit { title },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@ use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use radicle::node::AliasStore;
|
||||
use radicle::profile::Aliases;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use radicle::cob;
|
||||
use radicle::git;
|
||||
use radicle::identity;
|
||||
use radicle::patch;
|
||||
|
||||
use crate::cobs;
|
||||
use crate::cobs::Author;
|
||||
use crate::cobs::FromRadicleAction;
|
||||
|
||||
#[derive(Debug, TS, Serialize)]
|
||||
#[ts(export)]
|
||||
@@ -329,10 +331,7 @@ pub enum Action {
|
||||
state: patch::Lifecycle,
|
||||
},
|
||||
#[serde(rename = "assign")]
|
||||
Assign {
|
||||
#[ts(as = "Vec<String>")]
|
||||
assignees: BTreeSet<identity::Did>,
|
||||
},
|
||||
Assign { assignees: BTreeSet<cobs::Author> },
|
||||
#[serde(rename = "merge")]
|
||||
Merge {
|
||||
#[ts(as = "String")]
|
||||
@@ -512,3 +511,160 @@ pub enum Action {
|
||||
active: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl FromRadicleAction<radicle::patch::Action> for Action {
|
||||
fn from_radicle_action(value: radicle::patch::Action, aliases: &Aliases) -> Self {
|
||||
match value {
|
||||
radicle::patch::Action::ReviewRedact { review } => Self::ReviewRedact { review },
|
||||
radicle::patch::Action::RevisionCommentReact {
|
||||
revision,
|
||||
comment,
|
||||
reaction,
|
||||
active,
|
||||
} => Self::RevisionCommentReact {
|
||||
revision,
|
||||
comment,
|
||||
reaction,
|
||||
active,
|
||||
},
|
||||
radicle::patch::Action::RevisionCommentRedact { revision, comment } => {
|
||||
Self::RevisionCommentRedact { revision, comment }
|
||||
}
|
||||
radicle::patch::Action::RevisionCommentEdit {
|
||||
revision,
|
||||
comment,
|
||||
body,
|
||||
embeds,
|
||||
} => Self::RevisionCommentEdit {
|
||||
revision,
|
||||
comment,
|
||||
body,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::patch::Action::RevisionComment {
|
||||
revision,
|
||||
location,
|
||||
body,
|
||||
reply_to,
|
||||
embeds,
|
||||
} => Self::RevisionComment {
|
||||
revision,
|
||||
location: location.map(Into::into),
|
||||
body,
|
||||
reply_to,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::patch::Action::RevisionRedact { revision } => {
|
||||
Self::RevisionRedact { revision }
|
||||
}
|
||||
radicle::patch::Action::RevisionReact {
|
||||
revision,
|
||||
location,
|
||||
reaction,
|
||||
active,
|
||||
} => Self::RevisionReact {
|
||||
revision,
|
||||
location: location.map(Into::into),
|
||||
reaction,
|
||||
active,
|
||||
},
|
||||
radicle::patch::Action::RevisionEdit {
|
||||
revision,
|
||||
description,
|
||||
embeds,
|
||||
} => Self::RevisionEdit {
|
||||
revision,
|
||||
description,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::patch::Action::Assign { assignees } => Self::Assign {
|
||||
assignees: assignees
|
||||
.iter()
|
||||
.map(|a| Author::new(a, aliases))
|
||||
.collect::<BTreeSet<_>>(),
|
||||
},
|
||||
radicle::patch::Action::Edit { title, target } => Self::Edit { title, target },
|
||||
radicle::patch::Action::Label { labels } => Self::Label { labels },
|
||||
radicle::patch::Action::Lifecycle { state } => Self::Lifecycle { state },
|
||||
radicle::patch::Action::Merge { revision, commit } => Self::Merge { revision, commit },
|
||||
radicle::patch::Action::Revision {
|
||||
description,
|
||||
base,
|
||||
oid,
|
||||
resolves,
|
||||
} => Self::Revision {
|
||||
description,
|
||||
base,
|
||||
oid,
|
||||
resolves,
|
||||
},
|
||||
|
||||
radicle::patch::Action::Review {
|
||||
revision,
|
||||
summary,
|
||||
verdict,
|
||||
labels,
|
||||
} => Self::Review {
|
||||
revision,
|
||||
summary,
|
||||
verdict: verdict.map(Into::into),
|
||||
labels,
|
||||
},
|
||||
radicle::patch::Action::ReviewComment {
|
||||
review,
|
||||
body,
|
||||
location,
|
||||
reply_to,
|
||||
embeds,
|
||||
} => Self::ReviewComment {
|
||||
review,
|
||||
body,
|
||||
location: location.map(Into::into),
|
||||
reply_to,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::patch::Action::ReviewCommentEdit {
|
||||
review,
|
||||
comment,
|
||||
body,
|
||||
embeds,
|
||||
} => Self::ReviewCommentEdit {
|
||||
review,
|
||||
comment,
|
||||
body,
|
||||
embeds: embeds.into_iter().map(Into::into).collect::<Vec<_>>(),
|
||||
},
|
||||
radicle::patch::Action::ReviewCommentReact {
|
||||
review,
|
||||
comment,
|
||||
reaction,
|
||||
active,
|
||||
} => Self::ReviewCommentReact {
|
||||
review,
|
||||
comment,
|
||||
reaction,
|
||||
active,
|
||||
},
|
||||
radicle::patch::Action::ReviewCommentRedact { review, comment } => {
|
||||
Self::ReviewCommentRedact { review, comment }
|
||||
}
|
||||
radicle::patch::Action::ReviewCommentResolve { review, comment } => {
|
||||
Self::ReviewCommentResolve { review, comment }
|
||||
}
|
||||
radicle::patch::Action::ReviewCommentUnresolve { review, comment } => {
|
||||
Self::ReviewCommentUnresolve { review, comment }
|
||||
}
|
||||
radicle::patch::Action::ReviewEdit {
|
||||
review,
|
||||
summary,
|
||||
verdict,
|
||||
labels,
|
||||
} => Self::ReviewEdit {
|
||||
review,
|
||||
summary,
|
||||
verdict: verdict.map(Into::into),
|
||||
labels,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ use radicle::storage::ReadStorage;
|
||||
use radicle::{cob, git, identity};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::cobs::FromRadicleAction;
|
||||
use crate::error::Error;
|
||||
use crate::traits::Profile;
|
||||
|
||||
pub trait Cobs: Profile {
|
||||
#[allow(clippy::unnecessary_filter_map)]
|
||||
fn activity_by_id<A: DeserializeOwned>(
|
||||
fn activity_by_id<A: DeserializeOwned, B: FromRadicleAction<A>>(
|
||||
&self,
|
||||
rid: identity::RepoId,
|
||||
type_name: &cob::TypeName,
|
||||
id: git::Oid,
|
||||
) -> Result<Vec<crate::cobs::Operation<A>>, Error> {
|
||||
) -> Result<Vec<crate::cobs::Operation<B>>, Error> {
|
||||
let profile = self.profile();
|
||||
let aliases = profile.aliases();
|
||||
let repo = profile.storage.repository(rid)?;
|
||||
@@ -23,7 +24,16 @@ pub trait Cobs: Profile {
|
||||
let actions = op
|
||||
.actions
|
||||
.iter()
|
||||
.filter_map(|a| serde_json::from_slice(a).ok())
|
||||
.filter_map(|a| {
|
||||
if let Ok(r) = serde_json::from_slice::<A>(a) {
|
||||
let x = B::from_radicle_action(r, &aliases);
|
||||
Some(x)
|
||||
} else {
|
||||
log::error!("Not able to deserialize the action");
|
||||
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(crate::cobs::Operation {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use radicle::issue::cache::Issues as _;
|
||||
use radicle::node::Handle;
|
||||
use radicle::storage::ReadStorage;
|
||||
@@ -142,7 +144,10 @@ pub trait IssuesMut: Profile {
|
||||
issue.lifecycle(state.into(), &signer)?;
|
||||
}
|
||||
cobs::issue::Action::Assign { assignees } => {
|
||||
issue.assign(assignees, &signer)?;
|
||||
issue.assign(
|
||||
assignees.iter().map(|a| *a.did()).collect::<BTreeSet<_>>(),
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
cobs::issue::Action::Label { labels } => {
|
||||
issue.label(labels, &signer)?;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use radicle::node::Handle;
|
||||
use radicle::patch::cache::Patches as _;
|
||||
use radicle::storage::ReadStorage;
|
||||
@@ -198,7 +200,10 @@ pub trait PatchesMut: Profile {
|
||||
patch.lifecycle(state, &signer)?;
|
||||
}
|
||||
models::patch::Action::Assign { assignees } => {
|
||||
patch.assign(assignees, &signer)?;
|
||||
patch.assign(
|
||||
assignees.iter().map(|a| *a.did()).collect::<BTreeSet<_>>(),
|
||||
&signer,
|
||||
)?;
|
||||
}
|
||||
models::patch::Action::Label { labels } => {
|
||||
patch.label(labels, &signer)?;
|
||||
|
||||
@@ -13,10 +13,10 @@ use tower_http::cors::{self, CorsLayer};
|
||||
|
||||
use radicle::{git, identity};
|
||||
use radicle_types as types;
|
||||
use radicle_types::cobs;
|
||||
use radicle_types::cobs::issue;
|
||||
use radicle_types::cobs::issue::NewIssue;
|
||||
use radicle_types::cobs::CobOptions;
|
||||
use radicle_types::cobs::{self, FromRadicleAction};
|
||||
use radicle_types::domain::inbox::models::notification::NotificationCount;
|
||||
use radicle_types::domain::patch::models;
|
||||
use radicle_types::domain::patch::service::Service;
|
||||
@@ -71,11 +71,11 @@ pub fn router(ctx: Context) -> Router {
|
||||
.route("/diff_stats", post(diff_stats_handler))
|
||||
.route(
|
||||
"/activity_by_issue",
|
||||
post(activity_issue_handler::<issue::Action>),
|
||||
post(activity_issue_handler::<radicle::issue::Action, issue::Action>),
|
||||
)
|
||||
.route(
|
||||
"/activity_by_patch",
|
||||
post(activity_patch_handler::<models::patch::Action>),
|
||||
post(activity_patch_handler::<radicle::patch::Action, models::patch::Action>),
|
||||
)
|
||||
.route("/get_diff", post(diff_handler))
|
||||
.route("/list_issues", post(issues_handler))
|
||||
@@ -259,20 +259,26 @@ struct ActivityBody {
|
||||
pub id: git::Oid,
|
||||
}
|
||||
|
||||
async fn activity_issue_handler<A: serde::Serialize + serde::de::DeserializeOwned>(
|
||||
async fn activity_issue_handler<
|
||||
A: serde::Serialize + serde::de::DeserializeOwned,
|
||||
B: FromRadicleAction<A> + serde::Serialize,
|
||||
>(
|
||||
State(ctx): State<Context>,
|
||||
Json(ActivityBody { rid, id }): Json<ActivityBody>,
|
||||
) -> impl IntoResponse {
|
||||
let activity = ctx.activity_by_id::<A>(rid, &radicle::cob::issue::TYPENAME, id)?;
|
||||
let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::issue::TYPENAME, id)?;
|
||||
|
||||
Ok::<_, Error>(Json(activity))
|
||||
}
|
||||
|
||||
async fn activity_patch_handler<A: serde::Serialize + serde::de::DeserializeOwned>(
|
||||
async fn activity_patch_handler<
|
||||
A: serde::Serialize + serde::de::DeserializeOwned,
|
||||
B: FromRadicleAction<A> + serde::Serialize,
|
||||
>(
|
||||
State(ctx): State<Context>,
|
||||
Json(ActivityBody { rid, id }): Json<ActivityBody>,
|
||||
) -> impl IntoResponse {
|
||||
let activity = ctx.activity_by_id::<A>(rid, &radicle::cob::patch::TYPENAME, id)?;
|
||||
let activity = ctx.activity_by_id::<A, B>(rid, &radicle::cob::patch::TYPENAME, id)?;
|
||||
|
||||
Ok::<_, Error>(Json(activity))
|
||||
}
|
||||
|
||||
@@ -18,11 +18,9 @@
|
||||
formatTimestamp,
|
||||
issueStatusColor,
|
||||
pluralize,
|
||||
publicKeyFromDid,
|
||||
} from "@app/lib/utils";
|
||||
import Icon from "./Icon.svelte";
|
||||
import NodeId from "./NodeId.svelte";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import Id from "./Id.svelte";
|
||||
|
||||
interface Props {
|
||||
@@ -66,7 +64,7 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
function itemDiff(previousState: string[], newState: string[]) {
|
||||
function itemDiff<A>(previousState: A[], newState: A[]) {
|
||||
const removed = previousState.filter(x => !newState.includes(x));
|
||||
const added = newState.filter(x => !previousState.includes(x));
|
||||
return { removed, added };
|
||||
@@ -169,7 +167,7 @@
|
||||
<div class="wrapper">
|
||||
<NodeId {...authorForNodeId(op.author)} />
|
||||
{#if op.previous && op.previous.type === op.type}
|
||||
{@const changed = itemDiff(
|
||||
{@const changed = itemDiff<Author>(
|
||||
op.previous?.assignees ?? [],
|
||||
op.assignees,
|
||||
)}
|
||||
@@ -177,38 +175,20 @@
|
||||
{#if changed.added.length}
|
||||
assigned
|
||||
{#each changed.added as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
{#if changed.removed.length}
|
||||
unassigned
|
||||
{#each changed.removed as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
{/if}
|
||||
{:else}
|
||||
assigned
|
||||
{#each op.assignees as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
<div title={absoluteTimestamp(op.timestamp)}>
|
||||
|
||||
@@ -18,12 +18,10 @@
|
||||
formatTimestamp,
|
||||
patchStatusColor,
|
||||
pluralize,
|
||||
publicKeyFromDid,
|
||||
} from "@app/lib/utils";
|
||||
import Icon from "./Icon.svelte";
|
||||
import Id from "./Id.svelte";
|
||||
import NodeId from "./NodeId.svelte";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
|
||||
interface Props {
|
||||
patchId: string;
|
||||
@@ -67,7 +65,7 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
function itemDiff(previousState: string[], newState: string[]) {
|
||||
function itemDiff<A>(previousState: A[], newState: A[]) {
|
||||
const removed = previousState.filter(x => !newState.includes(x));
|
||||
const added = newState.filter(x => !previousState.includes(x));
|
||||
return { removed, added };
|
||||
@@ -190,44 +188,26 @@
|
||||
<div class="wrapper">
|
||||
<NodeId {...authorForNodeId(op.author)} />
|
||||
{#if op.previous && op.previous.type === op.type}
|
||||
{@const changed = itemDiff(
|
||||
{@const changed = itemDiff<Author>(
|
||||
op.previous?.assignees ?? [],
|
||||
op.assignees,
|
||||
)}
|
||||
{#if changed.added.length}
|
||||
assigned
|
||||
{#each changed.added as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
{#if changed.removed.length}
|
||||
unassigned
|
||||
{#each changed.removed as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
{:else}
|
||||
assigned
|
||||
{#each op.assignees as assignee}
|
||||
{#await invoke<string | null>( "alias", { nid: publicKeyFromDid(assignee) }, ) then alias}
|
||||
<NodeId
|
||||
{...authorForNodeId({
|
||||
did: assignee,
|
||||
alias: alias ?? undefined,
|
||||
})} />
|
||||
{/await}
|
||||
<NodeId {...authorForNodeId(assignee)} />
|
||||
{/each}
|
||||
{/if}
|
||||
<div title={absoluteTimestamp(op.timestamp)}>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
cobId: issue.id,
|
||||
action: {
|
||||
type: "assign",
|
||||
assignees: assignees.map(a => a.did),
|
||||
assignees,
|
||||
},
|
||||
opts: { announce: $nodeRunning && $announce },
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
cobId: patch.id,
|
||||
action: {
|
||||
type: "assign",
|
||||
assignees: assignees.map(a => a.did),
|
||||
assignees,
|
||||
},
|
||||
opts: { announce: $nodeRunning && $announce },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user