summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authorDessalines <tyhou13@gmx.com>2019-12-29 15:39:48 -0500
committerDessalines <tyhou13@gmx.com>2019-12-29 15:39:48 -0500
commita4428528e30b18eb85596edf9c26bc8b6b7d11ee (patch)
tree450f5662ada20a7f66f809033d57fbc12dcc963d /ui
parent106aaf4f28ef34d68848f48e5673f955a04b6deb (diff)
Adding user avatars / icons. Requires pictshare.
- Fixes #188
Diffstat (limited to 'ui')
-rw-r--r--ui/.eslintignore2
-rw-r--r--ui/fuse.js26
-rw-r--r--ui/src/components/comment-node.tsx18
-rw-r--r--ui/src/components/main.tsx14
-rw-r--r--ui/src/components/navbar.tsx16
-rw-r--r--ui/src/components/post-listing.tsx11
-rw-r--r--ui/src/components/search.tsx15
-rw-r--r--ui/src/components/sidebar.tsx12
-rw-r--r--ui/src/components/user.tsx82
-rw-r--r--ui/src/interfaces.ts7
-rw-r--r--ui/src/translations/en.ts1
-rw-r--r--ui/src/utils.ts7
-rw-r--r--ui/translation_report.ts20
13 files changed, 199 insertions, 32 deletions
diff --git a/ui/.eslintignore b/ui/.eslintignore
new file mode 100644
index 00000000..aa6ed178
--- /dev/null
+++ b/ui/.eslintignore
@@ -0,0 +1,2 @@
+fuse.js
+translation_report.ts
diff --git a/ui/fuse.js b/ui/fuse.js
index 85eb75e2..3feab490 100644
--- a/ui/fuse.js
+++ b/ui/fuse.js
@@ -1,11 +1,11 @@
-const {
+import {
FuseBox,
Sparky,
EnvPlugin,
CSSPlugin,
WebIndexPlugin,
- QuantumPlugin
-} = require('fuse-box');
+ QuantumPlugin,
+} from 'fuse-box';
// const transformInferno = require('../../dist').default
const transformInferno = require('ts-transform-inferno').default;
const transformClasscat = require('ts-transform-classcat').default;
@@ -25,22 +25,22 @@ Sparky.task('config', _ => {
before: [transformClasscat(), transformInferno()],
},
alias: {
- 'locale': 'moment/locale'
- },
+ locale: 'moment/locale',
+ },
plugins: [
EnvPlugin({ NODE_ENV: isProduction ? 'production' : 'development' }),
CSSPlugin(),
WebIndexPlugin({
title: 'Inferno Typescript FuseBox Example',
template: 'src/index.html',
- path: isProduction ? "/static" : "/"
+ path: isProduction ? '/static' : '/',
}),
isProduction &&
- QuantumPlugin({
- bakeApiIntoBundle: 'app',
- treeshake: true,
- uglify: true,
- }),
+ QuantumPlugin({
+ bakeApiIntoBundle: 'app',
+ treeshake: true,
+ uglify: true,
+ }),
],
});
app = fuse.bundle('app').instructions('>index.tsx');
@@ -48,7 +48,9 @@ Sparky.task('config', _ => {
// Sparky.task('version', _ => setVersion());
Sparky.task('clean', _ => Sparky.src('dist/').clean('dist/'));
Sparky.task('env', _ => (isProduction = true));
-Sparky.task('copy-assets', () => Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static'));
+Sparky.task('copy-assets', () =>
+ Sparky.src('assets/**/**.*').dest(isProduction ? 'dist/' : 'dist/static')
+);
Sparky.task('dev', ['clean', 'config', 'copy-assets'], _ => {
fuse.dev();
app.hmr().watch();
diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx
index f408ef27..34ec0dfb 100644
--- a/ui/src/components/comment-node.tsx
+++ b/ui/src/components/comment-node.tsx
@@ -17,7 +17,13 @@ import {
BanType,
} from '../interfaces';
import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime, canMod, isMod } from '../utils';
+import {
+ mdToHtml,
+ getUnixTime,
+ canMod,
+ isMod,
+ pictshareAvatarThumbnail,
+} from '../utils';
import * as moment from 'moment';
import { MomentTime } from './moment-time';
import { CommentForm } from './comment-form';
@@ -128,7 +134,15 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
className="text-info"
to={`/u/${node.comment.creator_name}`}
>
- {node.comment.creator_name}
+ {node.comment.creator_avatar && (
+ <img
+ height="32"
+ width="32"
+ src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
+ class="rounded-circle mr-1"
+ />
+ )}
+ <span>{node.comment.creator_name}</span>
</Link>
</li>
{this.isMod && (
diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx
index 0d6be91d..d1042f38 100644
--- a/ui/src/components/main.tsx
+++ b/ui/src/components/main.tsx
@@ -31,6 +31,7 @@ import {
routeSortTypeToEnum,
routeListingTypeToEnum,
postRefetchSeconds,
+ pictshareAvatarThumbnail,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
@@ -65,6 +66,9 @@ export class Main extends Component<any, MainState> {
number_of_posts: null,
number_of_comments: null,
number_of_communities: null,
+ enable_downvotes: null,
+ open_registration: null,
+ enable_nsfw: null,
},
admins: [],
banned: [],
@@ -341,7 +345,15 @@ export class Main extends Component<any, MainState> {
{this.state.site.admins.map(admin => (
<li class="list-inline-item">
<Link class="text-info" to={`/u/${admin.name}`}>
- {admin.name}
+ {admin.avatar && (
+ <img
+ height="32"
+ width="32"
+ src={pictshareAvatarThumbnail(admin.avatar)}
+ class="rounded-circle mr-1"
+ />
+ )}
+ <span>{admin.name}</span>
</Link>
</li>
))}
diff --git a/ui/src/components/navbar.tsx b/ui/src/components/navbar.tsx
index 306dc74f..f1a98941 100644
--- a/ui/src/components/navbar.tsx
+++ b/ui/src/components/navbar.tsx
@@ -13,7 +13,7 @@ import {
GetSiteResponse,
Comment,
} from '../interfaces';
-import { msgOp } from '../utils';
+import { msgOp, pictshareAvatarThumbnail } from '../utils';
import { version } from '../version';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
@@ -151,7 +151,19 @@ export class Navbar extends Component<any, NavbarState> {
class="nav-link"
to={`/u/${UserService.Instance.user.username}`}
>
- {UserService.Instance.user.username}
+ <span>
+ {UserService.Instance.user.avatar && (
+ <img
+ src={pictshareAvatarThumbnail(
+ UserService.Instance.user.avatar
+ )}
+ height="32"
+ width="32"
+ class="rounded-circle mr-2"
+ />
+ )}
+ {UserService.Instance.user.username}
+ </span>
</Link>
</li>
</>
diff --git a/ui/src/components/post-listing.tsx b/ui/src/components/post-listing.tsx
index 61a4c865..1f3a74ac 100644
--- a/ui/src/components/post-listing.tsx
+++ b/ui/src/components/post-listing.tsx
@@ -25,6 +25,7 @@ import {
isImage,
isVideo,
getUnixTime,
+ pictshareAvatarThumbnail,
} from '../utils';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
@@ -248,7 +249,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<li className="list-inline-item">
<span>{i18n.t('by')} </span>
<Link className="text-info" to={`/u/${post.creator_name}`}>
- {post.creator_name}
+ {post.creator_avatar && (
+ <img
+ height="32"
+ width="32"
+ src={pictshareAvatarThumbnail(post.creator_avatar)}
+ class="rounded-circle mr-1"
+ />
+ )}
+ <span>{post.creator_name}</span>
</Link>
{this.isMod && (
<span className="mx-1 badge badge-light">
diff --git a/ui/src/components/search.tsx b/ui/src/components/search.tsx
index bba7c5ad..d92c06e1 100644
--- a/ui/src/components/search.tsx
+++ b/ui/src/components/search.tsx
@@ -19,6 +19,7 @@ import {
fetchLimit,
routeSearchTypeToEnum,
routeSortTypeToEnum,
+ pictshareAvatarThumbnail,
} from '../utils';
import { PostListing } from './post-listing';
import { SortSelect } from './sort-select';
@@ -286,7 +287,19 @@ export class Search extends Component<any, SearchState> {
<Link
className="text-info"
to={`/u/${(i.data as UserView).name}`}
- >{`/u/${(i.data as UserView).name}`}</Link>
+ >
+ {(i.data as UserView).avatar && (
+ <img
+ height="32"
+ width="32"
+ src={pictshareAvatarThumbnail(
+ (i.data as UserView).avatar
+ )}
+ class="rounded-circle mr-1"
+ />
+ )}
+ <span>{`/u/${(i.data as UserView).name}`}</span>
+ </Link>
</span>
<span>{` - ${
(i.data as UserView).comment_score
diff --git a/ui/src/components/sidebar.tsx b/ui/src/components/sidebar.tsx
index 85f81a30..0e95666f 100644
--- a/ui/src/components/sidebar.tsx
+++ b/ui/src/components/sidebar.tsx
@@ -8,7 +8,7 @@ import {
UserView,
} from '../interfaces';
import { WebSocketService, UserService } from '../services';
-import { mdToHtml, getUnixTime } from '../utils';
+import { mdToHtml, getUnixTime, pictshareAvatarThumbnail } from '../utils';
import { CommunityForm } from './community-form';
import { i18n } from '../i18next';
import { T } from 'inferno-i18next';
@@ -194,7 +194,15 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
{this.props.moderators.map(mod => (
<li class="list-inline-item">
<Link class="text-info" to={`/u/${mod.user_name}`}>
- {mod.user_name}
+ {mod.avatar && (
+ <img
+ height="32"
+ width="32"
+ src={pictshareAvatarThumbnail(mod.avatar)}
+ class="rounded-circle mr-1"
+ />
+ )}
+ <span>{mod.user_name}</span>
</Link>
</li>
))}
diff --git a/ui/src/components/user.tsx b/ui/src/components/user.tsx
index 6d6a2e0c..e97b26f9 100644
--- a/ui/src/components/user.tsx
+++ b/ui/src/components/user.tsx
@@ -58,6 +58,7 @@ interface UserState {
sort: SortType;
page: number;
loading: boolean;
+ avatarLoading: boolean;
userSettingsForm: UserSettingsForm;
userSettingsLoading: boolean;
deleteAccountLoading: boolean;
@@ -78,6 +79,7 @@ export class User extends Component<any, UserState> {
number_of_comments: null,
comment_score: null,
banned: null,
+ avatar: null,
},
user_id: null,
username: null,
@@ -87,6 +89,7 @@ export class User extends Component<any, UserState> {
posts: [],
admins: [],
loading: true,
+ avatarLoading: false,
view: this.getViewFromProps(this.props),
sort: this.getSortTypeFromProps(this.props),
page: this.getPageFromProps(this.props),
@@ -96,6 +99,7 @@ export class User extends Component<any, UserState> {
default_sort_type: null,
default_listing_type: null,
lang: null,
+ avatar: null,
auth: null,
},
userSettingsLoading: null,
@@ -203,7 +207,17 @@ export class User extends Component<any, UserState> {
) : (
<div class="row">
<div class="col-12 col-md-8">
- <h5>/u/{this.state.user.name}</h5>
+ <h5>
+ {this.state.user.avatar && (
+ <img
+ height="80"
+ width="80"
+ src={this.state.user.avatar}
+ class="rounded-circle mr-2"
+ />
+ )}
+ <span>/u/{this.state.user.name}</span>
+ </h5>
{this.selects()}
{this.state.view == View.Overview && this.overview()}
{this.state.view == View.Comments && this.comments()}
@@ -425,6 +439,39 @@ export class User extends Component<any, UserState> {
<div class="form-group">
<div class="col-12">
<label>
+ <T i18nKey="avatar">#</T>
+ </label>
+ <form class="d-inline">
+ <label
+ htmlFor="file-upload"
+ class="pointer ml-4 text-muted small font-weight-bold"
+ >
+ <img
+ height="80"
+ width="80"
+ src={
+ this.state.userSettingsForm.avatar
+ ? this.state.userSettingsForm.avatar
+ : 'https://via.placeholder.com/300/000?text=Avatar'
+ }
+ class="rounded-circle"
+ />
+ </label>
+ <input
+ id="file-upload"
+ type="file"
+ accept="image/*,video/*"
+ name="file"
+ class="d-none"
+ disabled={!UserService.Instance.user}
+ onChange={linkEvent(this, this.handleImageUpload)}
+ />
+ </form>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-12">
+ <label>
<T i18nKey="language">#</T>
</label>
<select
@@ -739,6 +786,38 @@ export class User extends Component<any, UserState> {
this.setState(this.state);
}
+ handleImageUpload(i: User, event: any) {
+ event.preventDefault();
+ let file = event.target.files[0];
+ const imageUploadUrl = `/pictshare/api/upload.php`;
+ const formData = new FormData();
+ formData.append('file', file);
+
+ i.state.avatarLoading = true;
+ i.setState(i.state);
+
+ fetch(imageUploadUrl, {
+ method: 'POST',
+ body: formData,
+ })
+ .then(res => res.json())
+ .then(res => {
+ let url = `${window.location.origin}/pictshare/${res.url}`;
+ if (res.filetype == 'mp4') {
+ url += '/raw';
+ }
+ i.state.userSettingsForm.avatar = url;
+ console.log(url);
+ i.state.avatarLoading = false;
+ i.setState(i.state);
+ })
+ .catch(error => {
+ i.state.avatarLoading = false;
+ i.setState(i.state);
+ alert(error);
+ });
+ }
+
handleUserSettingsSubmit(i: User, event: any) {
event.preventDefault();
i.state.userSettingsLoading = true;
@@ -802,6 +881,7 @@ export class User extends Component<any, UserState> {
this.state.userSettingsForm.default_listing_type =
UserService.Instance.user.default_listing_type;
this.state.userSettingsForm.lang = UserService.Instance.user.lang;
+ this.state.userSettingsForm.avatar = UserService.Instance.user.avatar;
}
document.title = `/u/${this.state.user.name} - ${WebSocketService.Instance.site.name}`;
window.scrollTo(0, 0);
diff --git a/ui/src/interfaces.ts b/ui/src/interfaces.ts
index 8b860887..7020ea49 100644
--- a/ui/src/interfaces.ts
+++ b/ui/src/interfaces.ts
@@ -80,11 +80,13 @@ export interface User {
default_sort_type: SortType;
default_listing_type: ListingType;
lang: string;
+ avatar?: string;
}
export interface UserView {
id: number;
name: string;
+ avatar?: string;
fedi_name: string;
published: string;
number_of_posts: number;
@@ -98,6 +100,7 @@ export interface CommunityUser {
id: number;
user_id: number;
user_name: string;
+ avatar?: string;
community_id: number;
community_name: string;
published: string;
@@ -116,6 +119,7 @@ export interface Community {
published: string;
updated?: string;
creator_name: string;
+ creator_avatar?: string;
category_name: string;
number_of_subscribers: number;
number_of_posts: number;
@@ -141,6 +145,7 @@ export interface Post {
published: string;
updated?: string;
creator_name: string;
+ creator_avatar?: string;
community_name: string;
community_removed: boolean;
community_deleted: boolean;
@@ -172,6 +177,7 @@ export interface Comment {
banned: boolean;
banned_from_community: boolean;
creator_name: string;
+ creator_avatar?: string;
score: number;
upvotes: number;
downvotes: number;
@@ -474,6 +480,7 @@ export interface UserSettingsForm {
default_sort_type: SortType;
default_listing_type: ListingType;
lang: string;
+ avatar?: string;
auth: string;
}
diff --git a/ui/src/translations/en.ts b/ui/src/translations/en.ts
index 60a73a30..10862081 100644
--- a/ui/src/translations/en.ts
+++ b/ui/src/translations/en.ts
@@ -28,6 +28,7 @@ export const en = {
cancel: 'Cancel',
preview: 'Preview',
upload_image: 'upload image',
+ avatar: 'Avatar',
formatting_help: 'formatting help',
view_source: 'view source',
unlock: 'unlock',
diff --git a/ui/src/utils.ts b/ui/src/utils.ts
index 98645fe3..29aabdcf 100644
--- a/ui/src/utils.ts
+++ b/ui/src/utils.ts
@@ -338,3 +338,10 @@ export function objectFlip(obj: any) {
});
return ret;
}
+
+export function pictshareAvatarThumbnail(src: string): string {
+ // sample url: http://localhost:8535/pictshare/gs7xuu.jpg
+ let split = src.split('pictshare');
+ let out = `${split[0]}pictshare/96x96${split[1]}`;
+ return out;
+}
diff --git a/ui/translation_report.ts b/ui/translation_report.ts
index da4cd3aa..1975cfa5 100644
--- a/ui/translation_report.ts
+++ b/ui/translation_report.ts
@@ -10,15 +10,15 @@ import { nl } from './src/translations/nl';
import { it } from './src/translations/it';
let files = [
- {t: de, n: 'de'},
- {t: eo, n: 'eo'},
- {t: es, n: 'es'},
- {t: fr, n: 'fr'},
- {t: it, n: 'it'},
- {t: nl, n: 'nl'},
- {t: ru, n: 'ru'},
- {t: sv, n: 'sv'},
- {t: zh, n: 'zh'},
+ { t: de, n: 'de' },
+ { t: eo, n: 'eo' },
+ { t: es, n: 'es' },
+ { t: fr, n: 'fr' },
+ { t: it, n: 'it' },
+ { t: nl, n: 'nl' },
+ { t: ru, n: 'ru' },
+ { t: sv, n: 'sv' },
+ { t: zh, n: 'zh' },
];
let masterKeys = Object.keys(en.translation);
@@ -27,7 +27,7 @@ report += '--- | --- | ---\n';
for (let file of files) {
let keys = Object.keys(file.t.translation);
- let pct: number = (keys.length / masterKeys.length * 100);
+ let pct: number = (keys.length / masterKeys.length) * 100;
let missing = difference(masterKeys, keys);
report += `${file.n} | ${pct.toFixed(0)}% | ${missing} \n`;
}