diff --git a/frontend/package.json b/frontend/package.json index 0c7a44b..4fa78f8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "private": true, "dependencies": { + "@ant-design/icons": "^5.2.6", "@dfinity/auth-client": "^0.20.2", + "@reduxjs/toolkit": "^2.0.1", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", @@ -14,6 +16,7 @@ "antd": "^5.11.5", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-redux": "^9.0.4", "react-router-dom": "^6.20.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", diff --git a/frontend/src/actors/feed.ts b/frontend/src/actors/feed.ts index b89032b..b7e8076 100644 --- a/frontend/src/actors/feed.ts +++ b/frontend/src/actors/feed.ts @@ -19,7 +19,6 @@ export default class Feed { private async getNoIdentityActor() { return await getActor.noIdentityActor(idlFactory, this.canisterId.toString()); - } async createPost(title: string, content: string) { diff --git a/frontend/src/actors/user.ts b/frontend/src/actors/user.ts index 69dd95d..3bdc9c4 100644 --- a/frontend/src/actors/user.ts +++ b/frontend/src/actors/user.ts @@ -32,6 +32,18 @@ class User { } } + async batchGetProfile(who: Principal[]): Promise { + // async batchGetProfile(who: Principal[]): Promise { + const actor = await User.getActor(); + try { + return await actor.batchGetProfile(who) as Profile[] + // return await actor.batchGetProfile(who) + } catch(e) { + console.log("batchGetProfile", e) + throw e + } + } + async getFollowerNumber(who: Principal): Promise { const actor = await User.getActor() try { diff --git a/frontend/src/components/Modal/form.tsx b/frontend/src/components/Modal/form.tsx index 688b422..145e5f6 100644 --- a/frontend/src/components/Modal/form.tsx +++ b/frontend/src/components/Modal/form.tsx @@ -3,9 +3,11 @@ import { Button, Form, Input, - Select, + notification, } from 'antd'; import {Profile} from "../../declarations/user/user"; +import { userApi } from '../../actors/user'; +import { LoadingOutlined, CheckOutlined } from '@ant-design/icons'; interface DataNodeType { value: string; @@ -39,13 +41,37 @@ const tailFormItemLayout = { interface ProfileFormProps { userProfile: Profile | undefined; + drawCallBack: () => void; } export default function ProfileForm(props: ProfileFormProps) { - const [form] = Form.useForm(); + const [api, contextHolder] = notification.useNotification(); - const onFinish = (values: any) => { + const onFinish = async (values: any) => { + api.info({ + message: 'Update Profile ing ...', + key: 'updateProfile', + duration: null, + description: '', + icon: + }) + await userApi.createProfile({ + 'backImgUrl' : values.backImgUrl === undefined? '' : values.backImgUrl, + 'name' : values.name === undefined? '' : values.name, + 'education' : values.education === undefined? '' : values.education, + 'biography' : values.biography === undefined? '' : values.biography, + 'company' : values.company === undefined? '' : values.company, + 'avatarUrl' : values.avatarUrl === undefined? '' : values.avatarUrl, + 'feedCanister' : [], + }); + api.success({ + message: 'Update Profile Successful !', + key: 'updateProfile', + description: '', + icon: + }); + props.drawCallBack(); console.log('Received values of form: ', values); }; @@ -67,6 +93,7 @@ export default function ProfileForm(props: ProfileFormProps) { style={{ maxWidth: 600 }} scrollToFirstError > + {contextHolder} { if (!userFeedCai) return + api.info({ + message: 'Create Post ing ...', + key: 'createPost', + duration: null, + description: '', + icon: + }); const feedApi = new Feed(userFeedCai) await feedApi.createPost(values.title, values.content) + api.success({ + message: 'Create Post Successful !', + key: 'createPost', + description: '', + icon: + }) await feedApi.getAllPost() form.resetFields() props.setOpen(false) }; return ( - + <> + {contextHolder} + + ); }; diff --git a/frontend/src/components/post.tsx b/frontend/src/components/post.tsx index 2ba91d7..9234fc8 100644 --- a/frontend/src/components/post.tsx +++ b/frontend/src/components/post.tsx @@ -10,7 +10,7 @@ import Feed from "../actors/feed"; import React, {useState} from "react"; import {CommentForm} from "./Modal/commentForm"; -export default function Post(props: { content: PostImmutable, setPostItem?: Function }) { +export default function Post(props: { content: PostImmutable, setPostItem?: Function, avatar?: string, name?: string}) { const {content, setPostItem} = props const {userFeedCai} = useAuth() const [open, setOpen] = useState(false) @@ -52,12 +52,12 @@ export default function Post(props: { content: PostImmutable, setPostItem?: Func -

NeutronStarDAO

+

{props.name ? props.name : 'NeutronStarDAO'}

(); + + useEffect(() => { + const initUserProfile = async () => { + if(principal?._isPrincipal === true) { + const _profile = await userApi.getProfile(principal); + console.log('side bar init profile : ', _profile); + if(_profile.length > 0) { + setProfile(_profile[0]); + }; + }; + }; + + initUserProfile(); + + }, [principal]); const onClick = (info: MenuInfo) => { if (info.key === '1') { @@ -114,13 +132,23 @@ export default function Sider() { } - title="NeutronStarDAO" - description="@NeutronStarDAO" + title={ + profile ? profile.name : "NeutronstarDao" + } + description={ + + {principal?.toString()} + + } /> ) : ( logIn?.()}> diff --git a/frontend/src/declarations/user/index.d.ts b/frontend/src/declarations/user/index.d.ts index 3851f47..1a944c4 100644 --- a/frontend/src/declarations/user/index.d.ts +++ b/frontend/src/declarations/user/index.d.ts @@ -7,7 +7,7 @@ import type { import type { Principal } from "@dfinity/principal"; import type { IDL } from "@dfinity/candid"; -import { _SERVICE } from './user'; +import { _SERVICE } from './user.did'; export declare const idlFactory: IDL.InterfaceFactory; export declare const canisterId: string; diff --git a/frontend/src/declarations/user/user.did b/frontend/src/declarations/user/user.did index e703b0a..4165cee 100644 --- a/frontend/src/declarations/user/user.did +++ b/frontend/src/declarations/user/user.did @@ -3,6 +3,7 @@ type UserId__1 = principal; type UserId = principal; type User = service { + batchGetProfile: (vec UserId__1) -> (vec Profile) query; createProfile: (NewProfile) -> (); follow: (Vertex) -> (); getFollowerNumber: (Vertex) -> (nat) query; diff --git a/frontend/src/declarations/user/user.did.js b/frontend/src/declarations/user/user.did.js index 5ae7833..fba206a 100644 --- a/frontend/src/declarations/user/user.did.js +++ b/frontend/src/declarations/user/user.did.js @@ -1,5 +1,8 @@ export const idlFactory = ({ IDL }) => { - const NewProfile = IDL.Record({ + const UserId__1 = IDL.Principal; + const UserId = IDL.Principal; + const Profile = IDL.Record({ + 'id' : UserId, 'backImgUrl' : IDL.Text, 'name' : IDL.Text, 'education' : IDL.Text, @@ -8,11 +11,7 @@ export const idlFactory = ({ IDL }) => { 'avatarUrl' : IDL.Text, 'feedCanister' : IDL.Opt(IDL.Principal), }); - const Vertex = IDL.Principal; - const UserId__1 = IDL.Principal; - const UserId = IDL.Principal; - const Profile = IDL.Record({ - 'id' : UserId, + const NewProfile = IDL.Record({ 'backImgUrl' : IDL.Text, 'name' : IDL.Text, 'education' : IDL.Text, @@ -21,7 +20,13 @@ export const idlFactory = ({ IDL }) => { 'avatarUrl' : IDL.Text, 'feedCanister' : IDL.Opt(IDL.Principal), }); + const Vertex = IDL.Principal; const User = IDL.Service({ + 'batchGetProfile' : IDL.Func( + [IDL.Vec(UserId__1)], + [IDL.Vec(Profile)], + ['query'], + ), 'createProfile' : IDL.Func([NewProfile], [], []), 'follow' : IDL.Func([Vertex], [], []), 'getFollowerNumber' : IDL.Func([Vertex], [IDL.Nat], ['query']), diff --git a/frontend/src/declarations/user/user.ts b/frontend/src/declarations/user/user.ts index fd80845..23299b1 100644 --- a/frontend/src/declarations/user/user.ts +++ b/frontend/src/declarations/user/user.ts @@ -21,6 +21,7 @@ export interface Profile { 'feedCanister' : [] | [Principal], } export interface User { + 'batchGetProfile' : ActorMethod<[Array], Array>, 'createProfile' : ActorMethod<[NewProfile], undefined>, 'follow' : ActorMethod<[Vertex], undefined>, 'getFollowerNumber' : ActorMethod<[Vertex], bigint>, diff --git a/frontend/src/routes/content.tsx b/frontend/src/routes/content.tsx index ac71ee4..e3c0637 100644 --- a/frontend/src/routes/content.tsx +++ b/frontend/src/routes/content.tsx @@ -1,34 +1,88 @@ -import React, {useState} from "react"; -import {Layout} from "antd"; +import React, {useEffect, useState} from "react"; +import {Layout, Spin, Flex} from "antd"; import Post from "../components/post"; import {Comments} from "../components/comment"; import {Like, PostId, PostImmutable, Repost, Time, UserId} from "../declarations/feed/feed"; +import {Profile} from '../declarations/user/user'; +import { userApi } from '../actors/user'; +import { Principal } from "@dfinity/principal"; export const Content = React.memo((props: { contents?: PostImmutable[] }) => { const {contents} = props const [postItem, setPostItem] = useState() - return <> - - {contents && contents.map((v, k) => { - return - })} - - - {postItem && postItem.comment.map((v, k) => { - return - })} - - + const [userProfileArray, setUserProfileArray] = useState(); + const [onLoading, setOnloading] = useState(false); + + useEffect(() => { + const initUserProfileArray = async () => { + if(contents !== undefined) { + const userPrincipalArray = (Array.from(contents!.reduce((uniqueUsers, value) => { + uniqueUsers.add(value.user.toString()); + return uniqueUsers; + }, new Set()))).map(value => Principal.fromText(value)); + // console.log('userPrincipalArray : ', userPrincipalArray.toString()); + if(userPrincipalArray !== undefined && userPrincipalArray?.length > 0) { + const result = await userApi.batchGetProfile(userPrincipalArray!); + // console.log('initUserProfileArray result : ', result); + setUserProfileArray(result) + setOnloading(true) + } + } + }; + + initUserProfileArray() + }, [contents]); + + if(onLoading === true) { + return <> + + {contents && contents.map((v, k) => { + const _userProfile = userProfileArray?.find(item => { + return item.id.toString() === v.user.toString() + }); + return + })} + + + {postItem && postItem.comment.map((v, k) => { + return + })} + + + } else { + return <> + + + + + + + + + } + }) diff --git a/frontend/src/routes/explore.tsx b/frontend/src/routes/explore.tsx index 36e1c22..23ca560 100644 --- a/frontend/src/routes/explore.tsx +++ b/frontend/src/routes/explore.tsx @@ -9,6 +9,7 @@ export default function Explore() { const fetch = async () => { const bucket = await rootPostApi.getAvailableBucket() + console.log('explore bucket : ', bucket[0]?.toString()) if (!bucket[0]) return const bucketApi = new Bucket(bucket[0]) const res = await bucketApi.getLatestFeed(30) diff --git a/frontend/src/routes/home.tsx b/frontend/src/routes/home.tsx index 8b9d872..86e610a 100644 --- a/frontend/src/routes/home.tsx +++ b/frontend/src/routes/home.tsx @@ -1,10 +1,45 @@ import React from "react"; - import {Content} from "./content"; import {useAllDataStore} from "../redux"; +import { useAuth } from "../utils/useAuth"; +import { SmileOutlined } from '@ant-design/icons'; +import { Layout, Result } from 'antd'; export const Home = React.memo(() => { const {allFeed} = useAllDataStore() + + if(allFeed?.length === 0 || allFeed === undefined) { + return ( + <> + + } + title="There Is No Feed !" + subTitle="Please Follow SomeOne To Get Feed" + style={{ + backgroundColor: 'white' + }} + /> + + + + + + ) - return + } else { + return + }; }) diff --git a/frontend/src/routes/profile.tsx b/frontend/src/routes/profile.tsx index 559d620..0f31237 100644 --- a/frontend/src/routes/profile.tsx +++ b/frontend/src/routes/profile.tsx @@ -12,6 +12,7 @@ import {rootFeedApi} from "../actors/rootFeed"; import Feed from "../actors/feed"; import {useAllDataStore} from "../redux"; import {useAuth} from "../utils/useAuth"; +import { profile } from 'console'; export default function UserProfile() { const {principal: me} = useAuth() @@ -104,8 +105,8 @@ export default function UserProfile() { borderRight: '1px solid', }}> Profile Bakcground Picture - {userProfile?.name} + {userProfile?.name} setIsModalOpen(false)} > - + setIsModalOpen(false)}/>
(vec Profile) query; createProfile: (NewProfile) -> (); follow: (Vertex) -> (); getFollowerNumber: (Vertex) -> (nat) query; diff --git a/src/declarations/user/user.did.d.ts b/src/declarations/user/user.did.d.ts index fd80845..23299b1 100644 --- a/src/declarations/user/user.did.d.ts +++ b/src/declarations/user/user.did.d.ts @@ -21,6 +21,7 @@ export interface Profile { 'feedCanister' : [] | [Principal], } export interface User { + 'batchGetProfile' : ActorMethod<[Array], Array>, 'createProfile' : ActorMethod<[NewProfile], undefined>, 'follow' : ActorMethod<[Vertex], undefined>, 'getFollowerNumber' : ActorMethod<[Vertex], bigint>, diff --git a/src/declarations/user/user.did.js b/src/declarations/user/user.did.js index 5ae7833..fba206a 100644 --- a/src/declarations/user/user.did.js +++ b/src/declarations/user/user.did.js @@ -1,5 +1,8 @@ export const idlFactory = ({ IDL }) => { - const NewProfile = IDL.Record({ + const UserId__1 = IDL.Principal; + const UserId = IDL.Principal; + const Profile = IDL.Record({ + 'id' : UserId, 'backImgUrl' : IDL.Text, 'name' : IDL.Text, 'education' : IDL.Text, @@ -8,11 +11,7 @@ export const idlFactory = ({ IDL }) => { 'avatarUrl' : IDL.Text, 'feedCanister' : IDL.Opt(IDL.Principal), }); - const Vertex = IDL.Principal; - const UserId__1 = IDL.Principal; - const UserId = IDL.Principal; - const Profile = IDL.Record({ - 'id' : UserId, + const NewProfile = IDL.Record({ 'backImgUrl' : IDL.Text, 'name' : IDL.Text, 'education' : IDL.Text, @@ -21,7 +20,13 @@ export const idlFactory = ({ IDL }) => { 'avatarUrl' : IDL.Text, 'feedCanister' : IDL.Opt(IDL.Principal), }); + const Vertex = IDL.Principal; const User = IDL.Service({ + 'batchGetProfile' : IDL.Func( + [IDL.Vec(UserId__1)], + [IDL.Vec(Profile)], + ['query'], + ), 'createProfile' : IDL.Func([NewProfile], [], []), 'follow' : IDL.Func([Vertex], [], []), 'getFollowerNumber' : IDL.Func([Vertex], [IDL.Nat], ['query']), diff --git a/src/user/database.mo b/src/user/database.mo index d94167c..81d364e 100644 --- a/src/user/database.mo +++ b/src/user/database.mo @@ -17,9 +17,9 @@ module { type UserId = Types.UserId; type Time = Time.Time; - public class Directory() { + public class Directory(hashMapEntries: [(UserId, Profile)]) { - let hashMap = HashMap.HashMap(1, isEq, Principal.hash); + let hashMap = HashMap.fromIter(hashMapEntries.vals(), Array.size(hashMapEntries), isEq, Principal.hash); public func createOne(userId: UserId, profile: NewProfile) { hashMap.put(userId, makeProfile(userId, profile)); @@ -33,6 +33,8 @@ module { hashMap.get(userId) }; + public func getHashMapEntries(): [(UserId, Profile)] { Iter.toArray(hashMap.entries() )}; + public func findMany(userIds: [UserId]): [Profile] { func getProfile(userId: UserId): Profile { Option.unwrap(hashMap.get(userId)) diff --git a/src/user/digraph.mo b/src/user/digraph.mo index 016a679..d2ce988 100644 --- a/src/user/digraph.mo +++ b/src/user/digraph.mo @@ -6,15 +6,22 @@ module { type Vertex = Types.Vertex; - public class Digraph() { + public class Digraph( + vertexListEntries: [Vertex], + edgeListEntries: [(Vertex, Vertex)] + ) { - var vertexList: [Vertex] = []; - var edgeList: [(Vertex, Vertex)] = []; // 单向边 + var vertexList: [Vertex] = vertexListEntries; + var edgeList: [(Vertex, Vertex)] = edgeListEntries; // 单向边 public func addVertex(vertex: Vertex) { vertexList := Array.append(vertexList, [vertex]); }; + public func getVertexListEntries(): [Vertex] { vertexList }; + + public func getEdgeListEntries(): [(Vertex, Vertex)] { edgeList }; + // 添加 from 到 to 的单向边 public func addEdge(fromVertex: Vertex, toVertex: Vertex) { // 检查是否已经存在 diff --git a/src/user/main.mo b/src/user/main.mo index 3f47a45..b9c10b3 100644 --- a/src/user/main.mo +++ b/src/user/main.mo @@ -3,6 +3,7 @@ import Types "./types"; import Database "./database"; import Principal "mo:base/Principal"; import Array "mo:base/Array"; +import Buffer "mo:base/Buffer"; actor class User() = this { @@ -22,7 +23,9 @@ actor class User() = this { type RootFeedActor = Types.RootFeedActor; type FeedActor = Types.FeedActor; - var graph: Digraph.Digraph = Digraph.Digraph(); + stable var vertexListEntries: [Vertex] = []; + stable var edgeListEntries: [(Vertex, Vertex)] = []; + var graph: Digraph.Digraph = Digraph.Digraph(vertexListEntries, edgeListEntries); // User caller Follow user public shared({caller}) func follow(user: Vertex): async () { @@ -60,7 +63,8 @@ actor class User() = this { type Profile = Types.Profile; type UserId = Types.UserId; - var directory: Database.Directory = Database.Directory(); + stable var directoryMapEntries: [(UserId, Profile)] = []; + var directory: Database.Directory = Database.Directory(directoryMapEntries); public shared({caller}) func createProfile(profile: NewProfile): async () { directory.createOne(caller, profile); @@ -74,8 +78,33 @@ actor class User() = this { directory.findOne(userId) }; + public query func batchGetProfile(userIdArray: [UserId]): async [Profile] { + var profileBuffer = Buffer.Buffer(Array.size(userIdArray)); + for(_user in userIdArray.vals()) { + switch(directory.findOne(_user)) { + case(null) { }; + case(?_profile) { + profileBuffer.add(_profile); + }; + }; + }; + Buffer.toArray(profileBuffer) + }; + public query func searchProfile(term: Text): async [Profile] { directory.findBy(term) }; + + system func preupgrade() { + vertexListEntries := graph.getVertexListEntries(); + edgeListEntries := graph.getEdgeListEntries(); + directoryMapEntries := directory.getHashMapEntries(); + }; + + system func postupgrade() { + vertexListEntries := []; + edgeListEntries := []; + directoryMapEntries := []; + }; } \ No newline at end of file