Add a simple edit dialog for communities
This commit is contained in:
parent
133f981fa8
commit
fdbaddbace
6 changed files with 255 additions and 1 deletions
|
@ -68,6 +68,7 @@
|
||||||
@import "./views/dialogs/_CreateRoomDialog.scss";
|
@import "./views/dialogs/_CreateRoomDialog.scss";
|
||||||
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
@import "./views/dialogs/_DeactivateAccountDialog.scss";
|
||||||
@import "./views/dialogs/_DevtoolsDialog.scss";
|
@import "./views/dialogs/_DevtoolsDialog.scss";
|
||||||
|
@import "./views/dialogs/_EditCommunityPrototypeDialog.scss";
|
||||||
@import "./views/dialogs/_GroupAddressPicker.scss";
|
@import "./views/dialogs/_GroupAddressPicker.scss";
|
||||||
@import "./views/dialogs/_IncomingSasDialog.scss";
|
@import "./views/dialogs/_IncomingSasDialog.scss";
|
||||||
@import "./views/dialogs/_InviteDialog.scss";
|
@import "./views/dialogs/_InviteDialog.scss";
|
||||||
|
|
77
res/css/views/dialogs/_EditCommunityPrototypeDialog.scss
Normal file
77
res/css/views/dialogs/_EditCommunityPrototypeDialog.scss
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// XXX: many of these styles are shared with the create dialog
|
||||||
|
.mx_EditCommunityPrototypeDialog {
|
||||||
|
&.mx_Dialog_fixedWidth {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Dialog_content {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.mx_AccessibleButton.mx_AccessibleButton_kind_primary {
|
||||||
|
display: block;
|
||||||
|
height: 32px;
|
||||||
|
font-size: $font-16px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_rowAvatar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_avatarContainer {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_avatar,
|
||||||
|
.mx_EditCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_placeholderAvatar {
|
||||||
|
background-color: #368bd6; // hardcoded for both themes
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #fff; // hardcoded because the background is
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-size: 96px;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
mask-position: center;
|
||||||
|
content: '';
|
||||||
|
vertical-align: middle;
|
||||||
|
mask-image: url('$(res)/img/element-icons/add-photo.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_EditCommunityPrototypeDialog_tip {
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
& > b, & > span {
|
||||||
|
display: block;
|
||||||
|
color: $muted-fg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,7 @@ import { showCommunityInviteDialog } from "../../RoomInvite";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
import { RightPanelPhases } from "../../stores/RightPanelStorePhases";
|
||||||
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
import ErrorDialog from "../views/dialogs/ErrorDialog";
|
||||||
|
import EditCommunityPrototypeDialog from "../views/dialogs/EditCommunityPrototypeDialog";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
|
@ -207,7 +208,10 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
console.log("TODO@onCommunitySettingsClick");
|
Modal.createTrackedDialog('Edit Community', '', EditCommunityPrototypeDialog, {
|
||||||
|
communityId: CommunityPrototypeStore.instance.getSelectedCommunityId(),
|
||||||
|
});
|
||||||
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCommunityMembersClick = (ev: ButtonEvent) => {
|
private onCommunityMembersClick = (ev: ButtonEvent) => {
|
||||||
|
|
166
src/components/views/dialogs/EditCommunityPrototypeDialog.tsx
Normal file
166
src/components/views/dialogs/EditCommunityPrototypeDialog.tsx
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { ChangeEvent } from 'react';
|
||||||
|
import BaseDialog from "./BaseDialog";
|
||||||
|
import { _t } from "../../../languageHandler";
|
||||||
|
import { IDialogProps } from "./IDialogProps";
|
||||||
|
import Field from "../elements/Field";
|
||||||
|
import AccessibleButton from "../elements/AccessibleButton";
|
||||||
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
|
import { CommunityPrototypeStore } from "../../../stores/CommunityPrototypeStore";
|
||||||
|
import FlairStore from "../../../stores/FlairStore";
|
||||||
|
|
||||||
|
interface IProps extends IDialogProps {
|
||||||
|
communityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
name: string;
|
||||||
|
error: string;
|
||||||
|
busy: boolean;
|
||||||
|
currentAvatarUrl: string;
|
||||||
|
avatarFile: File;
|
||||||
|
avatarPreview: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: This is a lot of duplication from the create dialog, just in a different shape
|
||||||
|
export default class EditCommunityPrototypeDialog extends React.PureComponent<IProps, IState> {
|
||||||
|
private avatarUploadRef: React.RefObject<HTMLInputElement> = React.createRef();
|
||||||
|
|
||||||
|
constructor(props: IProps) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const profile = CommunityPrototypeStore.instance.getCommunityProfile(props.communityId);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: profile?.name || "",
|
||||||
|
error: null,
|
||||||
|
busy: false,
|
||||||
|
avatarFile: null,
|
||||||
|
avatarPreview: null,
|
||||||
|
currentAvatarUrl: profile?.avatarUrl,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private onNameChange = (ev: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({name: ev.target.value});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSubmit = async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
|
||||||
|
if (this.state.busy) return;
|
||||||
|
|
||||||
|
// We'll create the community now to see if it's taken, leaving it active in
|
||||||
|
// the background for the user to look at while they invite people.
|
||||||
|
this.setState({busy: true});
|
||||||
|
try {
|
||||||
|
let avatarUrl = this.state.currentAvatarUrl || ""; // must be a string for synapse to accept it
|
||||||
|
if (this.state.avatarFile) {
|
||||||
|
avatarUrl = await MatrixClientPeg.get().uploadContent(this.state.avatarFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
await MatrixClientPeg.get().setGroupProfile(this.props.communityId, {
|
||||||
|
name: this.state.name,
|
||||||
|
avatar_url: avatarUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ask the flair store to update the profile too
|
||||||
|
await FlairStore.refreshGroupProfile(MatrixClientPeg.get(), this.props.communityId);
|
||||||
|
|
||||||
|
// we did it, so close the dialog
|
||||||
|
this.props.onFinished(true);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
this.setState({
|
||||||
|
busy: false,
|
||||||
|
error: _t("There was an error updating your community. The server is unable to process your request."),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onAvatarChanged = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.target.files || !e.target.files.length) {
|
||||||
|
this.setState({avatarFile: null});
|
||||||
|
} else {
|
||||||
|
this.setState({busy: true});
|
||||||
|
const file = e.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (ev: ProgressEvent<FileReader>) => {
|
||||||
|
this.setState({avatarFile: file, busy: false, avatarPreview: ev.target.result as string});
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private onChangeAvatar = () => {
|
||||||
|
if (this.avatarUploadRef.current) this.avatarUploadRef.current.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
let preview = <img src={this.state.avatarPreview} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
||||||
|
if (!this.state.avatarPreview) {
|
||||||
|
if (this.state.currentAvatarUrl) {
|
||||||
|
const url = MatrixClientPeg.get().mxcUrlToHttp(this.state.currentAvatarUrl);
|
||||||
|
preview = <img src={url} className="mx_EditCommunityPrototypeDialog_avatar" />;
|
||||||
|
} else {
|
||||||
|
preview = <div className="mx_EditCommunityPrototypeDialog_placeholderAvatar" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseDialog
|
||||||
|
className="mx_EditCommunityPrototypeDialog"
|
||||||
|
onFinished={this.props.onFinished}
|
||||||
|
title={_t("Update community")}
|
||||||
|
>
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
<div className="mx_Dialog_content">
|
||||||
|
<div className="mx_EditCommunityPrototypeDialog_rowName">
|
||||||
|
<Field
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={this.onNameChange}
|
||||||
|
placeholder={_t("Enter name")}
|
||||||
|
label={_t("Enter name")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx_EditCommunityPrototypeDialog_rowAvatar">
|
||||||
|
<input
|
||||||
|
type="file" style={{display: "none"}}
|
||||||
|
ref={this.avatarUploadRef} accept="image/*"
|
||||||
|
onChange={this.onAvatarChanged}
|
||||||
|
/>
|
||||||
|
<AccessibleButton onClick={this.onChangeAvatar} className="mx_EditCommunityPrototypeDialog_avatarContainer">
|
||||||
|
{preview}
|
||||||
|
</AccessibleButton>
|
||||||
|
<div className="mx_EditCommunityPrototypeDialog_tip">
|
||||||
|
<b>{_t("Add image (optional)")}</b>
|
||||||
|
<span>
|
||||||
|
{_t("An image will help people identify your community.")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AccessibleButton kind="primary" onClick={this.onSubmit} disabled={this.state.busy}>
|
||||||
|
{_t("Save")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</BaseDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1687,6 +1687,8 @@
|
||||||
"Verification Requests": "Verification Requests",
|
"Verification Requests": "Verification Requests",
|
||||||
"Toolbox": "Toolbox",
|
"Toolbox": "Toolbox",
|
||||||
"Developer Tools": "Developer Tools",
|
"Developer Tools": "Developer Tools",
|
||||||
|
"There was an error updating your community. The server is unable to process your request.": "There was an error updating your community. The server is unable to process your request.",
|
||||||
|
"Update community": "Update community",
|
||||||
"An error has occurred.": "An error has occurred.",
|
"An error has occurred.": "An error has occurred.",
|
||||||
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
|
"Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.": "Verify this user to mark them as trusted. Trusting users gives you extra peace of mind when using end-to-end encrypted messages.",
|
||||||
"Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
|
"Verifying this user will mark their session as trusted, and also mark your session as trusted to them.": "Verifying this user will mark their session as trusted, and also mark your session as trusted to them.",
|
||||||
|
|
|
@ -71,6 +71,10 @@ export class CommunityPrototypeStore extends AsyncStoreWithClient<IState> {
|
||||||
return profile?.name || communityId;
|
return profile?.name || communityId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getCommunityProfile(communityId: string): { name?: string, avatarUrl?: string } {
|
||||||
|
return FlairStore.getGroupProfileCachedFast(this.matrixClient, communityId);
|
||||||
|
}
|
||||||
|
|
||||||
public getGeneralChat(communityId: string): Room {
|
public getGeneralChat(communityId: string): Room {
|
||||||
const rooms = GroupStore.getGroupRooms(communityId)
|
const rooms = GroupStore.getGroupRooms(communityId)
|
||||||
.map(r => MatrixClientPeg.get().getRoom(r.roomId))
|
.map(r => MatrixClientPeg.get().getRoom(r.roomId))
|
||||||
|
|
Loading…
Reference in a new issue