refactor Components, add newRoom.vue/userSearch.vue/popup.vue/overlay.vue/imageViewer.vue, some design changes

master
adb 3 years ago
parent c0d9de4fcf
commit 18bb86a7a9

@ -19,6 +19,7 @@
"sass": "^1.29.0",
"superagent": "^6.1.0",
"v-emoji-picker": "^2.3.1",
"viewerjs": "*",
"vue": "^2.6.11",
"ws": "^7.3.1"
},

@ -8,7 +8,7 @@
</template>
<script>
import error from "@/components/error.vue";
import error from '@/components/layout/error.vue';
export default {
name: 'App',

@ -22,14 +22,14 @@
</template>
<script>
import newMessage from '@/components/newMessage.vue';
import topBanner from '@/components/topBanner.vue';
import Icon from '@/components/icon';
import {matrix} from '@/main';
import newMessage from '@/components/chat/newMessage.vue';
import topBanner from '@/components/chat/topBanner.vue';
import Icon from '@/components/layout/icon';
import {matrix} from '@/main.js';
import splitArray from '@/lib/splitArray.js'
import timeline from '@/components/timeline';
import timeline from '@/components/chat/timeline';
import scrollHandler from '@/lib/scrollHandler';
import {getUser} from "@/lib/matrixUtils";
import {getUser} from '@/lib/matrixUtils';
export default {
name: 'chat',

@ -1,39 +1,34 @@
<template>
<div class="chatInfo">
<div class="box">
<div class="scrollContainer">
<div class="topContainer">
<avatar
class="roomImage"
:mxcURL="getMxcFromRoom(room)"
:fallback="room.roomId"
:size="5"
/>
<div class="info">
<div class="roomName">{{room.name}}</div>
<div class="users">{{members.length}} members</div>
</div>
</div>
<user-list-element v-for="member in members.slice(0,20)" :key="member" :user="getUser(member)"/>
<p v-if="members.length>20">and {{members.length-20}} other members</p>
<popup :on-close="closeChatInfo" class="popup">
<div class="topContainer">
<avatar
class="roomImage"
:mxcURL="getMxcFromChat(room)"
:fallback="room.roomId"
:size="5"
/>
<div class="info">
<div class="roomName">{{room.name}}</div>
<div class="users">{{members.length}} members</div>
</div>
</div>
<icon class="closeBtn" @click.native="closeChatInfo()" ic="./sym/ic_close_white.svg" />
</div>
<user-list-element v-for="member in members.slice(0,20)" :key="member" :user="getUser(member)"/>
<p v-if="members.length>20">and {{members.length-20}} other members</p>
</popup>
</template>
<script>
import icon from './icon.vue';
import UserListElement from "@/components/userListElement";
import avatar from "@/components/avatar";
import {getMxcFromRoom} from "@/lib/getMxc";
import {getUser} from "@/lib/matrixUtils";
import UserListElement from '@/components/matrix/userListElement';
import avatar from '@/components/matrix/avatar';
import {getMxcFromChat} from '@/lib/getMxc';
import {getUser} from '@/lib/matrixUtils';
import popup from '@/components/layout/popup';
export default {
name: "chatInformation",
name: 'chatInformation',
components:{
avatar,
UserListElement,
icon,
popup
},
props:{
room: {},
@ -44,7 +39,7 @@ export default {
return Object.keys(this.room.currentState.members)
},
getUser,
getMxcFromRoom
getMxcFromChat
},
data(){
return{
@ -84,6 +79,9 @@ export default {
height: 100%;
}
}
.popup{
min-height: calc(100% - 10rem);
}
.closeBtn{
position: absolute;
top: 0;

@ -19,8 +19,8 @@ import {calcUserName} from '@/lib/matrixUtils';
import {parseMessage} from '@/lib/eventUtils';
import {getTime} from '@/lib/getTimeStrings';
import {getMediaUrl} from '@/lib/getMxc';
import ReplyEvent from '@/components/replyEvent';
import EventContent from '@/components/eventContent';
import ReplyEvent from '@/components/chat/replyEvent';
import EventContent from '@/components/chat/eventContent';
export default {
name: 'message',

@ -1,26 +1,18 @@
<template>
<div v-if="content.msgtype==='m.text'" v-html="parseMessage(content.body)" :class="getEmojiClass(content)"/>
<div v-else-if="content.msgtype==='m.notice'" class="notice" v-html="parseMessage(content.body)"/>
<div v-else-if="content.msgtype==='m.image'" class="image">
<img :src="getSource(content.url)" :alt="content.body" :class="`${compact?'compact':''}`"/><br>
<image-viewer v-else-if="content.msgtype==='m.image'" :alt="content.body" class="image" :class="compact?'compact':''">
<img :src="getSource(content.url)" :alt="content.body" :class="compact?'compact':''"/><br>
{{content.body}}
</div>
</image-viewer>
<div v-else-if="content.msgtype==='m.file'" :class="`file ${compact?'compact':''}`">
<a :href="getSource(content.url)" target="_blank">
<div class="fileContent">
<icon
title="file"
ic="./sym/ic_attach_file_white.svg"
class="download"
/>
<div class="filename">
{{content.filename || getSource(content.url)}}
<div class="fileContent">
<icon title="file" ic="./sym/ic_attach_file_white.svg" class="download"/>
<div class="filename">{{content.filename || getSource(content.url)}}</div>
</div>
</div>
</a>
<div class="text">
{{content.body}}
</div>
<div class="text">{{content.body}}</div>
</div>
<div v-else-if="content.msgtype==='m.audio'" :class="`audio ${compact?'compact':''}`">
<audio controls :class="`${compact?'compact':''}`">
@ -43,11 +35,15 @@
<script>
import {getMediaUrl} from '@/lib/getMxc';
import {parseMessage} from '@/lib/eventUtils';
import Icon from '@/components/icon';
import Icon from '@/components/layout/icon';
import imageViewer from '@/components/layout/imageViewer';
export default {
name: 'eventContent',
components: {Icon},
components: {
Icon,
imageViewer
},
props: {
content: Object,
compact: {

@ -47,15 +47,15 @@
</template>
<script>
import icon from '@/components/icon.vue';
import icon from '@/components/layout/icon.vue';
import {matrix} from '@/main.js';
import {parseMessage} from '@/lib/eventUtils';
import {calcUserName} from '@/lib/matrixUtils';
import ReplyEvent from '@/components/replyEvent';
import ReplyEvent from '@/components/chat/replyEvent';
import {VEmojiPicker} from 'v-emoji-picker';
import EventContent from '@/components/eventContent';
import SoundRecorder from '@/components/soundRecorder';
import FileUpload from '@/components/fileUpload';
import EventContent from '@/components/chat/eventContent';
import SoundRecorder from '@/components/layout/soundRecorder';
import FileUpload from '@/components/layout/fileUpload';
import {readFileBlob} from '@/lib/readFileBlob';
export default {

@ -40,8 +40,8 @@
</template>
<script>
import event from '@/components/event';
import avatar from '@/components/avatar';
import event from '@/components/chat/event';
import avatar from '@/components/matrix/avatar';
import splitArray from '@/lib/splitArray';
import {getDate, getTime} from '@/lib/getTimeStrings';
import {getUser, calcUserName} from '@/lib/matrixUtils';

@ -3,7 +3,7 @@
<div>
<icon @click.native="closeChat()" class="topIcon" ic="./sym/ic_arrow_back_white.svg" />
<div @click="openChatInfo()" class="container">
<avatar class="topIcon avatar" :mxcURL="getMxcFromRoom(room)" :fallback="room.roomId" :size="3"/>
<avatar class="topIcon avatar" :mxcURL="getMxcFromChat(room)" :fallback="room.roomId" :size="3"/>
<div class="chatName">{{room.name}}</div>
<div class="info">{{Object.keys(room.currentState.members).length}} members</div>
</div>
@ -11,13 +11,12 @@
</div>
</template>
<script>
import icon from '@/components/icon.vue';
import avatar from "@/components/avatar";
import {getMxcFromRoom} from "@/lib/getMxc";
import icon from '@/components/layout/icon.vue';
import avatar from '@/components/matrix/avatar';
import {getMxcFromChat} from '@/lib/getMxc';
export default {
name: "topBanner",
name: 'topBanner',
components:{
icon,
avatar
@ -28,7 +27,7 @@ export default {
openChatInfo: Function
},
methods: {
getMxcFromRoom
getMxcFromChat
}
}
</script>
@ -40,6 +39,7 @@ export default {
width: 100%;
height: 3.5rem;
background-color: #1d1d1d;
cursor: pointer;
}
.topIcon{
position: absolute;

@ -14,7 +14,7 @@
</template>
<script>
import icon from '@/components/icon';
import icon from '@/components/layout/icon';
import {readFileBlob} from '@/lib/readFileBlob';
export default {

@ -0,0 +1,41 @@
<template>
<div class="viewer" ref="images">
<slot></slot>
</div>
</template>
<script>
import Viewer from 'viewerjs';
import 'viewerjs/dist/viewer.css';
export default {
name: 'imageViewer',
props: {
options: Object
},
mounted() {
new Viewer(this.$refs.images, this.options||{
inline: false,
navbar: false,
button: true,
toolbar: {
reset: {show: 1, size: 'large'},
zoomIn: {show: 1, size: 'large'},
zoomOut: {show: 1, size: 'large'},
rotateLeft: {show: 1, size: 'large'},
rotateRight: {show: 1, size: 'large'}
},
zoomRatio: 0.25,
minZoomRatio: 0.5,
maxZoomRatio: 10,
toggleOnDblclick: true,
});
}
}
</script>
<style scoped lang="scss">
.viewer{
cursor: pointer;
}
</style>

@ -0,0 +1,24 @@
<template>
<div v-if="this.$slots.default" class="overlay">
<slot></slot>
</div>
</template>
<script>
export default {
name: 'overlay'
}
</script>
<style scoped>
.overlay{
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #111d;
user-select: none;
z-index: 50;
}
</style>

@ -0,0 +1,91 @@
<template>
<div class="popup">
<div class="scrollContainer" ref="scrollContainer">
<div class="content">
<slot></slot>
</div>
<icon v-if="onClose" class="closeBtn" @click.native="onClose(false)" ic="./sym/ic_close_white.svg" />
</div>
</div>
</template>
<script>
import icon from '@/components/layout/icon';
export default {
name: 'popup',
components: {
icon
},
props: {
onClose: Function
},
methods: {
calcMaxHeight(){
this.$refs.scrollContainer.style.maxHeight = `calc(${this.$el.parentElement.clientHeight}px - 4rem`;
}
},
mounted() {
this.calcMaxHeight();
}
}
</script>
<style scoped>
.popup{
position: relative;
top: 5rem;
width: calc(100% - 4rem);
max-width: 30rem;
min-height: 10rem;
background-color: #1d1d1d;
box-shadow: 6px 6px 20px #111;
border-radius: 1rem;
z-index: 30;
overflow: hidden;
}
.scrollContainer{
position: relative;
max-height: 30rem;
top: 0;
width: calc(100% - 2rem);
padding: 0 1rem 0 1rem;
overflow-y: auto;
overflow-x: hidden;
}
@media (max-width: 30rem) {
.popup{
transform: unset;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 0;
}
.scrollContainer{
max-height: 100% !important;
}
}
@media (max-height: 40rem) {
.popup{
top: 0;
height: 100%;
}
.scrollContainer{
max-height: 100% !important;
}
}
.closeBtn{
position: fixed;
top: 0;
right: 0;
background-color: #0000;
box-shadow: none;
}
.content{
position: relative;
padding: 1rem 0 1rem 0;
width: 100%;
height: auto;
}
</style>

@ -0,0 +1,33 @@
<template>
<popup :on-close="callback">
<h2>{{title}}</h2>
<p>{{question}}</p>
<textbtn :text="action" @click.native="callback(true)"/>
<textbtn text="Cancel" @click.native="callback(false)" class="outline"/>
</popup>
</template>
<script>
import textbtn from '@/components/layout/textbtn';
import popup from '@/components/layout/popup';
export default {
name: 'popupQuestion',
components: {
textbtn,
popup
},
props:{
title: String,
question: String,
callback: Function,
action: {
type: String,
default: 'Apply'
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -22,7 +22,7 @@
</template>
<script>
import icon from '@/components/icon';
import icon from '@/components/layout/icon';
import Recorder from 'recorder-js';
const audioContext = new (window.AudioContext || window.webkitAudioContext)();

@ -0,0 +1,55 @@
<template>
<button class="btn">
<div class="btnText">{{text}}</div>
</button>
</template>
<script>
export default {
name: "textbtn",
props:{
text: String,
}
}
</script>
<style scoped>
.btn{
cursor: pointer;
border: none;
height: 2.5rem;
padding-left: 1.5rem;
padding-right: 1.5rem;
background-color: var(--primary);
box-shadow: var(--shadow100);
border-radius: 1rem;
margin: 0.2rem;
}
.btn.primary{
background-color: var(--primary);
}
.btn.underline{
background-color: unset;
text-decoration: underline;
color: #fff;
box-shadow: none;
}
.btn.rounded{
border-radius: 1.25rem;
}
.btn.outline{
background-color: unset;
box-shadow: none;
text-decoration: underline;
border: #fff solid 1px;
}
.btn.squared{
border-radius: 0;
}
.btnText {
position: relative;
font-size: 1rem;
color:#fff;
font-family:Arial, "lucida console", sans-serif;
}
</style>

@ -17,6 +17,9 @@ export default {
<style scoped lang="scss">
.box{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 8rem;
width: 8rem;
background-color: #1d1d1d;

@ -0,0 +1,86 @@
<template>
<popup :on-close="callback">
<h2>New room</h2>
<input v-model="name" type="text" placeholder="Room name">
<select v-model="access">
<option>private</option>
<option>public</option>
</select><br>
<textarea v-model="description" placeholder="Room description"></textarea><br>
<h3>Add User</h3>
<user-search :filter="prop=>!users.find(temp=>temp===prop)" :callback="addUser" class="userSearch"/>
<h3 v-if="users.length">User</h3>
<div>
<user-list-element
v-for="user in users"
:user="user" :key="user.userId"
@click.native="removeUser(user)"
/>
</div>
<textbtn :text="action" @click.native="createRoom({name, users, description, access}).then(callback)"/>
<textbtn text="Cancel" @click.native="callback(false)" class="outline"/>
<overlay v-if="loading"><throbber text="loading"/></overlay>
</popup>
</template>
<script>
import textbtn from '@/components/layout/textbtn';
import popup from '@/components/layout/popup';
import userListElement from '@/components/matrix/userListElement';
import UserSearch from '@/components/matrix/userSearch';
import overlay from '@/components/layout/overlay';
import throbber from '@/components/layout/throbber';
import {createRoom} from '@/lib/matrixUtils';
export default {
name: 'popupQuestion',
components: {
UserSearch,
textbtn,
popup,
userListElement,
overlay,
throbber
},
props:{
props: Object,
callback: Function,
action: {
type: String,
default: 'Apply'
}
},
data(){
return{
name: this.props.name,
description: this.props.description,
users: [],
loading: false,
access: 'private'
}
},
methods:{
addUser(user){
if (this.users.find(tmp => tmp === user)) return;
this.users.push(user);
this.userSearch = '';
},
removeUser(user){
this.users = this.users.filter(tmp => tmp !== user);
},
async createRoom(props){
this.loading = true;
return await createRoom(props);
}
}
}
</script>
<style scoped lang="scss">
textarea{
width: 100%;
}
.userSearch{
}
</style>

@ -2,20 +2,20 @@
<div class="roomListElement" :title="room.name">
<div class="imageContainer">
<avatar
class="roomImage"
:mxcURL="getMxcFromRoom(room)"
:fallback="room.roomId"
:size="3"
class="roomImage"
:mxcURL="getMxcFromChat(room)"
:fallback="room.roomId"
:size="3"
/>
</div>
<div class="roomListName">{{room.name}}</div>
<div class="status">{{getPreviewString(room)}}</div>
<div class="status">{{previewString}}</div>
</div>
</template>
<script>
import avatar from '@/components/avatar';
import {getMxcFromRoom} from '@/lib/getMxc';
import avatar from '@/components/matrix/avatar';
import {getMxcFromChat} from '@/lib/getMxc';
import {getTime} from '@/lib/getTimeStrings';
import {calcUserName} from '@/lib/matrixUtils';
@ -37,8 +37,16 @@ export default {
return room.timeline[room.timeline.length-1]
&& room.timeline[room.timeline.length-1].event;
},
getMxcFromChat,
calcUserName,
getMxcFromRoom
},
data(){
return {
previewString: 'loading'
}
},
created() {
this.previewString = this.getPreviewString(this.room);
}
}
</script>

@ -1,21 +1,21 @@
<template>
<div class="userListElement" :title="user.userId">
<div class="imageContainer">
<div :class="compact?'userListElement compact':'userListElement'" :title="user.userId">
<div :class="compact?'imageContainer compact':'imageContainer'">
<avatar
class="userImage"
:class="compact?'userImage compact':'userImage'"
:mxcURL="user.avatarUrl"
:fallback="user.userId"
:size="3"
:size="compact?1.5:3"
/>
<div v-if="user.currentlyActive" class="online"></div>
</div>
<div class="userListName">{{user.displayName || user.userId}}</div>
<div class="status">{{user.presence}}</div>
<div :class="compact?'userListName compact':'userListName'">{{user.displayName || user.userId}}</div>
<div v-if="!compact" class="status">{{user.presence}}</div>
</div>
</template>
<script>
import avatar from '@/components/avatar';
import avatar from '@/components/matrix/avatar';
export default {
name: 'userListElement',
@ -23,7 +23,11 @@ export default {
avatar
},
props:{
user: Object
user: Object,
compact: {
type: Boolean,
default: false
}
}
}
</script>
@ -50,11 +54,11 @@ export default {
position: absolute;
bottom: 0;
right: 0;
height: 0.6rem;
width: 0.6rem;
height: 0.5rem;
width: 0.5rem;
background-color: #42b983;
border-radius: 50%;
border: 0.2rem solid #222;
border: 2px solid #222;
}
}
.userListName{
@ -81,4 +85,15 @@ export default {
.userListElement:hover{
background-color: #4444;
}
.userListElement.compact{
height: 1.5rem;
}
.imageContainer.compact{
height: 1.5rem;
width: 1.5rem;
}
.userListName.compact{
left: 2.5rem;
font-size: 1rem;
}
</style>

@ -0,0 +1,78 @@
<template>
<div class="userSearch">
<div v-if="userSearch" class="box">
<div class="results">
<user-list-element
v-for="user in matrix.client.getUsers()
.filter(prop=>matchResults(prop.displayName, userSearch)||matchResults(prop.userId, userSearch))
.filter(filter)
.slice(0,16)"
:user="user" :key="user.userId"
@click.native="callback(user); userSearch='';"
:compact="true"
/>
</div>
<div class="filler"></div>
</div>
<input v-model="userSearch" type="text" placeholder="search" class="input">
</div>
</template>
<script>
import userListElement from '@/components/matrix/userListElement';
import {matrix} from '@/main';
export default {
name: 'userSearch',
components:{
userListElement
},
props:{
callback: Function,
filter: Function
},
methods:{
matchResults(prop, search){
return prop.toLowerCase().includes(search.toLowerCase().trim());
}
},
data(){
return {
matrix,
userSearch: ''
}
}
}
</script>
<style scoped>
.userSearch{
position: relative;
background-color: #1d1d1d;
height: 2.2rem;
width: 14rem;
margin: 0.2rem;
}
.input{
position: absolute;
width: 13rem;
margin: 0;
}
.filler{
height: 2.5rem;
}
.box{
position: absolute;
bottom: -0.4rem;
left: -0.4rem;
background-color: var(--grey500);
box-shadow: var(--shadow200);
width: calc(100% + 0.8rem);
border-radius: 0.6rem;
}
.results{
max-height: 10.5rem;
overflow-y: auto;
overflow-x: hidden;
}
</style>

@ -1,34 +0,0 @@
<template>
<div class="questionBox">
<p class="text">{{question}}</p>
<textbtn text="yes" @click.native="callback(true)"/>
<textbtn text="no" @click.native="callback(false)"/>
</div>
</template>
<script>
import textbtn from '@/components/textbtn';
export default {
name: 'popupQuestion',
components: {textbtn},
props:{
question: String,
callback: Function
}
}
</script>
<style scoped lang="scss">
.questionBox{
position: absolute;
width: auto;
background-color: #1d1d1d;
box-shadow: 6px 6px 10px #111;
border-radius: 1.5rem;
.text{
position: relative;
width: 100%;
text-align: center;
}
}
</style>

@ -1,34 +0,0 @@
<template>
<button class="btn">
<div class="btnText">{{text}}</div>
</button>
</template>
<script>
export default {
name: "textbtn",
props:{
text: String
}
}
</script>
<style scoped>
.btn{
cursor: pointer;
border: none;
height: 2.5rem;
padding-left: 2rem;
padding-right: 2rem;
background-color: #00BCD4;
box-shadow: 3px 3px 10px #222;
border-radius: 1.25rem;
margin: 1rem;
}
.btnText {
position: relative;
font-size: 1.4rem;
color:#fff;
font-family:Arial, "lucida console", sans-serif;
}
</style>

@ -1,36 +0,0 @@
<template>
<div class="overlay">
<throbber :text="text" class="throbber"/>
</div>
</template>
<script>
import throbber from "@/components/throbber";
export default {
name: "throbberOverlay",
components:{
throbber
},
props: {
text: String
}
}
</script>
<style scoped>
.throbber{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.overlay{
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background-color: #111d;
user-select: none;
}
</style>

@ -18,6 +18,12 @@ export function getMxcFromRoomId(roomId){
return getMxcFromRoom(matrix.client.getRoom(roomId));
}
export function getMxcFromChat(room){
return Object.keys(room.currentState.members).length===2
?getMxcFromUserId(Object.keys(room.currentState.members).filter(tmp=>tmp!==matrix.user)[0])
:getMxcFromRoom(room);
}
export function getPreviewUrl(mxcUrl, size = 64, resizeMethod = 'crop'){
return matrix.client.mxcUrlToHttp(mxcUrl, size, size, resizeMethod);
}

@ -15,4 +15,16 @@ export function isValidUserId(id){
}
export function isValidRoomId(id){
return id.match(/^(#|!)[a-zA-Z0-9_.+-]+:[a-z0-9.-]+\.[a-z]+$/);
}
export async function createRoom({name = '', users = [], description = undefined, access = 'private'}){
if (users.length === 0) return;
return matrix.client.createRoom({name}).then(async room => {
await Promise.all(users.map(async user => await matrix.client.invite(room.room_id, user.userId)));
if (description) await matrix.client.setRoomTopic(room.room_id, description);
await matrix.client.setGuestAccess(room.room_id, access === 'public'
?{allowJoin: true, allowRead: true}
:{allowJoin: false, allowRead: false}
);
return matrix.client.getRoom(room.room_id);
});
}

@ -1,6 +1,5 @@
import VueRouter from 'vue-router';
import login from '@/views/login';
import chat from '@/views/chat';
import rooms from '@/views/rooms';
import admin from '@/views/admin';
@ -16,11 +15,6 @@ export const router = new VueRouter({
name: 'login',
component: login
},
{
path: '/chat/*',
name: 'chat',
component: chat
},
{
path: '/rooms/*',
name: 'room',

@ -33,23 +33,25 @@
</div>
<textbtn @click.native="updateUser()" text="update user"/>
</div>
<throbber-overlay v-if="loading" :text="loading"/>
<overlay v-if="loading"><throbber :text="loading"/></overlay>
</div>
</template>
<script>
import {matrix} from "@/main";
import {AdminAPI} from "@/lib/AdminAPI";
import icon from "@/components/icon";
import textbtn from "@/components/textbtn";
import ThrobberOverlay from "@/components/throbberOverlay";
import {matrix} from '@/main';
import {AdminAPI} from '@/lib/AdminAPI';
import icon from '@/components/layout/icon';
import textbtn from '@/components/layout/textbtn';
import overlay from '@/components/layout/overlay';
import throbber from '@/components/layout/throbber';
export default {
name: "admin",
name: 'admin',
components:{
ThrobberOverlay,
icon,
textbtn
textbtn,
overlay,
throbber
},
data(){
return{

@ -7,30 +7,32 @@
<input v-model="password" class="input" name="password" type="password" maxlength="30" placeholder="password"><br>
<input v-model="homeServer" class="input" name="homeserver" placeholder="https://matrix.org"><br>
<div v-if="loginError" class="info">{{loginError}}</div>
<textbtn type="submit" text="login" />
<textbtn type="submit" text="login" class="rounded"/>
</form>
<div v-else>
<p>you are already logged in</p>
<textbtn @click.native="$router.push('rooms')" text="chat" />
<textbtn @click.native="logout()" text="logout" />
<textbtn @click.native="logout()" text="logout" class="outline"/>
</div>
</div>
<throbber-overlay v-if="loading" :text="loading" class="throbber"/>
<overlay v-if="loading"><throbber :text="loading"/></overlay>
</div>
</template>
<script>
import textbtn from '@/components/textbtn';
import textbtn from '@/components/layout/textbtn';
import {matrix} from '@/main.js';
import ThrobberOverlay from '@/components/throbberOverlay';
import {isValidUserId} from '@/lib/matrixUtils';
import {DataStore} from '@/lib/DataStore';
import Overlay from '@/components/layout/overlay';
import Throbber from '@/components/layout/throbber';
const store = new DataStore();
export default {
name: 'login.vue',
components: {
ThrobberOverlay,
Throbber,
Overlay,
textbtn
},
methods: {
@ -113,12 +115,6 @@ input:focus{
height: min-content;
width: 100%;
}
.throbber{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@media (max-width: 35rem) {
input {

@ -1,37 +1,44 @@
<template>
<div v-if="matrix.loading">
<throbber-overlay text="loading"/>
</div>
<overlay v-if="matrix.loading"><throbber text="loading" class="center"/></overlay>
<div v-else>
<div id="roomList" class="roomList">
<h1 class="wideElement">[chat]</h1><h1 class="smallElement">[c]</h1>
<input v-model="search" class="input wideElement" type="text" maxlength="50" placeholder="search">
<p class="wideElement">rooms </p>
<p class="wideElement">- rooms -</p>
<room-list-element
v-for="room in Object.assign([], matrix.client.getRooms())
.sort(obj => obj.timeline[obj.timeline.length-1].event.origin_server_ts)
v-for="room in matrix.client.getRooms()
.filter(prop=>matchResults(prop.name, search)||prop.roomId===search)"
:key="room.roomId" @click.native="openChat(room)"
:room="room"
class="roomListElement"
/>
<div v-if="search">
<p class="wideElement">users </p><p class="smallElement"></p>
<p class="wideElement">- users -</p><p class="smallElement"></p>
<user-list-element
v-for="user in matrix.client.getUsers()
.filter(prop=>matchResults(prop.displayName, search)||matchResults(prop.userId, search))
.slice(0,10)"
:user="user" :key="user.userId"
@click.native="setQuestion(`create private chat with '${user.displayName}'?`,()=>createRoom({user}))"
@click.native="setQuestion({
title:'New Chat',
question:`Create private chat with '${user.displayName}'?`,
callback:()=>createRoom({users:[user], access: 'private'}).then(openChat)
})"
/>
<p class="wideElement">suggestions </p><p class="smallElement"></p>
<p class="wideElement">- suggestions -</p><p class="smallElement"></p>
<div class="wideElement">
<p v-if="isValidUserId(search)">create chat: {{search}} </p>
<p v-if="isValidUserId(search)" class="suggestion">create chat: {{search}} </p>
<p v-if="isValidRoomId(search)"
@click="setQuestion(`join room '${search}'?`, ()=>joinRoom(search))"
class="suggestion"
@click="setQuestion({
title:'Join room',
question:`Join '${search}'?`,
callback:()=>joinRoom(search)
})"
>join room: {{search}} </p>
<p v-if="search.match(/^[a-zA-Z0-9_.+-]+$/)"
@click="setQuestion(`create room '${search}'?`,()=>createRoom({name: search}))"
@click="setShowCreateRoom({name: search}, openChat)"
class="suggestion"
>create room: {{search}} </p>
</div>
</div>
@ -45,32 +52,40 @@
:open-chat-info="()=>showChatInfo=true"
/>
<div class="noRoomSelected" v-else>Please select a room to be displayed.</div>
<chatInformation v-if="showRoom && showChatInfo" :room="getCurrentRoom()" :close-chat-info="()=>showChatInfo=false"/>
<popup-question v-if="popup.question" :callback="popup.callback" :question="popup.question" class="center"/>
<overlay>
<chatInformation v-if="showRoom && showChatInfo" :room="getCurrentRoom()" :close-chat-info="()=>showChatInfo=false" class="center"/>
<new-room v-if="showCreateRoom.props" :callback="showCreateRoom.callback" :props="showCreateRoom.props" class="center"/>
<popup-question v-if="popup.question" :callback="popup.callback" :question="popup.question" :title="popup.title" class="center"/>
</overlay>
</div>
</template>
<script>
import chat from '@/views/chat.vue';
import chatInformation from '@/components/chatInformation';
import chat from '@/components/chat/chat.vue';
import chatInformation from '@/components/chat/chatInformation';
import {matrix} from '@/main';
import ThrobberOverlay from '@/components/throbberOverlay';
import {getMxcFromRoom} from '@/lib/getMxc';
import roomListElement from '@/components/roomListElement';
import roomListElement from '@/components/matrix/roomListElement';
import {getRoom, getUser} from '@/lib/matrixUtils';
import {isValidUserId, isValidRoomId} from '@/lib/matrixUtils';
import userListElement from '@/components/userListElement';
import PopupQuestion from '@/components/popupQuestion';
import userListElement from '@/components/matrix/userListElement';
import PopupQuestion from '@/components/layout/popupQuestion';
import newRoom from '@/components/matrix/newRoom';
import Overlay from '@/components/layout/overlay';
import Throbber from '@/components/layout/throbber';
import {createRoom} from '@/lib/matrixUtils';
export default {
name: 'rooms',
components:{
Throbber,
Overlay,
PopupQuestion,
userListElement,
ThrobberOverlay,
chat,
chatInformation,
roomListElement
roomListElement,
newRoom
},
methods:{
openChat(room){
@ -89,11 +104,12 @@ export default {
matchResults(prop, search){
return prop.toLowerCase().includes(search.toLowerCase().trim());
},
setQuestion(question, callback){
setQuestion({title, question, callback}){
this.popup = {
question,
title,
callback:(res)=>{
this.popup = false;
this.popup = {};
if (res) callback();
}
}
@ -103,18 +119,21 @@ export default {
this.openChat(getRoom(room.room_id));
});
},
async createRoom({name = '', user = undefined}){
return this.matrix.client.createRoom({name}).then(room => {
if (user) this.matrix.client.invite(room.room_id, user.userId);
this.openChat(getRoom(room.room_id));
return room;
});
setShowCreateRoom(props, callback=()=>{}){
this.createRoom = {
props,
callback:(res)=>{
this.createRoom = {};
if (res) callback(res);
}
}
},
getMxcFromRoom,
getRoom,
getUser,
isValidUserId,
isValidRoomId
isValidRoomId,
createRoom
},
data(){
return {
@ -122,10 +141,8 @@ export default {
showChatInfo: false,
showRoom: true,
search: '',
popup:{
question: '',
callback: ()=>{}
}
popup:{},
showCreateRoom:{}
}
},
mounted() {
@ -171,7 +188,10 @@ export default {
text-align: center;
}
input{
width: calc(100% - 5.2rem);
position: relative;
margin-left: auto;
margin-right: auto;
width: calc(100% - 4rem);
}
.wideElement{
display: block;
@ -180,11 +200,17 @@ input{
display: none;
}
.center{
position: fixed;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: 50;
}
.suggestion{
cursor: pointer;
text-decoration: underline;
}
.suggestion:hover{
background-color: #4444;
}
@media (max-width: 48rem) and (min-width: 30rem) {

Loading…
Cancel
Save