diff --git a/src/App.js b/src/App.js index ec7dca9..737fdf7 100644 --- a/src/App.js +++ b/src/App.js @@ -12,7 +12,7 @@ class App extends Component { this.state = { state: null, }; - localforage.getItem("loginData").then((login) => { + localforage.getItem("login").then((login) => { if (login) { this.loginData = login; this.setState({ state: "matrix" }); diff --git a/src/Avatar.js b/src/Avatar.js new file mode 100644 index 0000000..2b780d0 --- /dev/null +++ b/src/Avatar.js @@ -0,0 +1,35 @@ +import { Component } from "inferno"; + +const presenceColor = { + online: "green", + offline: "gray", + unavailable: "orange", +}; + +class Avatar extends Component { + render() { + return ( +
+ +
+
+ ); + } +} + +export default Avatar; diff --git a/src/ChatDMItem.js b/src/ChatDMItem.js new file mode 100644 index 0000000..b1ef1b2 --- /dev/null +++ b/src/ChatDMItem.js @@ -0,0 +1,44 @@ +import { Component } from "inferno"; +import Avatar from "./Avatar"; +import IconListItem from "./ui/IconListItem"; + +class ChatDMItem extends Component { + updatePresence = (event, user) => { + if (user.userId === this.props.userId) + this.setState({ + online: user.presence, + displayName: user.displayName, + }); + }; + + constructor(props) { + super(props); + this.state = { + lastEvent: "", + online: "offline", + displayName: "", + }; + } + + componentWillMount() { + window.mClient.addListener("User.presence", this.updatePresence); + } + + componentWillUnmount() { + window.mClient.removeListener("User.presence", this.updatePresence); + } + + render() { + return ( + } + primary={this.state.displayName} + secondary={this.state.lastEvent} + key={this.props.isFocused} + isFocused={this.props.isFocused} + /> + ); + } +} + +export default ChatDMItem; diff --git a/src/DMsView.js b/src/DMsView.js new file mode 100644 index 0000000..18362eb --- /dev/null +++ b/src/DMsView.js @@ -0,0 +1,75 @@ +import { Component } from "inferno"; +import * as matrixcs from "matrix-js-sdk"; + +import ListView from "./ListView"; +import personIcon from "./person_icon.png"; +import ChatDMItem from "./ChatDMItem"; +import TextListItem from "./ui/TextListItem"; + +const AVATAR_WIDTH = 36; +const AVATAR_HEIGHT = 36; + +class DMsView extends Component { + handleKeyDown = (evt) => { + if (evt.key === "Call" || evt.key === "c") { + // 1. show a dialog to select call type + // 2. start a call + } + }; + + cursorChangeCb = (cursor) => { + this.setState({ cursor: cursor }); + }; + + getDMs = (room) => + room.getJoinedMemberCount() === 2 && room.getMyMembership() === "join"; + constructor(props) { + super(props); + this.rooms = []; + this.state = { + cursor: 0, + }; + } + + componentWillMount() { + document.addEventListener("keydown", this.handleKeyDown); + } + + componentWillUnmount() { + document.removeEventListener("keydown", this.handleKeyDown); + } + + render() { + const rooms = window.mClient.getVisibleRooms().filter(this.getDMs); + let renderedRooms = rooms.map((room) => { + console.log(room); + let theOtherId = room.guessDMUserId(); + let mxcUrl = window.mClient.getUser(theOtherId).avatarUrl; + let avatarUrl; + if (mxcUrl) { + avatarUrl = matrixcs.getHttpUriForMxc( + window.mClient.getHomeserverUrl(), + mxcUrl, + AVATAR_WIDTH, + AVATAR_HEIGHT, + "scale", + true + ); + } else { + avatarUrl = personIcon; + } + return ; + }); + console.log("================"); + if (renderedRooms.length === 0) { + renderedRooms.push(); + } + return ( + + {renderedRooms} + + ); + } +} + +export default DMsView; diff --git a/src/Login.js b/src/Login.js index e123b04..c524642 100644 --- a/src/Login.js +++ b/src/Login.js @@ -31,7 +31,7 @@ class Login extends Component { case "m.login.password": window.mClient .loginWithPassword( - `@${this.username}:${this.homeserverUrl}`, + `@${this.username}:${this.homeserverUrl.replace("https://", "")}`, this.password ) .then((result) => { @@ -42,7 +42,6 @@ class Login extends Component { }); }) .catch((err) => { - window.e666 = err; switch (err.errcode) { case "M_FORBIDDEN": alert("Incorrect login credentials"); @@ -177,12 +176,14 @@ class Login extends Component { > {listViewChildren} +
window.close()} rightText="Next" rightCb={this.rightCb} /> +
); } diff --git a/src/Matrix.js b/src/Matrix.js index 1dfbc6d..1e36790 100644 --- a/src/Matrix.js +++ b/src/Matrix.js @@ -1,17 +1,74 @@ import { Component } from "inferno"; -import * as localforage from "localforage"; +import * as matrixcs from "matrix-js-sdk"; + +import TabView from "./TabView"; +import SoftKey from "./ui/SoftKey"; +import DMsView from "./DMsView"; class Matrix extends Component { + onTabChange = (index) => { + this.setState({ currentTab: index }); + }; + constructor(props) { super(props); + window.mClient = matrixcs.createClient({ + userId: props.data.user_id, + accessToken: props.data.access_token, + deviceId: props.data.device_id, + baseUrl: props.data.well_known["m.homeserver"].base_url, + identityServer: + props.data.well_known["m.identity_server"] && + props.data.well_known["m.identity_server"].base_url, + }); + const client = window.mClient; + client.on("RoomMember.membership", (event, member) => { + const myId = client.getUserId(); + if (member.membership === "invite" && member.userId === myId) { + client.joinRoom(member.roomId).then(() => + alert(`Auto joined ${member.name} after invite`)); + } + }); + client.on("Call.incoming", (call) => { + call.once("state", (state) => { + if (this.state.isCall) { + // reject this call if there's already + // a call going + call.reject(); + } else { + this.call = call; + this.setState({ isCall: true }); + } + }); + }); + client.startClient({ initialSyncLimit: 3, lazyLoadMembers: true }); + this.tabs = ["People", "Rooms", "Invites", "Settings", "About"]; this.state = { - state: null, + currentTab: 0, + isCall: false }; } render() { - console.log(this.props.data); - return <>Hi!; + return ( + <> + + +

{"Rooms not implemented"}

+

{"Invites are not implemented and auto accepted"}

+

{"Settings not implemented"}

+

{"About info coming soon..."}

+
+
+ { + if (window.confirm("Quit?")) window.close(); + }} + /> +
+ + ); } } diff --git a/src/TabView.js b/src/TabView.js new file mode 100644 index 0000000..ec0cfe8 --- /dev/null +++ b/src/TabView.js @@ -0,0 +1,48 @@ +import { Component } from "inferno"; +import Tabs from "./ui/Tabs"; +import Tab from "./ui/Tab"; +import colors from "KaiUI/src/theme/colors.scss"; +import "KaiUI/src/views/TabView/TabView.scss"; + +const prefixCls = "kai-tab-view"; +const tabViewTabs = `${prefixCls}-tabs`; +const tabViewContent = `${prefixCls}-content`; + +class TabView extends Component { + handleChangeIndex = (tabIndex) => { + this.setState({ activeTab: tabIndex }); + if (this.props.onChangeIndex) this.props.onChangeIndex(tabIndex); + }; + + constructor(props) { + super(props); + const { focusColor } = props; + this.focusColor = focusColor || colors.defaultFocusColor; + this.tabs = this.props.tabLabels.map((label) => ( + + )); + this.tabs[0].props.isActive = true; + this.state = { activeTab: 0 }; + } + + render() { + return ( +
+
+ + {this.tabs} + +
+
+ {this.props.children.map( + (content, index) => + index === this.state.activeTab ? content : null, + this + )} +
+
+ ); // XXX maybe use filter()? + } +} + +export default TabView; diff --git a/src/Waiting.js b/src/Waiting.js index 52f4fc0..dd60fa2 100644 --- a/src/Waiting.js +++ b/src/Waiting.js @@ -1,12 +1,23 @@ -import { Component } from "inferno"; +import { createTextVNode, Component } from "inferno"; import "./waiting.css"; import cow from "./cowsay-pleasewait.png"; +let tips = [ + "Use call key in People tab to quickly call someone", + "In About tab, you can see credits", + "Contact developer by pressing Call key in About tab", +]; + +function randomTip() { + return tips[Math.random() * tips.length]; +} + class Waiting extends Component { render() { return ( <> +

{createTextVNode(randomTip())}

); } diff --git a/src/adrianavatar.png b/src/adrianavatar.png new file mode 100644 index 0000000..d69dedc Binary files /dev/null and b/src/adrianavatar.png differ diff --git a/src/farooqlogo.png b/src/farooqlogo.png new file mode 100644 index 0000000..d946a46 Binary files /dev/null and b/src/farooqlogo.png differ diff --git a/src/morecolor.scss b/src/morecolor.scss new file mode 100644 index 0000000..4690d58 --- /dev/null +++ b/src/morecolor.scss @@ -0,0 +1,7 @@ +@import "KaiUI/src/theme/theme.scss"; + +$item-bg-focus-color: var(--color-carrotorange); + +:export { + item_bg_focus_color: $item-bg-focus-color; +} diff --git a/src/person_icon.png b/src/person_icon.png new file mode 100644 index 0000000..0dfcc9c Binary files /dev/null and b/src/person_icon.png differ diff --git a/src/ui/IconListItem.js b/src/ui/IconListItem.js index da276c3..62085a4 100644 --- a/src/ui/IconListItem.js +++ b/src/ui/IconListItem.js @@ -4,41 +4,16 @@ import "KaiUI/src/components/IconListItem/IconListItem.scss"; import morecolor from "../morecolor.scss"; const prefixCls = "kai-il"; +const lineCls = `${prefixCls}-line`; +const itemCls = prefixCls; +const primaryCls = `${prefixCls}-line`; class IconListItem extends Component { constructor(props) { super(props); - const { - primary, - secondary, - icon, - iconSrc, - focusClass, - iconWidth, - disabled, - className, - onClick, - } = props; - this.primary = primary; - this.secondary = secondary; - this.itemCls = prefixCls; - this.lineCls = `${prefixCls}-line`; - this.primaryCls = `${this.primaryCls}-primary`; - this.secondaryCls = `${prefixCls}-secondary ${secondary ? "" : "hidden"}`; - this.disabledCls = disabled ? `${prefixCls}-disabled` : ""; - this.disabled = disabled; - this.className = className; - this.focusClass = focusClass; - this.handleClick = onClick; - if (iconSrc) - this.renderedIcon = ; - else if (icon instanceof String && icon.startsWith("kai")) - this.renderedIcon = ( - - ); - // Then we assume it is a valid element TODO: check for this - else this.renderedIcon = {icon}; - this.divRef = createRef(); + this.secondaryCls = `${prefixCls}-secondary ${props.secondary ? "" : "hidden"}`; + this.disabledCls = props.disabled ? `${prefixCls}-disabled` : ""; + this.divRef = createRef(); } componentDidUpdate(lastProps, lastState) { @@ -53,17 +28,26 @@ class IconListItem extends Component { const iconCls = `${prefixCls}-icon-${ this.props.isFocused ? "focused" : "unfocused" }`; + let renderedIcon; + if (this.props.iconSrc) + renderedIcon = ; + else if (this.props.icon instanceof String && this.props.icon.startsWith("kai")) + renderedIcon = ( + + ); + // Then we assume it is a valid element TODO: check for this + else renderedIcon = {this.props.icon}; return (
-
{this.renderedIcon}
-
- - {createTextVNode(this.primary)} +
{renderedIcon}
+
+ + {createTextVNode(this.props.primary)}
diff --git a/src/ui/Separator.js b/src/ui/Separator.js index 9ea9f51..79b8807 100644 --- a/src/ui/Separator.js +++ b/src/ui/Separator.js @@ -8,13 +8,12 @@ class Separator extends Component { constructor(props) { props.unFocusable = true; super(props); - this.text = props.text; } render() { return (
- {this.text} + {this.props.text}
); } diff --git a/src/ui/Tab.js b/src/ui/Tab.js index 5a99535..9204077 100644 --- a/src/ui/Tab.js +++ b/src/ui/Tab.js @@ -6,13 +6,11 @@ const prefixCls = "kai-tab"; class Tab extends Component { constructor(props) { - const { label, onTabChange, focusColor } = props; + const { focusColor } = props; super(props); this.style = { "--tab-underline-color": focusColor || colors.defaultFocusColor, }; - this.onTabChange = onTabChange; - this.label = label; } render() { @@ -22,14 +20,14 @@ class Tab extends Component { return (
{ - this.onTabChange && this.onTabChange(this.index); + this.props.onTabChange && this.props.onTabChange(this.index); }} className={actPrefixCls} style={this.style} key={this.props.isActive} >
- {createTextVNode(this.label)} + {createTextVNode(this.props.label)}
); diff --git a/src/ui/Tabs.js b/src/ui/Tabs.js index 01ada9e..2436b08 100644 --- a/src/ui/Tabs.js +++ b/src/ui/Tabs.js @@ -9,11 +9,11 @@ class Tabs extends Component { if (!["ArrowLeft", "ArrowRight"].includes(evt.key)) return; let index = this.state.activeChild; console.log("[Tabs] Old activeChild:", index); - this.children[index].props.isActive = false; + this.props.children[index].props.isActive = false; switch (evt.key) { case "ArrowLeft": index--; - index += this.children.length; + index += this.props.children.length; break; case "ArrowRight": index++; @@ -21,22 +21,19 @@ class Tabs extends Component { default: break; } - index %= this.children.length; - this.children[index].props.isActive = true; - findDOMNode(this.children[index]).scrollIntoView({ + index %= this.props.children.length; + this.props.children[index].props.isActive = true; + findDOMNode(this.props.children[index]).scrollIntoView({ behavior: "auto", block: "start", inline: "center", }); this.setState({ activeChild: index }); - this.onChangeIndex && this.onChangeIndex(index); + this.props.onChangeIndex && this.props.onChangeIndex(index); }; constructor(props) { - const { onChangeIndex, children } = props; super(props); - this.children = children; - this.onChangeIndex = onChangeIndex; this.state = { activeChild: 0 }; } @@ -49,7 +46,7 @@ class Tabs extends Component { } render() { - return
{this.children}
; + return
{this.props.children}
; } } diff --git a/src/waiting.css b/src/waiting.css index 939be7e..fc68805 100644 --- a/src/waiting.css +++ b/src/waiting.css @@ -6,3 +6,7 @@ img { height: 110px; width: 192px; } + +p { + text-align: center; +}