diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 31ba10d..1917ddd 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -8,6 +8,7 @@ import "@holochain-open-dev/profiles/dist/elements/profile-prompt.js"; import "@holochain-open-dev/profiles/dist/elements/create-profile.js"; import KDLogoIcon from "./icons/KDLogoIcon.svelte"; + import { appletServices } from './we'; const appId = import.meta.env.VITE_APP_ID ? import.meta.env.VITE_APP_ID : 'kando' const roleName = 'kando' @@ -16,6 +17,7 @@ const url = `ws://localhost:${appPort}`; let client: AppAgentWebsocket + let weClient: WeClient let profilesStore : ProfilesStore|undefined = undefined let connected = false @@ -45,7 +47,7 @@ profilesClient = new ProfilesClient(client, appId); } else { - const weClient = await WeClient.connect(); + weClient = await WeClient.connect(appletServices); if ( !(weClient.renderInfo.type === "applet-view") @@ -81,7 +83,7 @@ {:else if $prof.status=="error"} Error when loading profile: {$prof.error} {:else} - + {/if} diff --git a/ui/src/CardDetails.svelte b/ui/src/CardDetails.svelte index d27448b..89c2ece 100644 --- a/ui/src/CardDetails.svelte +++ b/ui/src/CardDetails.svelte @@ -17,6 +17,7 @@ import { Marked, Renderer } from "@ts-stack/markdown"; import SvgIcon from "./SvgIcon.svelte"; import ClickEdit from './ClickEdit.svelte'; + import { hrlB64WithContextToRaw, hrlWithContextToB64 } from './util'; Marked.setOptions ({ @@ -45,7 +46,7 @@ $: selectedAvatarsForSelect = selectedAvatars.join(" ") $: allProfiles = store.profilesStore.allProfiles - const DEFAULT_PROPS = {title:"", description:"", category: "", agents:[], labels:[]} + const DEFAULT_PROPS = {title:"", description:"", category: "", agents:[], labels:[], attachments:[]} //let props:CardProps = DEFAULT_PROPS let cardId:uuidv1 = "" @@ -235,6 +236,21 @@ let editDescriptionElement + const addAttachment = async () => { + const hrl = await store.weClient.userSelectHrl() + if (hrl) { + if (props.attachments === undefined) { + props.attachments = [] + } + props.attachments.push(hrlWithContextToB64(hrl)) + handleSave(props) + } + } + const removeAttachment = (idx: number) => { + props.attachments.splice(idx,1) + handleSave(props) + } + {/if} + {#if store.weClient} +
+ +
+ {#if props.attachments} +
+ {#each props.attachments as attachment, i} + {#await store.weClient.entryInfo(hrlB64WithContextToRaw(attachment).hrl)} + + {:then { entryInfo }} + { + const hrl = hrlB64WithContextToRaw(attachment) + store.weClient.openHrl(hrl.hrl, hrl.context) + }} + style="display:flex;flex-direction:row;margin-right:5px;margin-left:10px"> + {entryInfo.name} + + { + removeAttachment(i) + }} + > + + + {:catch error} + Oops. something's wrong. + {/await} + {/each} +
+ {/if} + {/if} +
diff --git a/ui/src/CardEditor.svelte b/ui/src/CardEditor.svelte index 0394ad2..3d6a821 100644 --- a/ui/src/CardEditor.svelte +++ b/ui/src/CardEditor.svelte @@ -24,7 +24,7 @@ export let categories: Array export let title - const DEFAULT_PROPS = {title:"", description:"", category: "", agents:[], labels:[]} + const DEFAULT_PROPS = {title:"", description:"", category: "", agents:[], labels:[], attachments: []} let props:CardProps = DEFAULT_PROPS let cardId:uuidv1 = "" diff --git a/ui/src/Controller.svelte b/ui/src/Controller.svelte index 953bc3b..af8ee9e 100644 --- a/ui/src/Controller.svelte +++ b/ui/src/Controller.svelte @@ -7,16 +7,19 @@ import type { SynStore } from '@holochain-syn/store'; import type { ProfilesStore } from "@holochain-open-dev/profiles"; import BoardMenu from "./BoardMenu.svelte"; + import type { WeClient } from '@lightningrodlabs/we-applet'; export let roleName = "" export let client : AppAgentClient + export let weClient : WeClient export let profilesStore : ProfilesStore let store: KanDoStore = new KanDoStore ( - profilesStore, - client, - roleName, - ); + weClient, + profilesStore, + client, + roleName, + ); let synStore: SynStore = store.synStore $: activeBoardHash = store.boardList.activeBoardHash diff --git a/ui/src/board.ts b/ui/src/board.ts index 124ff30..14a2511 100644 --- a/ui/src/board.ts +++ b/ui/src/board.ts @@ -3,6 +3,7 @@ import { get, type Readable } from "svelte/store"; import { v1 as uuidv1 } from "uuid"; import { type AgentPubKey, type EntryHash, type EntryHashB64, encodeHashToBase64, type AgentPubKeyB64, type Timestamp } from "@holochain/client"; import { BoardType } from "./boardList"; +import type { HrlB64WithContext } from "@lightningrodlabs/we-applet"; export class LabelDef { type: uuidv1 @@ -24,6 +25,7 @@ export type CardProps = { category: uuidv1, agents: Array, labels: Array, + attachments: Array } export type Comment = { diff --git a/ui/src/boardList.ts b/ui/src/boardList.ts index e0d8d4c..708dbbd 100644 --- a/ui/src/boardList.ts +++ b/ui/src/boardList.ts @@ -108,11 +108,7 @@ export class BoardList { } async setActiveCard(cardId: string | undefined) { - this.activeCard.update((n) => { - console.log("old card", n) - console.log("active card", cardId) - - return cardId} ) + this.activeCard.update((n) => {return cardId} ) } async setActiveBoard(hash: EntryHash | undefined) : Promise { diff --git a/ui/src/store.ts b/ui/src/store.ts index 1c1b539..8b33195 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -16,6 +16,7 @@ import type { v1 as uuidv1 } from "uuid"; import { get, writable, type Writable } from "svelte/store"; import type { ProfilesStore } from '@holochain-open-dev/profiles'; import type { BoardState } from './board'; +import type { WeClient } from '@lightningrodlabs/we-applet'; TimeAgo.addDefaultLocale(en) @@ -93,6 +94,7 @@ export class KanDoStore { } constructor( + public weClient : WeClient, public profilesStore: ProfilesStore, protected clientIn: AppAgentClient, protected roleName: RoleName, diff --git a/ui/src/svgIcons.ts b/ui/src/svgIcons.ts index 13e7511..cc1e999 100644 --- a/ui/src/svgIcons.ts +++ b/ui/src/svgIcons.ts @@ -21,5 +21,6 @@ export const svgIcons = { faClose: ``, faBug: ``, faBars: ``, - faClone: `` + faClone: ``, + faPaperclip: `` } \ No newline at end of file diff --git a/ui/src/util.ts b/ui/src/util.ts index 787d57c..3ce6a3d 100644 --- a/ui/src/util.ts +++ b/ui/src/util.ts @@ -1,3 +1,6 @@ +import { decodeHashFromBase64, encodeHashToBase64 } from "@holochain/client"; +import type { HrlB64WithContext, HrlWithContext } from "@lightningrodlabs/we-applet"; + export function onVisible(element, callback) { new IntersectionObserver((entries, observer) => { entries.forEach(entry => { @@ -6,4 +9,18 @@ export function onVisible(element, callback) { } }); }).observe(element); +} + +export function hrlWithContextToB64(hrl: HrlWithContext): HrlB64WithContext { + return { + hrl: [encodeHashToBase64(hrl.hrl[0]), encodeHashToBase64(hrl.hrl[1])], + context: hrl.context, + }; +} + +export function hrlB64WithContextToRaw(hrlB64: HrlB64WithContext): HrlWithContext { + return { + hrl: [decodeHashFromBase64(hrlB64.hrl[0]), decodeHashFromBase64(hrlB64.hrl[1])], + context: hrlB64.context, + }; } \ No newline at end of file diff --git a/ui/src/we.ts b/ui/src/we.ts new file mode 100644 index 0000000..f75498f --- /dev/null +++ b/ui/src/we.ts @@ -0,0 +1,158 @@ +import { DocumentStore, SynClient, SynStore, WorkspaceStore } from '@holochain-syn/core'; +import { CellType, type AppAgentClient, type RoleName, type ZomeName, type DnaHash } from '@holochain/client'; +import { Board, type BoardEphemeralState, type BoardState } from './board'; +import { asyncDerived, pipe, sliceAndJoin, toPromise } from '@holochain-open-dev/stores'; +import { BoardType } from './boardList'; +import { LazyHoloHashMap } from '@holochain-open-dev/utils'; +import type { AppletHash, AppletServices, EntryInfo, Hrl, HrlWithContext, WeServices } from '@lightningrodlabs/we-applet'; + +const ROLE_NAME = "talking-stickies" +const ZOME_NAME = "syn" + +const getMyDna = async (client: AppAgentClient) : Promise => { + const appInfo = await client.appInfo(); + const dnaHash = (appInfo.cell_info[ROLE_NAME][0] as any)[ + CellType.Provisioned + ].cell_id[0]; + return dnaHash +} + +export const appletServices: AppletServices = { + // Types of attachment that this Applet offers for other Applets to attach + attachmentTypes: async ( + appletClient: AppAgentClient, + appletHash: AppletHash, + weServices: WeServices + ) => ({ + board: { + label: "Board", + icon_src: "https://static-00.iconduck.com/assets.00/kanban-icon-480x512-y56i8vrh.png", + async create(attachToHrl: Hrl) { + const synStore = new SynStore(new SynClient(appletClient, ROLE_NAME)); + const board = await Board.Create(synStore) + const dnaHash = await getMyDna(appletClient) + return { + hrl: [dnaHash, board.hash], + context: {}, + }; + }, + }, + }), + // Types of UI widgets/blocks that this Applet supports + blockTypes: {}, + getEntryInfo: async ( + appletClient: AppAgentClient, + roleName: RoleName, + integrityZomeName: ZomeName, + entryType: string, + hrl: Hrl + ): Promise => { + + const synClient = new SynClient(appletClient, roleName, ZOME_NAME); + const synStore = new SynStore(synClient); + const documentHash = hrl[1] + const docStore = new DocumentStore (synStore, documentHash) + const workspaces = await toPromise(docStore.allWorkspaces) + const workspace = new WorkspaceStore(docStore, Array.from(workspaces.keys())[0]) + const latestSnapshot = await toPromise(workspace.latestSnapshot) + + return { + icon_src: "https://static-00.iconduck.com/assets.00/kanban-icon-480x512-y56i8vrh.png", + name: latestSnapshot.name, + }; + }, + search: async ( + appletClient: AppAgentClient, + appletHash: AppletHash, + weServices: WeServices, + searchFilter: string + ): Promise> => { + const synClient = new SynClient(appletClient, ROLE_NAME, ZOME_NAME); + const synStore = new SynStore(synClient); + const boardHashes = asyncDerived(synStore.documentsByTag.get(BoardType.active),x=>Array.from(x.keys())) + + const boardData = new LazyHoloHashMap( documentHash => { + const docStore = synStore.documents.get(documentHash) + + const workspace = pipe(docStore.allWorkspaces, + workspaces => { + return new WorkspaceStore(docStore, Array.from(workspaces.keys())[0]) + } + ) + const latestState = pipe(workspace, + w => w.latestSnapshot + ) + return latestState + }) + + const allBoardsAsync = pipe(boardHashes, + docHashes => sliceAndJoin(boardData, docHashes) + ) + + const allBoards = Array.from((await toPromise(allBoardsAsync)).entries()) + const dnaHash = await getMyDna(appletClient) + + return allBoards + .filter((r) => !!r) + .filter((r) => { + const state = r[1] + return state.name.toLowerCase().includes(searchFilter.toLowerCase()) + }) + .map((r) => ({ hrl: [dnaHash, r![0]], context: {} })); + }, +}; + + +// // Then handle all the different types of views that you offer +// switch (weClient.renderInfo.type) { +// case "applet-view": +// switch (weClient.renderInfo.view.type) { +// case "main": +// // here comes your rendering logic for the main view +// case "block": +// switch(weClient.renderInfo.view.block) { +// case "most_recent_posts": +// // your rendering logic to display this block type +// case "bookmarked_posts": +// // Your rendering logic to display this block type +// default: +// throw new Error("Unknown applet-view block type"); +// } +// case "entry": +// switch (weClient.renderInfo.view.roleName) { +// case "forum": +// switch (weClient.renderInfo.view.integrityZomeName) { +// case "posts_integrity": +// switch (weClient.renderInfo.view.entryType) { +// case "post": +// // here comes your rendering logic for that specific entry type +// default: +// throw new Error("Unknown entry type"); +// } +// default: +// throw new Error("Unknown integrity zome"); +// } +// default: +// throw new Error("Unknown role name"); +// } + +// default: +// throw new Error("Unknown applet-view type"); +// } + +// case "cross-applet-view": +// switch (this.weClient.renderInfo.view.type) { +// case "main": +// // here comes your rendering logic for the cross-applet main view +// case "block": +// // +// default: +// throw new Error("Unknown cross-applet-view render type.") + +// `; +// } + +// default: +// throw new Error("Unknown render view type"); + +// }