forked from nutomic/lemmy
parent
e7e5700b97
commit
641d66310e
11 changed files with 255 additions and 140 deletions
|
@ -10,24 +10,26 @@ declare const Sortable: any;
|
||||||
|
|
||||||
interface CommunitiesState {
|
interface CommunitiesState {
|
||||||
communities: Array<Community>;
|
communities: Array<Community>;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Communities extends Component<any, CommunitiesState> {
|
export class Communities extends Component<any, CommunitiesState> {
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: CommunitiesState = {
|
private emptyState: CommunitiesState = {
|
||||||
communities: []
|
communities: [],
|
||||||
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(msg) => this.parseMessage(msg),
|
(msg) => this.parseMessage(msg),
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
WebSocketService.Instance.listCommunities();
|
WebSocketService.Instance.listCommunities();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,40 +46,45 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<h4>Communities</h4>
|
{this.state.loading ?
|
||||||
<div class="table-responsive">
|
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<table id="community_table" class="table table-sm table-hover">
|
<div>
|
||||||
<thead class="pointer">
|
<h4>Communities</h4>
|
||||||
<tr>
|
<div class="table-responsive">
|
||||||
<th>Name</th>
|
<table id="community_table" class="table table-sm table-hover">
|
||||||
<th>Title</th>
|
<thead class="pointer">
|
||||||
<th>Category</th>
|
|
||||||
<th class="text-right">Subscribers</th>
|
|
||||||
<th class="text-right">Posts</th>
|
|
||||||
<th class="text-right">Comments</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.state.communities.map(community =>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td><Link to={`/community/${community.id}`}>{community.name}</Link></td>
|
<th>Name</th>
|
||||||
<td>{community.title}</td>
|
<th>Title</th>
|
||||||
<td>{community.category_name}</td>
|
<th>Category</th>
|
||||||
<td class="text-right">{community.number_of_subscribers}</td>
|
<th class="text-right">Subscribers</th>
|
||||||
<td class="text-right">{community.number_of_posts}</td>
|
<th class="text-right">Posts</th>
|
||||||
<td class="text-right">{community.number_of_comments}</td>
|
<th class="text-right">Comments</th>
|
||||||
<td class="text-right">
|
<th></th>
|
||||||
{community.subscribed
|
|
||||||
? <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button>
|
|
||||||
: <button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button>
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{this.state.communities.map(community =>
|
||||||
|
<tr>
|
||||||
|
<td><Link to={`/community/${community.id}`}>{community.name}</Link></td>
|
||||||
|
<td>{community.title}</td>
|
||||||
|
<td>{community.category_name}</td>
|
||||||
|
<td class="text-right">{community.number_of_subscribers}</td>
|
||||||
|
<td class="text-right">{community.number_of_posts}</td>
|
||||||
|
<td class="text-right">{community.number_of_comments}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{community.subscribed ?
|
||||||
|
<button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleUnsubscribe)}>Unsubscribe</button> :
|
||||||
|
<button class="btn btn-sm btn-secondary" onClick={linkEvent(community.id, this.handleSubscribe)}>Subscribe</button>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -109,6 +116,7 @@ export class Communities extends Component<any, CommunitiesState> {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let res: ListCommunitiesResponse = msg;
|
||||||
this.state.communities = res.communities;
|
this.state.communities = res.communities;
|
||||||
this.state.communities.sort((a, b) => b.number_of_subscribers - a.number_of_subscribers);
|
this.state.communities.sort((a, b) => b.number_of_subscribers - a.number_of_subscribers);
|
||||||
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.FollowCommunity) {
|
} else if (op == UserOperation.FollowCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let res: CommunityResponse = msg;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { CommunityForm as CommunityFormI, UserOperation, Category, ListCategoriesResponse, CommunityResponse } from '../interfaces';
|
import { CommunityForm as CommunityFormI, UserOperation, Category, ListCategoriesResponse, CommunityResponse } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
import { Community } from '../interfaces';
|
import { Community } from '../interfaces';
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ interface CommunityFormProps {
|
||||||
interface CommunityFormState {
|
interface CommunityFormState {
|
||||||
communityForm: CommunityFormI;
|
communityForm: CommunityFormI;
|
||||||
categories: Array<Category>;
|
categories: Array<Category>;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CommunityForm extends Component<CommunityFormProps, CommunityFormState> {
|
export class CommunityForm extends Component<CommunityFormProps, CommunityFormState> {
|
||||||
|
@ -28,7 +30,8 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
title: null,
|
title: null,
|
||||||
category_id: null
|
category_id: null
|
||||||
},
|
},
|
||||||
categories: []
|
categories: [],
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -58,6 +61,10 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
WebSocketService.Instance.listCategories();
|
WebSocketService.Instance.listCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
autosize(document.querySelectorAll('textarea'));
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
@ -81,7 +88,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-12 col-form-label">Sidebar</label>
|
<label class="col-12 col-form-label">Sidebar</label>
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={6} />
|
<textarea value={this.state.communityForm.description} onInput={linkEvent(this, this.handleCommunityDescriptionChange)} class="form-control" rows={3} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -96,8 +103,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<button type="submit" class="btn btn-secondary mr-2">{this.props.community ? 'Save' : 'Create'}</button>
|
<button type="submit" class="btn btn-secondary mr-2">
|
||||||
{this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
|
{this.state.loading ?
|
||||||
|
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> :
|
||||||
|
this.props.community ? 'Save' : 'Create'}</button>
|
||||||
|
{this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -106,11 +116,13 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
|
|
||||||
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
|
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
i.state.loading = true;
|
||||||
if (i.props.community) {
|
if (i.props.community) {
|
||||||
WebSocketService.Instance.editCommunity(i.state.communityForm);
|
WebSocketService.Instance.editCommunity(i.state.communityForm);
|
||||||
} else {
|
} else {
|
||||||
WebSocketService.Instance.createCommunity(i.state.communityForm);
|
WebSocketService.Instance.createCommunity(i.state.communityForm);
|
||||||
}
|
}
|
||||||
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommunityNameChange(i: CommunityForm, event: any) {
|
handleCommunityNameChange(i: CommunityForm, event: any) {
|
||||||
|
@ -142,6 +154,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(msg.error);
|
alert(msg.error);
|
||||||
|
this.state.loading = false;
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCategories){
|
} else if (op == UserOperation.ListCategories){
|
||||||
let res: ListCategoriesResponse = msg;
|
let res: ListCategoriesResponse = msg;
|
||||||
|
@ -150,9 +163,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateCommunity) {
|
} else if (op == UserOperation.CreateCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let res: CommunityResponse = msg;
|
||||||
|
this.state.loading = false;
|
||||||
this.props.onCreate(res.community.id);
|
this.props.onCreate(res.community.id);
|
||||||
} else if (op == UserOperation.EditCommunity) {
|
} else if (op == UserOperation.EditCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let res: CommunityResponse = msg;
|
||||||
|
this.state.loading = false;
|
||||||
this.props.onEdit(res.community);
|
this.props.onEdit(res.community);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ interface State {
|
||||||
community: CommunityI;
|
community: CommunityI;
|
||||||
communityId: number;
|
communityId: number;
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Community extends Component<any, State> {
|
export class Community extends Component<any, State> {
|
||||||
|
@ -31,7 +32,8 @@ export class Community extends Component<any, State> {
|
||||||
published: null
|
published: null
|
||||||
},
|
},
|
||||||
moderators: [],
|
moderators: [],
|
||||||
communityId: Number(this.props.match.params.id)
|
communityId: Number(this.props.match.params.id),
|
||||||
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -40,12 +42,12 @@ export class Community extends Component<any, State> {
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(msg) => this.parseMessage(msg),
|
(msg) => this.parseMessage(msg),
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
WebSocketService.Instance.getCommunity(this.state.communityId);
|
WebSocketService.Instance.getCommunity(this.state.communityId);
|
||||||
}
|
}
|
||||||
|
@ -57,6 +59,8 @@ export class Community extends Component<any, State> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
{this.state.loading ?
|
||||||
|
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-lg-9">
|
<div class="col-12 col-lg-9">
|
||||||
<h4>/f/{this.state.community.name}</h4>
|
<h4>/f/{this.state.community.name}</h4>
|
||||||
|
@ -66,6 +70,7 @@ export class Community extends Component<any, State> {
|
||||||
<Sidebar community={this.state.community} moderators={this.state.moderators} />
|
<Sidebar community={this.state.community} moderators={this.state.moderators} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,6 +86,7 @@ export class Community extends Component<any, State> {
|
||||||
let res: GetCommunityResponse = msg;
|
let res: GetCommunityResponse = msg;
|
||||||
this.state.community = res.community;
|
this.state.community = res.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = res.moderators;
|
||||||
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.EditCommunity) {
|
} else if (op == UserOperation.EditCommunity) {
|
||||||
let res: CommunityResponse = msg;
|
let res: CommunityResponse = msg;
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { msgOp } from '../utils';
|
||||||
interface State {
|
interface State {
|
||||||
loginForm: LoginForm;
|
loginForm: LoginForm;
|
||||||
registerForm: RegisterForm;
|
registerForm: RegisterForm;
|
||||||
|
loginLoading: boolean;
|
||||||
|
registerLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let emptyState: State = {
|
let emptyState: State = {
|
||||||
|
@ -19,7 +21,9 @@ let emptyState: State = {
|
||||||
username: undefined,
|
username: undefined,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
password_verify: undefined
|
password_verify: undefined
|
||||||
}
|
},
|
||||||
|
loginLoading: false,
|
||||||
|
registerLoading: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Login extends Component<any, State> {
|
export class Login extends Component<any, State> {
|
||||||
|
@ -31,12 +35,12 @@ export class Login extends Component<any, State> {
|
||||||
this.state = emptyState;
|
this.state = emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(msg) => this.parseMessage(msg),
|
(msg) => this.parseMessage(msg),
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log("complete")
|
() => console.log("complete")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -77,7 +81,8 @@ export class Login extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-secondary">Login</button>
|
<button type="submit" class="btn btn-secondary">{this.state.loginLoading ?
|
||||||
|
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Login'}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -115,7 +120,9 @@ export class Login extends Component<any, State> {
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-secondary">Sign Up</button>
|
<button type="submit" class="btn btn-secondary">{this.state.registerLoading ?
|
||||||
|
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> : 'Sign Up'}</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -124,6 +131,8 @@ export class Login extends Component<any, State> {
|
||||||
|
|
||||||
handleLoginSubmit(i: Login, event: any) {
|
handleLoginSubmit(i: Login, event: any) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
i.state.loginLoading = true;
|
||||||
|
i.setState(i.state);
|
||||||
WebSocketService.Instance.login(i.state.loginForm);
|
WebSocketService.Instance.login(i.state.loginForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +147,8 @@ export class Login extends Component<any, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRegisterSubmit(i: Login, event: any) {
|
handleRegisterSubmit(i: Login, event: any) {
|
||||||
|
i.state.registerLoading = true;
|
||||||
|
i.setState(i.state);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
WebSocketService.Instance.register(i.state.registerForm);
|
WebSocketService.Instance.register(i.state.registerForm);
|
||||||
}
|
}
|
||||||
|
@ -166,9 +177,14 @@ export class Login extends Component<any, State> {
|
||||||
let op: UserOperation = msgOp(msg);
|
let op: UserOperation = msgOp(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
alert(msg.error);
|
alert(msg.error);
|
||||||
|
this.state.loginLoading = false;
|
||||||
|
this.state.registerLoading = false;
|
||||||
|
this.setState(this.state);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
if (op == UserOperation.Register || op == UserOperation.Login) {
|
if (op == UserOperation.Register || op == UserOperation.Login) {
|
||||||
|
this.state.loginLoading = false;
|
||||||
|
this.state.registerLoading = false;
|
||||||
let res: LoginResponse = msg;
|
let res: LoginResponse = msg;
|
||||||
UserService.Instance.login(res);
|
UserService.Instance.login(res);
|
||||||
this.props.history.push('/');
|
this.props.history.push('/');
|
||||||
|
|
|
@ -9,13 +9,15 @@ import { msgOp } from '../utils';
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
subscribedCommunities: Array<CommunityUser>;
|
subscribedCommunities: Array<CommunityUser>;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Main extends Component<any, State> {
|
export class Main extends Component<any, State> {
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
private emptyState: State = {
|
private emptyState: State = {
|
||||||
subscribedCommunities: []
|
subscribedCommunities: [],
|
||||||
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -24,12 +26,12 @@ export class Main extends Component<any, State> {
|
||||||
this.state = this.emptyState;
|
this.state = this.emptyState;
|
||||||
|
|
||||||
this.subscription = WebSocketService.Instance.subject
|
this.subscription = WebSocketService.Instance.subject
|
||||||
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(msg) => this.parseMessage(msg),
|
(msg) => this.parseMessage(msg),
|
||||||
(err) => console.error(err),
|
(err) => console.error(err),
|
||||||
() => console.log('complete')
|
() => console.log('complete')
|
||||||
);
|
);
|
||||||
|
|
||||||
if (UserService.Instance.loggedIn) {
|
if (UserService.Instance.loggedIn) {
|
||||||
WebSocketService.Instance.getFollowedCommunities();
|
WebSocketService.Instance.getFollowedCommunities();
|
||||||
|
@ -51,13 +53,18 @@ export class Main extends Component<any, State> {
|
||||||
<h4>A Landing message</h4>
|
<h4>A Landing message</h4>
|
||||||
{UserService.Instance.loggedIn &&
|
{UserService.Instance.loggedIn &&
|
||||||
<div>
|
<div>
|
||||||
<hr />
|
{this.state.loading ?
|
||||||
<h4>Subscribed forums</h4>
|
<h4 class="mt-3"><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
<ul class="list-unstyled">
|
<div>
|
||||||
{this.state.subscribedCommunities.map(community =>
|
<hr />
|
||||||
<li><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
|
<h4>Subscribed forums</h4>
|
||||||
)}
|
<ul class="list-unstyled">
|
||||||
</ul>
|
{this.state.subscribedCommunities.map(community =>
|
||||||
|
<li><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,6 +83,7 @@ export class Main extends Component<any, State> {
|
||||||
} else if (op == UserOperation.GetFollowedCommunities) {
|
} else if (op == UserOperation.GetFollowedCommunities) {
|
||||||
let res: GetFollowedCommunitiesResponse = msg;
|
let res: GetFollowedCommunitiesResponse = msg;
|
||||||
this.state.subscribedCommunities = res.communities;
|
this.state.subscribedCommunities = res.communities;
|
||||||
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { retryWhen, delay, take } from 'rxjs/operators';
|
||||||
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
|
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
|
||||||
import { WebSocketService } from '../services';
|
import { WebSocketService } from '../services';
|
||||||
import { msgOp } from '../utils';
|
import { msgOp } from '../utils';
|
||||||
|
import * as autosize from 'autosize';
|
||||||
|
|
||||||
interface PostFormProps {
|
interface PostFormProps {
|
||||||
post?: Post; // If a post is given, that means this is an edit
|
post?: Post; // If a post is given, that means this is an edit
|
||||||
|
@ -15,6 +16,7 @@ interface PostFormProps {
|
||||||
interface PostFormState {
|
interface PostFormState {
|
||||||
postForm: PostFormI;
|
postForm: PostFormI;
|
||||||
communities: Array<Community>;
|
communities: Array<Community>;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostForm extends Component<PostFormProps, PostFormState> {
|
export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
|
@ -26,7 +28,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
auth: null,
|
auth: null,
|
||||||
community_id: null
|
community_id: null
|
||||||
},
|
},
|
||||||
communities: []
|
communities: [],
|
||||||
|
loading: false
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -56,6 +59,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
WebSocketService.Instance.listCommunities();
|
WebSocketService.Instance.listCommunities();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
autosize(document.querySelectorAll('textarea'));
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
@ -73,13 +80,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-2 col-form-label">Title</label>
|
<label class="col-sm-2 col-form-label">Title</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea value={this.state.postForm.name} onInput={linkEvent(this, this.handlePostNameChange)} class="form-control" required rows={3} />
|
<textarea value={this.state.postForm.name} onInput={linkEvent(this, this.handlePostNameChange)} class="form-control" required rows={2} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label class="col-sm-2 col-form-label">Body</label>
|
<label class="col-sm-2 col-form-label">Body</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={6} />
|
<textarea value={this.state.postForm.body} onInput={linkEvent(this, this.handlePostBodyChange)} class="form-control" rows={4} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
|
@ -94,7 +101,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<button type="submit" class="btn btn-secondary mr-2">{this.props.post ? 'Save' : 'Create'}</button>
|
<button type="submit" class="btn btn-secondary mr-2">
|
||||||
|
{this.state.loading ?
|
||||||
|
<svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg> :
|
||||||
|
this.props.post ? 'Save' : 'Create'}</button>
|
||||||
{this.props.post && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
|
{this.props.post && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -110,6 +120,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
} else {
|
} else {
|
||||||
WebSocketService.Instance.createPost(i.state.postForm);
|
WebSocketService.Instance.createPost(i.state.postForm);
|
||||||
}
|
}
|
||||||
|
i.state.loading = true;
|
||||||
|
i.setState(i.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePostUrlChange(i: PostForm, event: any) {
|
handlePostUrlChange(i: PostForm, event: any) {
|
||||||
|
@ -139,6 +151,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
parseMessage(msg: any) {
|
parseMessage(msg: any) {
|
||||||
let op: UserOperation = msgOp(msg);
|
let op: UserOperation = msgOp(msg);
|
||||||
if (msg.error) {
|
if (msg.error) {
|
||||||
|
this.state.loading = false;
|
||||||
return;
|
return;
|
||||||
} else if (op == UserOperation.ListCommunities) {
|
} else if (op == UserOperation.ListCommunities) {
|
||||||
let res: ListCommunitiesResponse = msg;
|
let res: ListCommunitiesResponse = msg;
|
||||||
|
@ -150,9 +163,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
|
||||||
}
|
}
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePost) {
|
} else if (op == UserOperation.CreatePost) {
|
||||||
|
this.state.loading = false;
|
||||||
let res: PostResponse = msg;
|
let res: PostResponse = msg;
|
||||||
this.props.onCreate(res.post.id);
|
this.props.onCreate(res.post.id);
|
||||||
} else if (op == UserOperation.EditPost) {
|
} else if (op == UserOperation.EditPost) {
|
||||||
|
this.state.loading = false;
|
||||||
let res: PostResponse = msg;
|
let res: PostResponse = msg;
|
||||||
this.props.onEdit(res.post);
|
this.props.onEdit(res.post);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ interface PostListingsState {
|
||||||
posts: Array<Post>;
|
posts: Array<Post>;
|
||||||
sortType: SortType;
|
sortType: SortType;
|
||||||
type_: ListingType;
|
type_: ListingType;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostListings extends Component<PostListingsProps, PostListingsState> {
|
export class PostListings extends Component<PostListingsProps, PostListingsState> {
|
||||||
|
@ -44,7 +45,8 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
? ListingType.Community
|
? ListingType.Community
|
||||||
: UserService.Instance.loggedIn
|
: UserService.Instance.loggedIn
|
||||||
? ListingType.Subscribed
|
? ListingType.Subscribed
|
||||||
: ListingType.All
|
: ListingType.All,
|
||||||
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -77,11 +79,16 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>{this.selects()}</div>
|
{this.state.loading ?
|
||||||
{this.state.posts.length > 0
|
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
? this.state.posts.map(post =>
|
<div>
|
||||||
<PostListing post={post} showCommunity={!this.props.communityId}/>)
|
<div>{this.selects()}</div>
|
||||||
: <div>No Listings. Subscribe to some <Link to="/communities">forums</Link>.</div>
|
{this.state.posts.length > 0
|
||||||
|
? this.state.posts.map(post =>
|
||||||
|
<PostListing post={post} showCommunity={!this.props.communityId}/>)
|
||||||
|
: <div>No Listings. Subscribe to some <Link to="/communities">forums</Link>.</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -103,11 +110,11 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
</select>
|
</select>
|
||||||
{!this.props.communityId &&
|
{!this.props.communityId &&
|
||||||
UserService.Instance.loggedIn &&
|
UserService.Instance.loggedIn &&
|
||||||
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
|
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
|
||||||
<option disabled>Type</option>
|
<option disabled>Type</option>
|
||||||
<option value={ListingType.All}>All</option>
|
<option value={ListingType.All}>All</option>
|
||||||
<option value={ListingType.Subscribed}>Subscribed</option>
|
<option value={ListingType.Subscribed}>Subscribed</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -149,6 +156,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
|
||||||
} else if (op == UserOperation.GetPosts) {
|
} else if (op == UserOperation.GetPosts) {
|
||||||
let res: GetPostsResponse = msg;
|
let res: GetPostsResponse = msg;
|
||||||
this.state.posts = res.posts;
|
this.state.posts = res.posts;
|
||||||
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreatePostLike) {
|
} else if (op == UserOperation.CreatePostLike) {
|
||||||
let res: CreatePostLikeResponse = msg;
|
let res: CreatePostLikeResponse = msg;
|
||||||
|
|
|
@ -19,6 +19,7 @@ interface PostState {
|
||||||
moderators: Array<CommunityUser>;
|
moderators: Array<CommunityUser>;
|
||||||
scrolled?: boolean;
|
scrolled?: boolean;
|
||||||
scrolled_comment_id?: number;
|
scrolled_comment_id?: number;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Post extends Component<any, PostState> {
|
export class Post extends Component<any, PostState> {
|
||||||
|
@ -30,7 +31,8 @@ export class Post extends Component<any, PostState> {
|
||||||
commentSort: CommentSortType.Hot,
|
commentSort: CommentSortType.Hot,
|
||||||
community: null,
|
community: null,
|
||||||
moderators: [],
|
moderators: [],
|
||||||
scrolled: false
|
scrolled: false,
|
||||||
|
loading: true
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -74,8 +76,9 @@ export class Post extends Component<any, PostState> {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{this.state.post &&
|
{this.state.loading ?
|
||||||
<div class="row">
|
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
|
||||||
|
<div class="row">
|
||||||
<div class="col-12 col-sm-8 col-lg-7 mb-3">
|
<div class="col-12 col-sm-8 col-lg-7 mb-3">
|
||||||
<PostListing post={this.state.post} showBody showCommunity editable />
|
<PostListing post={this.state.post} showBody showCommunity editable />
|
||||||
<div className="mb-2" />
|
<div className="mb-2" />
|
||||||
|
@ -202,6 +205,7 @@ export class Post extends Component<any, PostState> {
|
||||||
this.state.comments = res.comments;
|
this.state.comments = res.comments;
|
||||||
this.state.community = res.community;
|
this.state.community = res.community;
|
||||||
this.state.moderators = res.moderators;
|
this.state.moderators = res.moderators;
|
||||||
|
this.state.loading = false;
|
||||||
this.setState(this.state);
|
this.setState(this.state);
|
||||||
} else if (op == UserOperation.CreateComment) {
|
} else if (op == UserOperation.CreateComment) {
|
||||||
let res: CommentResponse = msg;
|
let res: CommentResponse = msg;
|
||||||
|
|
80
ui/src/components/symbols.tsx
Normal file
80
ui/src/components/symbols.tsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { Component } from 'inferno';
|
||||||
|
|
||||||
|
export class Symbols extends Component<any, any> {
|
||||||
|
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<symbol id="icon-mouse" version="1.1" x="0px" y="0px"
|
||||||
|
viewBox="0 0 512 512">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path d="M499.059,323.505l-7.52-32.532l-70.047,16.19c1.513-11.983,2.297-24.042,2.297-36.037c0-18.334-1.801-35.785-5.316-52.19
|
||||||
|
c29.365-12.101,55.143-28.885,69.372-45.529c17.524-20.498,25.985-46.568,23.822-73.406
|
||||||
|
c-2.163-26.862-14.706-51.268-35.316-68.724C433.879-4.694,369.917,0.439,333.774,42.718
|
||||||
|
c-9.546,11.168-18.318,27.381-25.379,46.649c-16.512-5.419-34.132-8.243-52.395-8.243s-35.885,2.824-52.395,8.243
|
||||||
|
c-7.06-19.267-15.832-35.481-25.379-46.649C142.082,0.44,78.123-4.695,35.648,31.277C15.038,48.733,2.494,73.141,0.332,100.001
|
||||||
|
c-2.161,26.838,6.297,52.907,23.822,73.406c14.229,16.644,40.006,33.427,69.372,45.529c-3.515,16.405-5.316,33.856-5.316,52.189
|
||||||
|
c0,11.995,0.785,24.053,2.297,36.037l-70.047-16.19l-7.52,32.532l84.337,19.492c4.349,17.217,10.201,33.953,17.421,49.752
|
||||||
|
L12.941,416.27l7.52,32.532l110.634-25.57c1.38,2.197,2.779,4.373,4.218,6.509c32.548,48.323,75.409,74.934,120.687,74.934
|
||||||
|
c45.278,0,88.138-26.612,120.687-74.934c1.439-2.136,2.839-4.313,4.218-6.509l110.634,25.57l7.52-32.532l-101.758-23.519
|
||||||
|
c7.221-15.799,13.072-32.535,17.421-49.752L499.059,323.505z M183.578,220.372c0-11.41,9.189-20.65,20.482-20.65
|
||||||
|
c11.306,0,20.494,9.24,20.494,20.65c0,11.408-9.188,20.656-20.494,20.656C192.768,241.028,183.578,231.78,183.578,220.372z
|
||||||
|
M256,413.29c-29.895,0-54.216-19.471-54.216-43.403c0-23.932,24.322-43.403,54.216-43.403s54.216,19.471,54.216,43.403
|
||||||
|
C310.216,393.819,285.895,413.29,256,413.29z M307.785,241.183c-11.402,0-20.65-9.317-20.65-20.81
|
||||||
|
c0-11.494,9.248-20.81,20.65-20.81c11.387,0,20.635,9.317,20.635,20.81C328.422,231.866,319.173,241.183,307.785,241.183z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
</g>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-search" viewBox="0 0 32 32">
|
||||||
|
<title>search</title>
|
||||||
|
<path d="M31.008 27.231l-7.58-6.447c-0.784-0.705-1.622-1.029-2.299-0.998 1.789-2.096 2.87-4.815 2.87-7.787 0-6.627-5.373-12-12-12s-12 5.373-12 12 5.373 12 12 12c2.972 0 5.691-1.081 7.787-2.87-0.031 0.677 0.293 1.515 0.998 2.299l6.447 7.58c1.104 1.226 2.907 1.33 4.007 0.23s0.997-2.903-0.23-4.007zM12 20c-4.418 0-8-3.582-8-8s3.582-8 8-8 8 3.582 8 8-3.582 8-8 8z"></path>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-github" viewBox="0 0 32 32">
|
||||||
|
<title>github</title>
|
||||||
|
<path d="M16 0.395c-8.836 0-16 7.163-16 16 0 7.069 4.585 13.067 10.942 15.182 0.8 0.148 1.094-0.347 1.094-0.77 0-0.381-0.015-1.642-0.022-2.979-4.452 0.968-5.391-1.888-5.391-1.888-0.728-1.849-1.776-2.341-1.776-2.341-1.452-0.993 0.11-0.973 0.11-0.973 1.606 0.113 2.452 1.649 2.452 1.649 1.427 2.446 3.743 1.739 4.656 1.33 0.143-1.034 0.558-1.74 1.016-2.14-3.554-0.404-7.29-1.777-7.29-7.907 0-1.747 0.625-3.174 1.649-4.295-0.166-0.403-0.714-2.030 0.155-4.234 0 0 1.344-0.43 4.401 1.64 1.276-0.355 2.645-0.532 4.005-0.539 1.359 0.006 2.729 0.184 4.008 0.539 3.054-2.070 4.395-1.64 4.395-1.64 0.871 2.204 0.323 3.831 0.157 4.234 1.026 1.12 1.647 2.548 1.647 4.295 0 6.145-3.743 7.498-7.306 7.895 0.574 0.497 1.085 1.47 1.085 2.963 0 2.141-0.019 3.864-0.019 4.391 0 0.426 0.288 0.925 1.099 0.768 6.354-2.118 10.933-8.113 10.933-15.18 0-8.837-7.164-16-16-16z"></path>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="icon-spinner" viewBox="0 0 32 32">
|
||||||
|
<title>spinner</title>
|
||||||
|
<path d="M16 32c-4.274 0-8.292-1.664-11.314-4.686s-4.686-7.040-4.686-11.314c0-3.026 0.849-5.973 2.456-8.522 1.563-2.478 3.771-4.48 6.386-5.791l1.344 2.682c-2.126 1.065-3.922 2.693-5.192 4.708-1.305 2.069-1.994 4.462-1.994 6.922 0 7.168 5.832 13 13 13s13-5.832 13-13c0-2.459-0.69-4.853-1.994-6.922-1.271-2.015-3.066-3.643-5.192-4.708l1.344-2.682c2.615 1.31 4.824 3.313 6.386 5.791 1.607 2.549 2.456 5.495 2.456 8.522 0 4.274-1.664 8.292-4.686 11.314s-7.040 4.686-11.314 4.686z"></path>
|
||||||
|
</symbol>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import { Post } from './components/post';
|
||||||
import { Community } from './components/community';
|
import { Community } from './components/community';
|
||||||
import { Communities } from './components/communities';
|
import { Communities } from './components/communities';
|
||||||
import { User } from './components/user';
|
import { User } from './components/user';
|
||||||
|
import { Symbols } from './components/symbols';
|
||||||
|
|
||||||
import './main.css';
|
import './main.css';
|
||||||
|
|
||||||
|
@ -42,68 +43,12 @@ class Index extends Component<any, any> {
|
||||||
<Route path={`/user/:id/:heading`} component={User} />
|
<Route path={`/user/:id/:heading`} component={User} />
|
||||||
<Route path={`/user/:id`} component={User} />
|
<Route path={`/user/:id`} component={User} />
|
||||||
</Switch>
|
</Switch>
|
||||||
{this.symbols()}
|
<Symbols />
|
||||||
</div>
|
</div>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
symbols() {
|
|
||||||
return(
|
|
||||||
<symbol id="icon-mouse" version="1.1" x="0px" y="0px"
|
|
||||||
viewBox="0 0 512 512">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path d="M499.059,323.505l-7.52-32.532l-70.047,16.19c1.513-11.983,2.297-24.042,2.297-36.037c0-18.334-1.801-35.785-5.316-52.19
|
|
||||||
c29.365-12.101,55.143-28.885,69.372-45.529c17.524-20.498,25.985-46.568,23.822-73.406
|
|
||||||
c-2.163-26.862-14.706-51.268-35.316-68.724C433.879-4.694,369.917,0.439,333.774,42.718
|
|
||||||
c-9.546,11.168-18.318,27.381-25.379,46.649c-16.512-5.419-34.132-8.243-52.395-8.243s-35.885,2.824-52.395,8.243
|
|
||||||
c-7.06-19.267-15.832-35.481-25.379-46.649C142.082,0.44,78.123-4.695,35.648,31.277C15.038,48.733,2.494,73.141,0.332,100.001
|
|
||||||
c-2.161,26.838,6.297,52.907,23.822,73.406c14.229,16.644,40.006,33.427,69.372,45.529c-3.515,16.405-5.316,33.856-5.316,52.189
|
|
||||||
c0,11.995,0.785,24.053,2.297,36.037l-70.047-16.19l-7.52,32.532l84.337,19.492c4.349,17.217,10.201,33.953,17.421,49.752
|
|
||||||
L12.941,416.27l7.52,32.532l110.634-25.57c1.38,2.197,2.779,4.373,4.218,6.509c32.548,48.323,75.409,74.934,120.687,74.934
|
|
||||||
c45.278,0,88.138-26.612,120.687-74.934c1.439-2.136,2.839-4.313,4.218-6.509l110.634,25.57l7.52-32.532l-101.758-23.519
|
|
||||||
c7.221-15.799,13.072-32.535,17.421-49.752L499.059,323.505z M183.578,220.372c0-11.41,9.189-20.65,20.482-20.65
|
|
||||||
c11.306,0,20.494,9.24,20.494,20.65c0,11.408-9.188,20.656-20.494,20.656C192.768,241.028,183.578,231.78,183.578,220.372z
|
|
||||||
M256,413.29c-29.895,0-54.216-19.471-54.216-43.403c0-23.932,24.322-43.403,54.216-43.403s54.216,19.471,54.216,43.403
|
|
||||||
C310.216,393.819,285.895,413.29,256,413.29z M307.785,241.183c-11.402,0-20.65-9.317-20.65-20.81
|
|
||||||
c0-11.494,9.248-20.81,20.65-20.81c11.387,0,20.635,9.317,20.635,20.81C328.422,231.866,319.173,241.183,307.785,241.183z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
<g>
|
|
||||||
</g>
|
|
||||||
</symbol>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(<Index />, container);
|
render(<Index />, container);
|
||||||
|
|
|
@ -54,5 +54,15 @@ body {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-bottom: 6px;
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation: spins 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spins {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(359deg); }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue