From 3764df21797727e002c550071345a61d3f795085 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Nov 2024 00:31:08 +0100 Subject: [PATCH] added logic to emit signals and update store accordingly --- .../zomes/coordinator/generic_zome/src/api.rs | 234 +++++++++++++----- .../zomes/coordinator/generic_zome/src/lib.rs | 30 +++ .../zomes/integrity/generic_zome/src/lib.rs | 8 + .../generic_zome/src/link_tag_content.rs | 4 +- lib/src/index.ts | 130 +++++++++- lib/src/types.ts | 73 ++++-- ui/src/elements/all-posts.ts | 46 ++-- ui/src/elements/post-detail.ts | 39 +-- 8 files changed, 432 insertions(+), 132 deletions(-) diff --git a/dnas/generic_dna/zomes/coordinator/generic_zome/src/api.rs b/dnas/generic_dna/zomes/coordinator/generic_zome/src/api.rs index c42fd6e..2ac8d6c 100644 --- a/dnas/generic_dna/zomes/coordinator/generic_zome/src/api.rs +++ b/dnas/generic_dna/zomes/coordinator/generic_zome/src/api.rs @@ -1,3 +1,4 @@ +use crate::{NodeLink, Signal, Thing}; use generic_zome_integrity::*; use hdk::prelude::*; @@ -9,14 +10,6 @@ pub enum LinkDirection { Bidirectional, } -#[derive(Serialize, Deserialize, Clone, Debug)] -#[serde(tag = "type", content = "id")] -pub enum NodeId { - Agent(AgentPubKey), - Anchor(String), - Thing(ActionHash), -} - #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(tag = "type", content = "content")] pub enum Node { @@ -32,15 +25,6 @@ pub struct LinkInput { pub tag: Option>, } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Thing { - pub id: ActionHash, - pub content: String, - pub creator: AgentPubKey, - pub created_at: Timestamp, - pub updated_at: Option, -} - #[derive(Serialize, Deserialize, Debug)] pub struct CreateThingInput { pub content: String, @@ -58,23 +42,47 @@ pub fn create_thing(input: CreateThingInput) -> ExternResult { WasmErrorInner::Guest("Failed to get record that was just created.".into()) ))?; + let mut links_created: Vec = Vec::new(); + // 2. Create all links as necessary - match input.links { + match input.links.clone() { Some(links) => { for link in links { - create_link_from_node_by_hash(thing_id.clone().into(), link)?; + create_link_from_node_by_id(NodeId::Thing(thing_id.clone()), link.clone())?; + links_created.push(NodeLink { + src: NodeId::Thing(thing_id.clone()), + dst: link.node_id.clone(), + }); + if let LinkDirection::Bidirectional = link.direction { + links_created.push(NodeLink { + src: link.node_id, + dst: NodeId::Thing(thing_id.clone()), + }); + } } } None => (), } - Ok(Thing { + let thing = Thing { id: thing_id, content: input.content, creator: thing_record.action().author().clone(), created_at: thing_record.action().timestamp(), updated_at: None, - }) + }; + + // 3. Emit signals to the frontend + emit_signal(Signal::ThingCreated { + thing: thing.clone(), + })?; + if let Some(_) = input.links.clone() { + emit_signal(Signal::LinksCreated { + links: links_created, + })?; + } + + Ok(thing) } /// Gets the latest known version of a Thing @@ -196,13 +204,20 @@ pub fn update_thing(input: UpdateThingInput) -> ExternResult { (), )?; - Ok(Thing { + let thing = Thing { id: input.thing_id, content: input.updated_content, creator: thing_record.action().author().clone(), created_at: thing_record.action().timestamp(), updated_at: Some(updated_thing_record.action().timestamp()), - }) + }; + + // 3. Emit signals to the frontend + emit_signal(Signal::ThingUpdated { + thing: thing.clone(), + })?; + + Ok(thing) } #[derive(Serialize, Deserialize, Debug)] @@ -225,6 +240,8 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { } }; + let mut links_deleted: Vec = Vec::new(); + // 1. Delete the original Thing entry (don't care about updates as they are anyway // not retreivable without the original Thing entry) delete_entry(input.thing_id.clone())?; @@ -238,6 +255,10 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { let link_tag_content = deserialize_link_tag(link.tag.0)?; if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: NodeId::Thing(input.thing_id.clone()), + }); } } let links_to_things = get_links( @@ -247,6 +268,10 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { let link_tag_content = deserialize_link_tag(link.tag.0)?; if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: NodeId::Thing(input.thing_id.clone()), + }); } } let links_to_anchors = get_links( @@ -256,22 +281,26 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { let link_tag_content = deserialize_link_tag(link.tag.0)?; if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: NodeId::Thing(input.thing_id.clone()), + }); } } } // 3. Delete all links from the creator to the Thing if input.delete_links_from_creator { - let links_from_creator = get_links( - GetLinksInputBuilder::try_new( - thing_record.action().author().clone(), - LinkTypes::ToAgent, - )? - .build(), - )?; + let creator = thing_record.action().author(); + let links_from_creator = + get_links(GetLinksInputBuilder::try_new(creator.clone(), LinkTypes::ToAgent)?.build())?; for link in links_from_creator { if link.target == input.thing_id.clone().into() { delete_link(link.create_link_hash)?; + links_deleted.push(NodeLink { + src: NodeId::Agent(creator.clone()), + dst: NodeId::Thing(input.thing_id.clone()), + }); } } } @@ -325,26 +354,40 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { match link_to_delete.node_id { NodeId::Agent(agent) => { delete_links_for_base_with_target( - agent.into(), + agent.clone().into(), input.thing_id.clone().into(), LinkTypes::ToThing, )?; + // If multiple links got deleted with the same base and target we assume that + // they get deduplicated in the frontend anyway so we only push it once + links_deleted.push(NodeLink { + src: NodeId::Agent(agent), + dst: NodeId::Thing(input.thing_id.clone()), + }); } NodeId::Anchor(anchor) => { - let path = Path::from(anchor); + let path = Path::from(anchor.clone()); let path_entry_hash = path.path_entry_hash()?; delete_links_for_base_with_target( path_entry_hash.into(), input.thing_id.clone().into(), LinkTypes::ToThing, )?; + links_deleted.push(NodeLink { + src: NodeId::Anchor(anchor), + dst: NodeId::Thing(input.thing_id.clone()), + }); } NodeId::Thing(action_hash) => { delete_links_for_base_with_target( - action_hash.into(), + action_hash.clone().into(), input.thing_id.clone().into(), LinkTypes::ToThing, )?; + links_deleted.push(NodeLink { + src: NodeId::Thing(action_hash), + dst: NodeId::Thing(input.thing_id.clone()), + }); } } } @@ -352,6 +395,14 @@ pub fn delete_thing(input: DeleteThingInput) -> ExternResult<()> { } } + // 4. Emit signals to the frontend + emit_signal(Signal::ThingDeleted { + id: input.thing_id.clone(), + })?; + emit_signal(Signal::LinksDeleted { + links: links_deleted, + })?; + Ok(()) } @@ -416,7 +467,7 @@ pub fn get_linked_anchors(node_id: NodeId) -> ExternResult> { .into_iter() .map(|l| deserialize_link_tag(l.tag.0).ok()) .filter_map(|c| c) - .map(|c| c.anchor) + .map(|c| anchor_string_from_node_id(c.target_node_id)) .filter_map(|a| a) .collect()) } @@ -455,16 +506,31 @@ pub struct CreateOrDeleteLinksInput { #[hdk_extern] pub fn create_links_from_node(input: CreateOrDeleteLinksInput) -> ExternResult<()> { - let base: HoloHash = linkable_hash_from_node_id(input.src)?; + let mut links_created: Vec = Vec::new(); for link in input.links { - create_link_from_node_by_hash(base.clone(), link)?; + create_link_from_node_by_id(input.src.clone(), link.clone())?; + links_created.push(NodeLink { + src: input.src.clone(), + dst: link.node_id.clone(), + }); + if let LinkDirection::Bidirectional = link.direction { + links_created.push(NodeLink { + src: link.node_id, + dst: input.src.clone(), + }); + } } + emit_signal(Signal::LinksCreated { + links: links_created, + })?; Ok(()) } #[hdk_extern] pub fn delete_links_from_node(input: CreateOrDeleteLinksInput) -> ExternResult<()> { - let base = linkable_hash_from_node_id(input.src)?; + let base = linkable_hash_from_node_id(input.src.clone())?; + + let mut links_deleted: Vec = Vec::new(); let anchor_link_inputs = input .links @@ -510,8 +576,16 @@ pub fn delete_links_from_node(input: CreateOrDeleteLinksInput) -> ExternResult<( if target == link.target && link_input.tag == link_tag_content.tag { if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: input.src.clone(), + }); } delete_link(link.create_link_hash)?; + links_deleted.push(NodeLink { + src: input.src.clone(), + dst: link_input.node_id.clone(), + }); } } } @@ -528,8 +602,16 @@ pub fn delete_links_from_node(input: CreateOrDeleteLinksInput) -> ExternResult<( if target == link.target && link_input.tag == link_tag_content.tag { if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: input.src.clone(), + }); } delete_link(link.create_link_hash)?; + links_deleted.push(NodeLink { + src: input.src.clone(), + dst: link_input.node_id.clone(), + }); } } } @@ -546,46 +628,61 @@ pub fn delete_links_from_node(input: CreateOrDeleteLinksInput) -> ExternResult<( if target == link.target && link_input.tag == link_tag_content.tag { if let Some(backlink_action_hash) = link_tag_content.backlink_action_hash { delete_link(backlink_action_hash)?; + links_deleted.push(NodeLink { + src: link_tag_content.target_node_id, + dst: input.src.clone(), + }); } delete_link(link.create_link_hash)?; + links_deleted.push(NodeLink { + src: input.src.clone(), + dst: link_input.node_id.clone(), + }); } } } } + + // Emit signals about deleted links to the frontend + emit_signal(Signal::LinksCreated { + links: links_deleted, + })?; + Ok(()) } -fn create_link_from_node_by_hash(src: AnyLinkableHash, link: LinkInput) -> ExternResult<()> { - match link.node_id { +fn create_link_from_node_by_id(src: NodeId, link: LinkInput) -> ExternResult<()> { + let base: HoloHash = linkable_hash_from_node_id(src.clone())?; + match link.node_id.clone() { NodeId::Agent(agent) => match link.direction { LinkDirection::To => { create_link( - src.clone(), + base.clone(), agent, LinkTypes::ToAgent, - derive_link_tag(link.tag, None, None)?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::From => { create_link( agent, - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag, None, None)?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::Bidirectional => { let backlink_action_hash = create_link( agent.clone(), - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag.clone(), None, None)?, + derive_link_tag(link.tag.clone(), None, link.node_id.clone())?, )?; create_link( - src.clone(), + base.clone(), agent, LinkTypes::ToAgent, - derive_link_tag(link.tag, Some(backlink_action_hash), None)?, + derive_link_tag(link.tag, Some(backlink_action_hash), src.clone())?, )?; } }, @@ -595,32 +692,32 @@ fn create_link_from_node_by_hash(src: AnyLinkableHash, link: LinkInput) -> Exter match link.direction { LinkDirection::To => { create_link( - src.clone(), + base.clone(), path_entry_hash, LinkTypes::ToAgent, - derive_link_tag(link.tag, None, Some(anchor))?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::From => { create_link( path_entry_hash, - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag, None, Some(anchor))?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::Bidirectional => { let backlink_action_hash = create_link( path_entry_hash.clone(), - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag.clone(), None, Some(anchor.clone()))?, + derive_link_tag(link.tag.clone(), None, link.node_id.clone())?, )?; create_link( - src.clone(), + base.clone(), path_entry_hash, LinkTypes::ToAgent, - derive_link_tag(link.tag, Some(backlink_action_hash), Some(anchor))?, + derive_link_tag(link.tag, Some(backlink_action_hash), src.clone())?, )?; } } @@ -628,32 +725,32 @@ fn create_link_from_node_by_hash(src: AnyLinkableHash, link: LinkInput) -> Exter NodeId::Thing(action_hash) => match link.direction { LinkDirection::To => { create_link( - src.clone(), + base.clone(), action_hash, LinkTypes::ToAgent, - derive_link_tag(link.tag, None, None)?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::From => { create_link( action_hash, - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag, None, None)?, + derive_link_tag(link.tag, None, link.node_id.clone())?, )?; } LinkDirection::Bidirectional => { let backlink_action_hash = create_link( action_hash.clone(), - src.clone(), + base.clone(), LinkTypes::ToThing, - derive_link_tag(link.tag.clone(), None, None)?, + derive_link_tag(link.tag.clone(), None, link.node_id.clone())?, )?; create_link( - src.clone(), + base.clone(), action_hash, LinkTypes::ToAgent, - derive_link_tag(link.tag, Some(backlink_action_hash), None)?, + derive_link_tag(link.tag, Some(backlink_action_hash), src.clone())?, )?; } }, @@ -672,12 +769,12 @@ fn linkable_hash_from_node_id(node_id: NodeId) -> ExternResult fn derive_link_tag( input: Option>, backlink_action_hash: Option, - anchor: Option, + target_node_id: NodeId, ) -> ExternResult { let link_tag_content = LinkTagContent { tag: input, backlink_action_hash, - anchor, + target_node_id, }; let serialized_content = serialize_link_tag(link_tag_content)?; Ok(LinkTag::from(serialized_content)) @@ -727,3 +824,10 @@ fn thing_record_to_thing(record: Record) -> ExternResult { updated_at: None, }) } + +fn anchor_string_from_node_id(node_id: NodeId) -> Option { + match node_id { + NodeId::Anchor(s) => Some(s), + _ => None, + } +} diff --git a/dnas/generic_dna/zomes/coordinator/generic_zome/src/lib.rs b/dnas/generic_dna/zomes/coordinator/generic_zome/src/lib.rs index d41fa24..7b0e1be 100644 --- a/dnas/generic_dna/zomes/coordinator/generic_zome/src/lib.rs +++ b/dnas/generic_dna/zomes/coordinator/generic_zome/src/lib.rs @@ -8,10 +8,40 @@ pub fn init() -> ExternResult { Ok(InitCallbackResult::Pass) } +#[derive(Serialize, Deserialize, Debug)] +pub struct NodeLink { + src: NodeId, + dst: NodeId, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Thing { + pub id: ActionHash, + pub content: String, + pub creator: AgentPubKey, + pub created_at: Timestamp, + pub updated_at: Option, +} + /// Don't modify this enum if you want the scaffolding tool to generate appropriate signals for your entries and links #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type")] pub enum Signal { + ThingCreated { + thing: Thing, + }, + ThingUpdated { + thing: Thing, + }, + ThingDeleted { + id: ActionHash, + }, + LinksCreated { + links: Vec, + }, + LinksDeleted { + links: Vec, + }, LinkCreated { action: SignedActionHashed, link_type: LinkTypes, diff --git a/dnas/generic_dna/zomes/integrity/generic_zome/src/lib.rs b/dnas/generic_dna/zomes/integrity/generic_zome/src/lib.rs index 443171e..ee9d726 100644 --- a/dnas/generic_dna/zomes/integrity/generic_zome/src/lib.rs +++ b/dnas/generic_dna/zomes/integrity/generic_zome/src/lib.rs @@ -28,6 +28,14 @@ pub enum LinkTypes { ToAnchor, } +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "type", content = "id")] +pub enum NodeId { + Agent(AgentPubKey), + Anchor(String), + Thing(ActionHash), +} + // Validation you perform during the genesis process. Nobody else on the network performs it, only you. // There *is no* access to network calls in this callback #[hdk_extern] diff --git a/dnas/generic_dna/zomes/integrity/generic_zome/src/link_tag_content.rs b/dnas/generic_dna/zomes/integrity/generic_zome/src/link_tag_content.rs index 0ee2add..0ae964c 100644 --- a/dnas/generic_dna/zomes/integrity/generic_zome/src/link_tag_content.rs +++ b/dnas/generic_dna/zomes/integrity/generic_zome/src/link_tag_content.rs @@ -1,5 +1,7 @@ use hdi::prelude::*; +use crate::NodeId; + #[derive(Serialize, Deserialize, SerializedBytes, Debug)] pub struct LinkTagContent { pub tag: Option>, @@ -11,7 +13,7 @@ pub struct LinkTagContent { // For links to anchors we store the anchor string as well to be able // to retrieve the anchor string that they're pointing to directly // from the link - pub anchor: Option, + pub target_node_id: NodeId, } pub fn serialize_link_tag(link_tag_content: LinkTagContent) -> ExternResult> { diff --git a/lib/src/index.ts b/lib/src/index.ts index 5ddc2fd..308fa9e 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -29,6 +29,7 @@ export * from "./types"; import { derived, + get, Readable, Unsubscriber, Writable, @@ -47,11 +48,12 @@ export type AsyncStatus = const DEFAULT_POLLING_FREQUENCY = 10000; export class NodeStore { - client: SimpleHolochain; - nodeId: NodeId; + private client: SimpleHolochain; + private nodeId: NodeId; private subscribers: number[] = []; - private nodeStore: Writable> = writable({ + + nodeStore: Writable> = writable({ status: "pending", }); @@ -98,16 +100,22 @@ export class NodeStore { ); let linkedNodeIds = await this.client.getAllLinkedNodeIds(this.nodeId); + console.log("@pollStore: Linked node ids: ", linkedNodeIds); if (this.nodeId.type === "Thing") { const latestThing = await this.client.getThing(this.nodeId.id); if (!latestThing) { - this.nodeStore.set({ - status: "error", - error: `Failed to get Thing record for thing with id ${encodeHashToBase64( - this.nodeId.id - )}`, - }); + const currentThing = get(this.nodeStore); + // If it is already complete, we assume that the Thing arrived through emit_signal + // otherwise we set it to "error" + if (currentThing.status !== "complete") { + this.nodeStore.set({ + status: "error", + error: `Failed to get Thing record for thing with id ${encodeHashToBase64( + this.nodeId.id + )}`, + }); + } return; } const content: NodeContent = { @@ -182,6 +190,89 @@ export class SimpleHolochain { this.roleName = roleName; this.zomeName = zomeName; // TODO set up signal listener. Potentially emit signal to conductor + this.zomeClient.onSignal(async (signal) => { + switch (signal.type) { + case "ThingCreated": { + // ignore since things are probably mostly discovered through anchors and then the thing will be polled + const nodeStore = this.nodeStore({ + type: "Thing", + id: signal.thing.id, + }); + nodeStore.nodeStore.update((content) => { + if (content.status === "complete") { + content.value.content.content = signal.thing; + return content; + } + return { + status: "complete", + value: { + content: { type: "Thing", content: signal.thing }, + linkedNodeIds: [], + }, + }; + }); + break; + } + case "ThingUpdated": { + const nodeStore = this.nodeStore({ + type: "Thing", + id: signal.thing.id, + }); + nodeStore.nodeStore.update((content) => { + if (content.status === "complete") { + content.value.content.content = signal.thing; + return content; + } + return { + status: "complete", + value: { + content: { type: "Thing", content: signal.thing }, + linkedNodeIds: [], + }, + }; + }); + break; + } + case "ThingDeleted": { + break; + } + case "LinksCreated": { + console.log("Got LINKS_CREATED SIGNAL!!"); + + signal.links.forEach(({ src, dst }) => { + const nodeStore = this.nodeStore(src); + nodeStore.nodeStore.update((store) => { + if (store.status === "complete") { + const currentLinkedNodeIds = store.value.linkedNodeIds; + const nodeExists = currentLinkedNodeIds.find((nodeId) => + areNodesEqual(dst, nodeId) + ); + if (nodeExists) return store; + currentLinkedNodeIds.push(dst); + store.value.linkedNodeIds = currentLinkedNodeIds; + } + return store; + }); + }); + break; + } + case "LinksDeleted": { + signal.links.forEach(({ src, dst }) => { + const nodeStore = this.nodeStore(src); + nodeStore.nodeStore.update((store) => { + if (store.status === "complete") { + const currentLinkedNodeIds = store.value.linkedNodeIds; + store.value.linkedNodeIds = currentLinkedNodeIds.filter( + (nodeId) => !areNodesEqual(dst, nodeId) + ); + } + return store; + }); + }); + break; + } + } + }); } static async connect(options: AppWebsocketConnectionOptions = {}) { @@ -194,7 +285,7 @@ export class SimpleHolochain { return new SimpleHolochain(client, zomeClient); } - nodeStore(nodeId: NodeId): NodeStore { + private nodeStore(nodeId: NodeId): NodeStore { switch (nodeId.type) { case "Agent": { const agentId = encodeHashToBase64(nodeId.id); @@ -219,6 +310,14 @@ export class SimpleHolochain { } } + subscribeToNode( + nodeId: NodeId, + cb: (value: AsyncStatus) => any + ): Unsubscriber { + const nodeStore = this.nodeStore(nodeId); + return nodeStore.subscribe(cb); + } + /** * Creates a "Thing", i.e. an arbitrary piece of content in the DHT. You are responsible * yourself for making sure that the content adheres to the format you want @@ -420,3 +519,14 @@ function linkInputToRustFormat(linkInput: LinkInput): LinkInputRust { tag: linkInput.tag, }; } + +function areNodesEqual(nodeId_a: NodeId, nodeId_b: NodeId): boolean { + if (nodeId_a.type !== nodeId_b.type) return false; + if (nodeId_a.type === "Agent" && nodeId_b.type === "Agent") + return encodeHashToBase64(nodeId_a.id) === encodeHashToBase64(nodeId_b.id); + if (nodeId_a.type === "Thing" && nodeId_b.type === "Thing") + return encodeHashToBase64(nodeId_a.id) === encodeHashToBase64(nodeId_b.id); + if (nodeId_a.type === "Anchor" && nodeId_b.type === "Anchor") + return nodeId_a.id === nodeId_b.id; + return false; +} diff --git a/lib/src/types.ts b/lib/src/types.ts index 8b91a39..eb91032 100644 --- a/lib/src/types.ts +++ b/lib/src/types.ts @@ -7,72 +7,97 @@ import { DeleteLink, SignedActionHashed, Update, -} from '@holochain/client'; +} from "@holochain/client"; export type GenericZomeSignal = | { - type: 'EntryCreated'; + type: "ThingCreated"; + thing: Thing; + } + | { + type: "ThingUpdated"; + thing: Thing; + } + | { + type: "ThingDeleted"; + id: ActionHash; + } + | { + type: "LinksCreated"; + links: NodeLink[]; + } + | { + type: "LinksDeleted"; + links: NodeLink[]; + } + | { + type: "EntryCreated"; action: SignedActionHashed; app_entry: EntryTypes; } | { - type: 'EntryUpdated'; + type: "EntryUpdated"; action: SignedActionHashed; app_entry: EntryTypes; original_app_entry: EntryTypes; } | { - type: 'EntryDeleted'; + type: "EntryDeleted"; action: SignedActionHashed; original_app_entry: EntryTypes; } | { - type: 'LinkCreated'; + type: "LinkCreated"; action: SignedActionHashed; link_type: string; } | { - type: 'LinkDeleted'; + type: "LinkDeleted"; action: SignedActionHashed; link_type: string; }; /* dprint-ignore-start */ -export type EntryTypes = { type: 'Thing' } & ThingEntry; +export type EntryTypes = { type: "Thing" } & ThingEntry; /* dprint-ignore-end */ export interface ThingEntry { content: string; } +export type NodeLink = { + src: NodeId, + dst: NodeId, +} + /** * A node in the graph can be of three distinct types, identified in different ways */ export type NodeId = | { - type: 'Anchor'; + type: "Anchor"; id: string; } | { - type: 'Thing'; + type: "Thing"; id: ThingId; // "id" --> original action hash } | { - type: 'Agent'; + type: "Agent"; id: AgentPubKey; }; export type NodeContent = | { - type: 'Anchor'; + type: "Anchor"; content: string; } | { - type: 'Thing'; + type: "Thing"; content: Thing; } | { - type: 'Agent'; + type: "Agent"; content: AgentPubKey; }; @@ -110,13 +135,13 @@ export type LinkInput = { export type LinkDirectionRust = | { - type: 'From'; + type: "From"; } | { - type: 'To'; + type: "To"; } | { - type: 'Bidirectional'; + type: "Bidirectional"; }; export type LinkInputRust = { @@ -136,13 +161,13 @@ export type UpdateThingInput = { }; export type DeleteThingInput = { - thing_id: ActionHash, - delete_backlinks: boolean, - delete_links_from_creator: boolean, - delete_links?: LinkInputRust[], -} + thing_id: ActionHash; + delete_backlinks: boolean; + delete_links_from_creator: boolean; + delete_links?: LinkInputRust[]; +}; export type CreateOrDeleteLinksInput = { - src: NodeId, - links: LinkInputRust[], -}; \ No newline at end of file + src: NodeId; + links: LinkInputRust[]; +}; diff --git a/ui/src/elements/all-posts.ts b/ui/src/elements/all-posts.ts index c70aa6c..d469152 100644 --- a/ui/src/elements/all-posts.ts +++ b/ui/src/elements/all-posts.ts @@ -6,7 +6,12 @@ import './post-detail'; import './edit-post'; import { simpleHolochainContext } from '../contexts'; -import { AsyncStatus, NodeId, NodeStoreContent, SimpleHolochain } from '@holochain/simple-holochain'; +import { + AsyncStatus, + NodeId, + NodeStoreContent, + SimpleHolochain, +} from '@holochain/simple-holochain'; @customElement('all-posts') export class AllPosts extends LitElement { @@ -14,20 +19,23 @@ export class AllPosts extends LitElement { client!: SimpleHolochain; @state() - nodeContent: AsyncStatus = { status: "pending" }; + nodeContent: AsyncStatus = { status: 'pending' }; @state() nodeStoreUnsubscriber: (() => void) | undefined; firstUpdated() { - const nodeStore = this.client.nodeStore({ - type: "Anchor", - id: "ALL_POSTS", - }) - this.nodeStoreUnsubscriber = nodeStore.subscribe((val) => { - console.log("Got new wal: ", val); - this.nodeContent = val; - }) + this.nodeStoreUnsubscriber = this.client.subscribeToNode( + { + type: 'Anchor', + id: 'ALL_POSTS', + }, + val => { + console.log('Got new wal: ', val); + this.nodeContent = val; + this.requestUpdate(); + } + ); } disconnectedCallback(): void { @@ -35,16 +43,20 @@ export class AllPosts extends LitElement { } renderNodes(nodeIds: NodeId[]) { - const thingNodes = nodeIds.filter((nodeId) => nodeId.type === "Thing"); - console.log("Rendering thingNodes: ", thingNodes); - return thingNodes.map((node) => html` - - `) + const thingNodes = nodeIds.filter(nodeId => nodeId.type === 'Thing'); + console.log('Rendering thingNodes: ', thingNodes); + return thingNodes.map( + node => html` ` + ); } render() { - if (this.nodeContent.status === "error") return html`
Error fetching the thing: ${this.nodeContent.error}
`; - if (this.nodeContent.status === "pending") return html``; + if (this.nodeContent.status === 'error') + return html`
+ Error fetching the thing: ${this.nodeContent.error} +
`; + if (this.nodeContent.status === 'pending') + return html``; return this.renderNodes(this.nodeContent.value.linkedNodeIds); } } diff --git a/ui/src/elements/post-detail.ts b/ui/src/elements/post-detail.ts index 6729e98..38ea616 100644 --- a/ui/src/elements/post-detail.ts +++ b/ui/src/elements/post-detail.ts @@ -1,7 +1,4 @@ -import { - ActionHash, - HolochainError, -} from '@holochain/client'; +import { ActionHash, HolochainError } from '@holochain/client'; import { consume } from '@lit/context'; import { html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; @@ -9,7 +6,13 @@ import { customElement, property, state } from 'lit/decorators.js'; import './edit-post'; import { simpleHolochainContext } from '../contexts'; -import { AsyncStatus, LinkDirection, NodeStoreContent, SimpleHolochain, Thing } from '@holochain/simple-holochain'; +import { + AsyncStatus, + LinkDirection, + NodeStoreContent, + SimpleHolochain, + Thing, +} from '@holochain/simple-holochain'; @customElement('post-detail') export class PostDetail extends LitElement { @@ -26,7 +29,7 @@ export class PostDetail extends LitElement { _editing = false; @state() - nodeContent: AsyncStatus = { status: "pending" }; + nodeContent: AsyncStatus = { status: 'pending' }; @state() nodeStoreUnsubscriber: (() => void) | undefined; @@ -37,13 +40,15 @@ export class PostDetail extends LitElement { `The thingHash property is required for the thing-detail element` ); } - const nodeStore = this.client.nodeStore({ - type: "Thing", - id: this.thingHash, - }) - this.nodeStoreUnsubscriber = nodeStore.subscribe((val) => { - this.nodeContent = val; - }) + this.nodeStoreUnsubscriber = this.client.subscribeToNode( + { + type: 'Thing', + id: this.thingHash, + }, + val => { + this.nodeContent = val; + } + ); } disconnectedCallback(): void { @@ -92,8 +97,12 @@ export class PostDetail extends LitElement { } render() { - if (this.nodeContent.status === "error") return html`
Error fetching the Thing: ${this.nodeContent.error}
`; - if (this.nodeContent.status === "pending") return html``; + if (this.nodeContent.status === 'error') + return html`
+ Error fetching the Thing: ${this.nodeContent.error} +
`; + if (this.nodeContent.status === 'pending') + return html``; // if (this._editing) { // return html` //