Loading indicators.

- Fixes #10
- Adding SVG symbols component
This commit is contained in:
Dessalines 2019-04-08 14:46:09 -07:00
parent 7194289f14
commit 70ff638103
11 changed files with 255 additions and 140 deletions

View File

@ -10,24 +10,26 @@ declare const Sortable: any;
interface CommunitiesState {
communities: Array<Community>;
loading: boolean;
}
export class Communities extends Component<any, CommunitiesState> {
private subscription: Subscription;
private emptyState: CommunitiesState = {
communities: []
communities: [],
loading: true
}
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
);
WebSocketService.Instance.listCommunities();
}
@ -44,40 +46,45 @@ export class Communities extends Component<any, CommunitiesState> {
render() {
return (
<div class="container-fluid">
<h4>Communities</h4>
<div class="table-responsive">
<table id="community_table" class="table table-sm table-hover">
<thead class="pointer">
<tr>
<th>Name</th>
<th>Title</th>
<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 =>
{this.state.loading ?
<h4 class=""><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
<h4>Communities</h4>
<div class="table-responsive">
<table id="community_table" class="table table-sm table-hover">
<thead class="pointer">
<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>
<th>Name</th>
<th>Title</th>
<th>Category</th>
<th class="text-right">Subscribers</th>
<th class="text-right">Posts</th>
<th class="text-right">Comments</th>
<th></th>
</tr>
)}
</tbody>
</table>
</thead>
<tbody>
{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>
);
}
@ -109,6 +116,7 @@ export class Communities extends Component<any, CommunitiesState> {
let res: ListCommunitiesResponse = msg;
this.state.communities = res.communities;
this.state.communities.sort((a, b) => b.number_of_subscribers - a.number_of_subscribers);
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.FollowCommunity) {
let res: CommunityResponse = msg;

View File

@ -4,6 +4,7 @@ import { retryWhen, delay, take } from 'rxjs/operators';
import { CommunityForm as CommunityFormI, UserOperation, Category, ListCategoriesResponse, CommunityResponse } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp } from '../utils';
import * as autosize from 'autosize';
import { Community } from '../interfaces';
@ -17,6 +18,7 @@ interface CommunityFormProps {
interface CommunityFormState {
communityForm: CommunityFormI;
categories: Array<Category>;
loading: boolean;
}
export class CommunityForm extends Component<CommunityFormProps, CommunityFormState> {
@ -28,7 +30,8 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
title: null,
category_id: null
},
categories: []
categories: [],
loading: false
}
constructor(props: any, context: any) {
@ -58,6 +61,10 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
WebSocketService.Instance.listCategories();
}
componentDidMount() {
autosize(document.querySelectorAll('textarea'));
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
@ -81,7 +88,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
<div class="form-group row">
<label class="col-12 col-form-label">Sidebar</label>
<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 class="form-group row">
@ -96,8 +103,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
</div>
<div class="form-group row">
<div class="col-12">
<button type="submit" class="btn btn-secondary mr-2">{this.props.community ? 'Save' : 'Create'}</button>
{this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</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.community ? 'Save' : 'Create'}</button>
{this.props.community && <button type="button" class="btn btn-secondary" onClick={linkEvent(this, this.handleCancel)}>Cancel</button>}
</div>
</div>
</form>
@ -106,11 +116,13 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
handleCreateCommunitySubmit(i: CommunityForm, event: any) {
event.preventDefault();
i.state.loading = true;
if (i.props.community) {
WebSocketService.Instance.editCommunity(i.state.communityForm);
} else {
WebSocketService.Instance.createCommunity(i.state.communityForm);
}
i.setState(i.state);
}
handleCommunityNameChange(i: CommunityForm, event: any) {
@ -142,6 +154,7 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
console.log(msg);
if (msg.error) {
alert(msg.error);
this.state.loading = false;
return;
} else if (op == UserOperation.ListCategories){
let res: ListCategoriesResponse = msg;
@ -150,9 +163,11 @@ export class CommunityForm extends Component<CommunityFormProps, CommunityFormSt
this.setState(this.state);
} else if (op == UserOperation.CreateCommunity) {
let res: CommunityResponse = msg;
this.state.loading = false;
this.props.onCreate(res.community.id);
} else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg;
this.state.loading = false;
this.props.onEdit(res.community);
}
}

View File

@ -11,6 +11,7 @@ interface State {
community: CommunityI;
communityId: number;
moderators: Array<CommunityUser>;
loading: boolean;
}
export class Community extends Component<any, State> {
@ -31,7 +32,8 @@ export class Community extends Component<any, State> {
published: null
},
moderators: [],
communityId: Number(this.props.match.params.id)
communityId: Number(this.props.match.params.id),
loading: true
}
constructor(props: any, context: any) {
@ -40,12 +42,12 @@ export class Community extends Component<any, State> {
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
);
WebSocketService.Instance.getCommunity(this.state.communityId);
}
@ -57,6 +59,8 @@ export class Community extends Component<any, State> {
render() {
return (
<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="col-12 col-lg-9">
<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} />
</div>
</div>
}
</div>
)
}
@ -81,6 +86,7 @@ export class Community extends Component<any, State> {
let res: GetCommunityResponse = msg;
this.state.community = res.community;
this.state.moderators = res.moderators;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.EditCommunity) {
let res: CommunityResponse = msg;

View File

@ -8,6 +8,8 @@ import { msgOp } from '../utils';
interface State {
loginForm: LoginForm;
registerForm: RegisterForm;
loginLoading: boolean;
registerLoading: boolean;
}
let emptyState: State = {
@ -19,7 +21,9 @@ let emptyState: State = {
username: undefined,
password: undefined,
password_verify: undefined
}
},
loginLoading: false,
registerLoading: false
}
export class Login extends Component<any, State> {
@ -31,12 +35,12 @@ export class Login extends Component<any, State> {
this.state = emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log("complete")
);
);
}
componentWillUnmount() {
@ -77,7 +81,8 @@ export class Login extends Component<any, State> {
</div>
<div class="form-group row">
<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>
</form>
@ -115,7 +120,9 @@ export class Login extends Component<any, State> {
</div>
<div class="form-group row">
<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>
</form>
@ -124,6 +131,8 @@ export class Login extends Component<any, State> {
handleLoginSubmit(i: Login, event: any) {
event.preventDefault();
i.state.loginLoading = true;
i.setState(i.state);
WebSocketService.Instance.login(i.state.loginForm);
}
@ -138,6 +147,8 @@ export class Login extends Component<any, State> {
}
handleRegisterSubmit(i: Login, event: any) {
i.state.registerLoading = true;
i.setState(i.state);
event.preventDefault();
WebSocketService.Instance.register(i.state.registerForm);
}
@ -166,9 +177,14 @@ export class Login extends Component<any, State> {
let op: UserOperation = msgOp(msg);
if (msg.error) {
alert(msg.error);
this.state.loginLoading = false;
this.state.registerLoading = false;
this.setState(this.state);
return;
} else {
if (op == UserOperation.Register || op == UserOperation.Login) {
this.state.loginLoading = false;
this.state.registerLoading = false;
let res: LoginResponse = msg;
UserService.Instance.login(res);
this.props.history.push('/');

View File

@ -9,13 +9,15 @@ import { msgOp } from '../utils';
interface State {
subscribedCommunities: Array<CommunityUser>;
loading: boolean;
}
export class Main extends Component<any, State> {
private subscription: Subscription;
private emptyState: State = {
subscribedCommunities: []
subscribedCommunities: [],
loading: true
}
constructor(props: any, context: any) {
@ -24,12 +26,12 @@ export class Main extends Component<any, State> {
this.state = this.emptyState;
this.subscription = WebSocketService.Instance.subject
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
.pipe(retryWhen(errors => errors.pipe(delay(3000), take(10))))
.subscribe(
(msg) => this.parseMessage(msg),
(err) => console.error(err),
() => console.log('complete')
);
);
if (UserService.Instance.loggedIn) {
WebSocketService.Instance.getFollowedCommunities();
@ -51,13 +53,18 @@ export class Main extends Component<any, State> {
<h4>A Landing message</h4>
{UserService.Instance.loggedIn &&
<div>
<hr />
<h4>Subscribed forums</h4>
<ul class="list-unstyled">
{this.state.subscribedCommunities.map(community =>
<li><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
)}
</ul>
{this.state.loading ?
<h4 class="mt-3"><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
<hr />
<h4>Subscribed forums</h4>
<ul class="list-unstyled">
{this.state.subscribedCommunities.map(community =>
<li><Link to={`/community/${community.community_id}`}>{community.community_name}</Link></li>
)}
</ul>
</div>
}
</div>
}
</div>
@ -76,6 +83,7 @@ export class Main extends Component<any, State> {
} else if (op == UserOperation.GetFollowedCommunities) {
let res: GetFollowedCommunitiesResponse = msg;
this.state.subscribedCommunities = res.communities;
this.state.loading = false;
this.setState(this.state);
}
}

View File

@ -4,6 +4,7 @@ import { retryWhen, delay, take } from 'rxjs/operators';
import { PostForm as PostFormI, Post, PostResponse, UserOperation, Community, ListCommunitiesResponse } from '../interfaces';
import { WebSocketService } from '../services';
import { msgOp } from '../utils';
import * as autosize from 'autosize';
interface PostFormProps {
post?: Post; // If a post is given, that means this is an edit
@ -15,6 +16,7 @@ interface PostFormProps {
interface PostFormState {
postForm: PostFormI;
communities: Array<Community>;
loading: boolean;
}
export class PostForm extends Component<PostFormProps, PostFormState> {
@ -26,7 +28,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
auth: null,
community_id: null
},
communities: []
communities: [],
loading: false
}
constructor(props: any, context: any) {
@ -56,6 +59,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
WebSocketService.Instance.listCommunities();
}
componentDidMount() {
autosize(document.querySelectorAll('textarea'));
}
componentWillUnmount() {
this.subscription.unsubscribe();
}
@ -73,13 +80,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<div class="form-group row">
<label class="col-sm-2 col-form-label">Title</label>
<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 class="form-group row">
<label class="col-sm-2 col-form-label">Body</label>
<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 class="form-group row">
@ -94,7 +101,10 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</div>
<div class="form-group row">
<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>}
</div>
</div>
@ -110,6 +120,8 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
} else {
WebSocketService.Instance.createPost(i.state.postForm);
}
i.state.loading = true;
i.setState(i.state);
}
handlePostUrlChange(i: PostForm, event: any) {
@ -139,6 +151,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
parseMessage(msg: any) {
let op: UserOperation = msgOp(msg);
if (msg.error) {
this.state.loading = false;
return;
} else if (op == UserOperation.ListCommunities) {
let res: ListCommunitiesResponse = msg;
@ -150,9 +163,11 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
}
this.setState(this.state);
} else if (op == UserOperation.CreatePost) {
this.state.loading = false;
let res: PostResponse = msg;
this.props.onCreate(res.post.id);
} else if (op == UserOperation.EditPost) {
this.state.loading = false;
let res: PostResponse = msg;
this.props.onEdit(res.post);
}

View File

@ -18,6 +18,7 @@ interface PostListingsState {
posts: Array<Post>;
sortType: SortType;
type_: ListingType;
loading: boolean;
}
export class PostListings extends Component<PostListingsProps, PostListingsState> {
@ -44,7 +45,8 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
? ListingType.Community
: UserService.Instance.loggedIn
? ListingType.Subscribed
: ListingType.All
: ListingType.All,
loading: true
}
constructor(props: any, context: any) {
@ -77,11 +79,16 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
render() {
return (
<div>
<div>{this.selects()}</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>
{this.state.loading ?
<h4><svg class="icon icon-spinner spin"><use xlinkHref="#icon-spinner"></use></svg></h4> :
<div>
<div>{this.selects()}</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>
)
@ -103,11 +110,11 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
</select>
{!this.props.communityId &&
UserService.Instance.loggedIn &&
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
<option disabled>Type</option>
<option value={ListingType.All}>All</option>
<option value={ListingType.Subscribed}>Subscribed</option>
</select>
<select value={this.state.type_} onChange={linkEvent(this, this.handleTypeChange)} class="ml-2 custom-select w-auto">
<option disabled>Type</option>
<option value={ListingType.All}>All</option>
<option value={ListingType.Subscribed}>Subscribed</option>
</select>
}
</div>
@ -149,6 +156,7 @@ export class PostListings extends Component<PostListingsProps, PostListingsState
} else if (op == UserOperation.GetPosts) {
let res: GetPostsResponse = msg;
this.state.posts = res.posts;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.CreatePostLike) {
let res: CreatePostLikeResponse = msg;

View File

@ -19,6 +19,7 @@ interface PostState {
moderators: Array<CommunityUser>;
scrolled?: boolean;
scrolled_comment_id?: number;
loading: boolean;
}
export class Post extends Component<any, PostState> {
@ -30,7 +31,8 @@ export class Post extends Component<any, PostState> {
commentSort: CommentSortType.Hot,
community: null,
moderators: [],
scrolled: false
scrolled: false,
loading: true
}
constructor(props: any, context: any) {
@ -74,8 +76,9 @@ export class Post extends Component<any, PostState> {
render() {
return (
<div class="container">
{this.state.post &&
<div class="row">
{this.state.loading ?
<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">
<PostListing post={this.state.post} showBody showCommunity editable />
<div className="mb-2" />
@ -202,6 +205,7 @@ export class Post extends Component<any, PostState> {
this.state.comments = res.comments;
this.state.community = res.community;
this.state.moderators = res.moderators;
this.state.loading = false;
this.setState(this.state);
} else if (op == UserOperation.CreateComment) {
let res: CommentResponse = msg;

View 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>
);
}
}

View File

@ -10,6 +10,7 @@ import { Post } from './components/post';
import { Community } from './components/community';
import { Communities } from './components/communities';
import { User } from './components/user';
import { Symbols } from './components/symbols';
import './main.css';
@ -42,68 +43,12 @@ class Index extends Component<any, any> {
<Route path={`/user/:id/:heading`} component={User} />
<Route path={`/user/:id`} component={User} />
</Switch>
{this.symbols()}
<Symbols />
</div>
</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);

View File

@ -54,5 +54,15 @@ body {
stroke: currentColor;
fill: currentColor;
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); }
}