diff --git a/CHANGELOG.md b/CHANGELOG.md index 16391c5c5..9b035f7de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,75 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project *loosely* adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). More specifically: +## [0.1.6] - 24/06/2021 + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + - Holochain connection in ad4m being started in async meaning connection would not always occur before trying to call conductor in subsequent init languages calls + +### Security + +--- + +## [0.1.5] - 24/06/2021 + +### Added +- Push notifications on message received if app not in view! +- Black theme +- Border around modals on 90s theme + +### Changed +- Optimised all polling code to use web workers to avoid polluting the main render thread + +### Deprecated + +### Removed + +### Fixed + - Various fixes on channel notification icons + - Update to new ad4m version to fix concurrent language installation in new holochain version + +### Security + +--- + +## [0.1.4] - 22/06/2021 + +### Added +- Script to fetch languages on build vs building from src +- Cyberpunk theme! +- Global error popup if ad4m or holochain cannot start +- channel notification icon when live message received during sessions +- use vue virtual scroll again for our scrolling +- New messages arriving in channel will prompt UI to give notification to scroll to bottom of view +- Holochain now needs to be built manually for each dev install with nix vs built in repo binaries +- 5 channel views are now kept alive at once to allow for fast rendering/switching between them + +### Changed +- Holochain now at latest version @ commit: 8600350687dd80bbc7a5620e8fe71ad55c97eed2 +- Global error now has proper styling +- Use key/value store vs array for channels/communities/messages in vuex store + +### Deprecated + +### Removed + +### Fixed +- Bug where old loading/error dialogues would remain in state after app restart +- Pressing enter would make a new line in the editor +- Sending a message will now always correctly move to bottom of channel + +### Security + +--- + ## [0.1.3] - 19/06/2021 ### Added diff --git a/package.json b/package.json index 38a41fc5a..5c97141d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "junto", - "version": "0.1.6", + "version": "0.1.7", "private": true, "description": "Junto ad4m chat application leveraging distributed & decentralized technologies", "author": "josh@junto.foundation", @@ -27,7 +27,7 @@ "@apollo/client": "^3.3.14", "@junto-foundation/junto-elements": "0.0.13", "@perspect3vism/ad4m": "0.0.6", - "@perspect3vism/ad4m-executor": "0.0.16", + "@perspect3vism/ad4m-executor": "0.0.17", "@types/jest": "^26.0.20", "@types/mocha": "^8.2.1", "@vue/apollo-composable": "^4.0.0-alpha.12", diff --git a/scripts/clean-state.command b/scripts/clean-state.command new file mode 100755 index 000000000..ceda93a47 --- /dev/null +++ b/scripts/clean-state.command @@ -0,0 +1,18 @@ +#!/bin/bash + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + path="$HOME/.config/junto" +elif [[ "$OSTYPE" == "darwin"* ]]; then + path="$HOME/Library/Application Support/junto" +fi + +echo "Will delete path: $path" +# read -p "IS THIS CORRECT? (y/n) " answer + +# if [[ $answer =~ ^[Yy]$ ]] ; +# then +# echo "Accepted, deleting directory" +rm -rf $path +# else +# echo "Not deleting" +# fi diff --git a/src/containers/EditProfile.vue b/src/containers/EditProfile.vue index b82663cfc..cfa81d3e1 100644 --- a/src/containers/EditProfile.vue +++ b/src/containers/EditProfile.vue @@ -8,13 +8,13 @@
Cancel - + Save
@@ -56,10 +56,10 @@ export default defineComponent({ }, }, methods: { - updateUser() { + updateProfile() { this.isUpdatingProfile = true; this.$store - .dispatch("updateUser", { + .dispatch("updateProfile", { username: this.username, profilePicture: this.profilePicture, }) diff --git a/src/core/graphql_queries.ts b/src/core/graphql_queries.ts index a85f587e2..488bf522e 100644 --- a/src/core/graphql_queries.ts +++ b/src/core/graphql_queries.ts @@ -96,6 +96,16 @@ export const LANGUAGES = gql` languages(filter: $filter) { name address + constructorIcon { + code + } + iconFor { + code + } + settings + settingsIcon { + code + } } } `; diff --git a/src/core/methods/getTypedExpressionLangs.ts b/src/core/methods/getTypedExpressionLangs.ts index ccc0dd704..8cb0e78a8 100644 --- a/src/core/methods/getTypedExpressionLangs.ts +++ b/src/core/methods/getTypedExpressionLangs.ts @@ -24,6 +24,7 @@ export async function getTypedExpressionLanguages( languageAddress: lang!, createIcon: languageRes.constructorIcon!.code!, viewIcon: languageRes.iconFor!.code!, + name: languageRes.name!, }; store!.commit({ type: "addExpressionUI", diff --git a/src/core/queries/getLanguages.ts b/src/core/queries/getLanguages.ts new file mode 100644 index 000000000..0dc429380 --- /dev/null +++ b/src/core/queries/getLanguages.ts @@ -0,0 +1,18 @@ +import { apolloClient } from "@/main"; +import { LANGUAGES } from "../graphql_queries"; +import ad4m from "@perspect3vism/ad4m-executor"; + +export async function getLanguages(): Promise { + return new Promise((resolve, reject) => { + apolloClient + .query<{ languages: ad4m.Language[] }>({ + query: LANGUAGES, + }) + .then((result) => { + resolve(result.data!.languages); + }) + .catch((error) => { + reject(error); + }); + }); +} diff --git a/src/store/actions/createCommunity.ts b/src/store/actions/createCommunity.ts index e86df7e5a..28793b421 100644 --- a/src/store/actions/createCommunity.ts +++ b/src/store/actions/createCommunity.ts @@ -182,6 +182,7 @@ export default async ( languageAddress: lang, createIcon: languageRes.constructorIcon!.code!, viewIcon: languageRes.iconFor!.code!, + name: languageRes.name!, }; commit("addExpressionUI", uiData); await sleep(40); diff --git a/src/store/actions/getCommunityMembers.ts b/src/store/actions/getCommunityMembers.ts index 4db0ab9be..19fbe3e77 100644 --- a/src/store/actions/getCommunityMembers.ts +++ b/src/store/actions/getCommunityMembers.ts @@ -3,6 +3,7 @@ import { getLinks } from "@/core/queries/getLinks"; import { Commit } from "vuex"; import { ExpressionTypes, State } from ".."; import type Expression from "@perspect3vism/ad4m/Expression"; +import { TimeoutCache } from "../../utils/timeoutCache"; export interface Context { commit: Commit; @@ -18,6 +19,7 @@ export default async function ( { communityId }: Payload ): Promise { const profiles: { [x: string]: Expression } = {}; + const cache = new TimeoutCache(1000 * 60 * 5); try { const communities = state.communities; @@ -39,6 +41,7 @@ export default async function ( const did = `${profileLang.languageAddress}://${profileLink.author! .did!}`; + //TODO: we should store the whole profile in the store but just the did and then resolve the profile via cache/network const profile = await getProfile( profileLang.languageAddress, profileLink.author!.did! @@ -46,6 +49,7 @@ export default async function ( if (profile) { profiles[did] = Object.assign({}, profile); + cache.set(did, profile); } } diff --git a/src/store/actions/index.ts b/src/store/actions/index.ts index cb381abf1..6e1e0e294 100644 --- a/src/store/actions/index.ts +++ b/src/store/actions/index.ts @@ -6,7 +6,7 @@ import logIn from "./logIn"; import getPerspectiveChannelsAndMetadata from "./getPerspectiveChannelsAndMetadata"; import loadExpressionLanguages from "./loadExpressionLanguages"; import updateCommunity from "./updateCommunity"; -import updateUser from "./updateUser"; +import updateProfile from "./updateProfile"; import getCommunityMembers from "./getCommunityMembers"; import loadExpressions from "./loadExpressions"; import createExpression from "./createExpression"; @@ -20,7 +20,7 @@ export default { getPerspectiveChannelsAndMetadata, loadExpressionLanguages, updateCommunity, - updateUser, + updateProfile, getCommunityMembers, loadExpressions, createExpression, diff --git a/src/store/actions/loadExpressionLanguages.ts b/src/store/actions/loadExpressionLanguages.ts index 9fa28e0bd..e2d86d14e 100644 --- a/src/store/actions/loadExpressionLanguages.ts +++ b/src/store/actions/loadExpressionLanguages.ts @@ -1,7 +1,7 @@ import { Commit } from "vuex"; import { ExpressionUIIcons } from "@/store"; -import { getLanguage } from "@/core/queries/getLanguage"; +import { getLanguages } from "@/core/queries/getLanguages"; export interface Context { commit: Commit; @@ -14,18 +14,18 @@ export interface Payload { export default async ({ commit, getters }: Context): Promise => { try { - const expressionLangs = getters.getAllExpressionLanguagesNotLoaded; - // console.log({ expressionLangs }); - for (const [, lang] of expressionLangs.entries()) { - const language = await getLanguage(lang); - console.log("Got language", language); - if (language !== null) { - const uiData: ExpressionUIIcons = { - languageAddress: language!.address!, - createIcon: language!.constructorIcon!.code!, - viewIcon: language!.iconFor!.code!, - }; - commit("addExpressionUI", uiData); + const languages = await getLanguages(); + for (const language of languages) { + if (language.iconFor) { + if (!getters.getLanguageUI(language.address!)) { + const uiData: ExpressionUIIcons = { + languageAddress: language!.address!, + createIcon: language!.constructorIcon!.code!, + viewIcon: language!.iconFor!.code!, + name: language!.name!, + }; + commit("addExpressionUI", uiData); + } } } } catch (e) { diff --git a/src/store/actions/updateProfile.ts b/src/store/actions/updateProfile.ts new file mode 100644 index 000000000..17b79c8b0 --- /dev/null +++ b/src/store/actions/updateProfile.ts @@ -0,0 +1,77 @@ +import { createProfile } from "@/core/methods/createProfile"; +import { Commit } from "vuex"; +import { ExpressionTypes, State, Profile } from ".."; +import { TimeoutCache } from "../../utils/timeoutCache"; +import type Expression from "@perspect3vism/ad4m/Expression"; +import { getExpression } from "@/core/queries/getExpression"; + +export interface Context { + commit: Commit; + state: State; +} + +export interface Payload { + username: string; + profilePicture: string; + thumbnail: string; +} + +export default async ( + { commit, state }: Context, + payload: Payload +): Promise => { + commit("setUserProfile", payload); + + try { + const user: Profile | null = state.userProfile; + + const communities = Object.values(state.communities); + const cache = new TimeoutCache(1000 * 60 * 5); + + for (const community of communities) { + const profileExpression = community.typedExpressionLanguages.find( + (t) => t.expressionType == ExpressionTypes.ProfileExpression + ); + const didExpression = `${ + profileExpression!.languageAddress + }://${state.userDid!}`; + + console.log("profileExpression: ", profileExpression); + + if (profileExpression) { + const exp = await createProfile( + profileExpression.languageAddress, + payload.username, + user!.email, + user!.givenName, + user!.familyName, + payload.profilePicture, + payload.thumbnail + ); + + console.log("Created new profileExpression: ", exp); + + const expressionGql = await getExpression(exp); + const profileExp = { + author: expressionGql.author!, + data: JSON.parse(expressionGql.data!), + timestamp: expressionGql.timestamp!, + proof: expressionGql.proof!, + } as Expression; + cache.set(didExpression, profileExp); + } else { + const errorMessage = + "Expected to find profile expression language for this community"; + commit("showDangerToast", { + message: errorMessage, + }); + throw Error(errorMessage); + } + } + } catch (e) { + commit("showDangerToast", { + message: e.message, + }); + throw new Error(e); + } +}; diff --git a/src/store/actions/updateUser.ts b/src/store/actions/updateUser.ts deleted file mode 100644 index c53cb9074..000000000 --- a/src/store/actions/updateUser.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Commit } from "vuex"; - -export interface Context { - commit: Commit; -} - -export interface Payload { - username: string; -} - -export default async ({ commit }: Context, payload: Payload): Promise => { - // TODO: GraphQL Mutation - commit("setUserProfile", payload); -}; diff --git a/src/store/getters/index.ts b/src/store/getters/index.ts index df01189ca..10ea81045 100644 --- a/src/store/getters/index.ts +++ b/src/store/getters/index.ts @@ -75,26 +75,6 @@ export default { return perspective; }, - getAllExpressionLanguagesNotLoaded(state: State): Address[] { - const expressionLangs: Address[] = []; - - for (const community of Object.values(state.communities)) { - for (const expLang of community.expressionLanguages) { - if ( - expressionLangs.indexOf(expLang) === -1 && - //@ts-ignore - state.expressionUI.find( - (val: ExpressionUIIcons) => val.languageAddress === expLang - ) === undefined - ) { - expressionLangs.push(expLang); - } - } - } - - return expressionLangs; - }, - getAgentLockStatus(state: State): boolean { return state.agentUnlocked; }, @@ -106,4 +86,8 @@ export default { getApplicationStartTime(state: State): Date { return state.applicationStartTime; }, + + getLanguageUI: (state: State) => (language: string) => { + return state.expressionUI[language]; + }, }; diff --git a/src/store/index.ts b/src/store/index.ts index 7b9e0e1d6..dd1343a98 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -128,7 +128,7 @@ export interface State { applicationStartTime: Date; //TODO: this is a horrible type for this use; would be better to have a real map with values pointing to same strings where appropriate //fow now this is fine - expressionUI: ExpressionUIIcons[]; + expressionUI: { [x: string]: ExpressionUIIcons }; agentUnlocked: boolean; agentInit: boolean; userProfile: Profile | null; @@ -141,6 +141,7 @@ export interface ExpressionUIIcons { languageAddress: string; createIcon: string; viewIcon: string; + name: string; } export enum ExpressionTypes { @@ -197,7 +198,7 @@ export default createStore({ localLanguagesPath: "", databasePerspective: "", applicationStartTime: new Date(), - expressionUI: [], + expressionUI: {}, agentUnlocked: false, agentInit: false, userProfile: null, diff --git a/src/store/mutations/index.ts b/src/store/mutations/index.ts index 901d3df1a..31d394830 100644 --- a/src/store/mutations/index.ts +++ b/src/store/mutations/index.ts @@ -130,7 +130,7 @@ export default { }, addExpressionUI(state: State, payload: ExpressionUIIcons): void { - state.expressionUI.push(payload); + state.expressionUI[payload.languageAddress] = payload; }, updateApplicationStartTime(state: State, payload: Date): void { diff --git a/src/utils/profileHelpers.ts b/src/utils/profileHelpers.ts index d2dd55f16..286f580a6 100644 --- a/src/utils/profileHelpers.ts +++ b/src/utils/profileHelpers.ts @@ -26,7 +26,7 @@ export async function getProfile( profileLangAddress: string, did: string ): Promise { - const cache = new TimeoutCache(1000 * 60 * 60); + const cache = new TimeoutCache(1000 * 60 * 5); const profileLink = `${profileLangAddress}://${did}`; diff --git a/src/utils/showMessageNotification.ts b/src/utils/showMessageNotification.ts index 66a2d54ca..f8c90d07b 100644 --- a/src/utils/showMessageNotification.ts +++ b/src/utils/showMessageNotification.ts @@ -35,7 +35,7 @@ export default async ( // Only show the notification when the the message is not from self & the active community & channel is different if ( - isMinimized || + (isMinimized && !channel?.notifications.mute) || (store.state.userDid !== authorDid && (community?.perspective === communityId ? channel?.perspective !== channelId