1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 12:56:41 +07:00

flow get more

This commit is contained in:
Fedor Katurov 2019-11-18 14:49:32 +07:00
parent f440a5f4c3
commit 971578bb21
11 changed files with 281 additions and 159 deletions

View file

@ -9,6 +9,7 @@ import { flowSetCellView } from "~/redux/flow/actions";
import { PRESETS } from "~/constants/urls"; import { PRESETS } from "~/constants/urls";
import { debounce } from "throttle-debounce"; import { debounce } from "throttle-debounce";
import { NODE_TYPES } from "~/redux/node/constants"; import { NODE_TYPES } from "~/redux/node/constants";
import { Group } from "~/components/containers/Group";
interface IProps { interface IProps {
node: INode; node: INode;
@ -135,7 +136,7 @@ const Cell: FC<IProps> = ({
<div className={styles.text}> <div className={styles.text}>
{title && <div className={styles.text_title}>{title}</div>} {title && <div className={styles.text_title}>{title}</div>}
<div <Group
dangerouslySetInnerHTML={{ __html: formatCellText(text) }} dangerouslySetInnerHTML={{ __html: formatCellText(text) }}
/> />
</div> </div>
@ -145,7 +146,7 @@ const Cell: FC<IProps> = ({
<div className={styles.text_only}> <div className={styles.text_only}>
{title && <div className={styles.text_title}>{title}</div>} {title && <div className={styles.text_title}>{title}</div>}
<div <Group
dangerouslySetInnerHTML={{ __html: formatCellText(text) }} dangerouslySetInnerHTML={{ __html: formatCellText(text) }}
/> />
</div> </div>

View file

@ -1,30 +1,52 @@
import React, { FC } from 'react'; import React, { FC, useEffect, useCallback } from "react";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { FlowGrid } from '~/components/flow/FlowGrid'; import { FlowGrid } from "~/components/flow/FlowGrid";
import { selectFlow } from '~/redux/flow/selectors'; import { selectFlow } from "~/redux/flow/selectors";
import * as NODE_ACTIONS from '~/redux/node/actions'; import * as NODE_ACTIONS from "~/redux/node/actions";
import * as FLOW_ACTIONS from '~/redux/flow/actions'; import * as FLOW_ACTIONS from "~/redux/flow/actions";
import pick from 'ramda/es/pick'; import pick from "ramda/es/pick";
import { selectUser } from '~/redux/auth/selectors'; import { selectUser } from "~/redux/auth/selectors";
const mapStateToProps = state => ({ const mapStateToProps = state => ({
flow: pick(['nodes', 'heroes', 'recent', 'updated'], selectFlow(state)), flow: pick(
user: pick(['role', 'id'], selectUser(state)), ["nodes", "heroes", "recent", "updated", "is_loading"],
selectFlow(state)
),
user: pick(["role", "id"], selectUser(state))
}); });
const mapDispatchToProps = { const mapDispatchToProps = {
nodeGotoNode: NODE_ACTIONS.nodeGotoNode, nodeGotoNode: NODE_ACTIONS.nodeGotoNode,
flowSetCellView: FLOW_ACTIONS.flowSetCellView, flowSetCellView: FLOW_ACTIONS.flowSetCellView,
flowGetMore: FLOW_ACTIONS.flowGetMore
}; };
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}; type IProps = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {};
const FlowLayoutUnconnected: FC<IProps> = ({ const FlowLayoutUnconnected: FC<IProps> = ({
flow: { nodes, heroes, recent, updated }, flow: { nodes, heroes, recent, updated, is_loading },
user, user,
nodeGotoNode, nodeGotoNode,
flowSetCellView, flowSetCellView,
}) => ( flowGetMore
}) => {
const loadMore = useCallback(() => {
const pos =
window.scrollY + window.innerHeight - document.body.scrollHeight;
if (is_loading || pos < -600) return;
flowGetMore();
}, [flowGetMore, is_loading]);
useEffect(() => {
window.addEventListener("scroll", loadMore);
return () => window.removeEventListener("scroll", loadMore);
}, [loadMore]);
return (
<FlowGrid <FlowGrid
nodes={nodes} nodes={nodes}
heroes={heroes} heroes={heroes}
@ -35,6 +57,7 @@ const FlowLayoutUnconnected: FC<IProps> = ({
onChangeCellView={flowSetCellView} onChangeCellView={flowSetCellView}
/> />
); );
};
const FlowLayout = connect( const FlowLayout = connect(
mapStateToProps, mapStateToProps,

View file

@ -21,11 +21,10 @@ render(
/* /*
[Stage 0]: [Stage 0]:
- fix: text nodes cell has no preview (actually, that's a problem of brief) - check if email is registered at social login
- friendship
- password restore - password restore
- signup? - signup?
- better node brief update
- flow updates - flow updates
- flow infinite scroll - flow infinite scroll
- avatar upload - avatar upload
@ -43,6 +42,8 @@ render(
- comment editing - comment editing
Done: Done:
- better node brief update
- fix: text nodes cell has no preview (actually, that's a problem of brief)
- relocate files - relocate files
- backend: exclude node covers on import - backend: exclude node covers on import
- profile editing - profile editing
@ -59,5 +60,4 @@ Done:
- fix: select node and edit it. All images will be not loaded - fix: select node and edit it. All images will be not loaded
- fix: text nodes cell not clickable - fix: text nodes cell not clickable
- fix: text nodes should not have 'no comments yet badge - fix: text nodes should not have 'no comments yet badge
*/ */

View file

@ -1,29 +1,43 @@
import { FLOW_ACTIONS } from './constants'; import { FLOW_ACTIONS } from "./constants";
import { IFlowState } from './reducer'; import { IFlowState } from "./reducer";
import { INode } from '../types'; import { INode } from "../types";
export const flowSetNodes = (nodes: IFlowState['nodes']) => ({ export const flowSetNodes = (nodes: IFlowState["nodes"]) => ({
nodes, nodes,
type: FLOW_ACTIONS.SET_NODES, type: FLOW_ACTIONS.SET_NODES
}); });
export const flowSetHeroes = (heroes: IFlowState['heroes']) => ({ export const flowSetHeroes = (heroes: IFlowState["heroes"]) => ({
heroes, heroes,
type: FLOW_ACTIONS.SET_HEROES, type: FLOW_ACTIONS.SET_HEROES
}); });
export const flowSetRecent = (recent: IFlowState['recent']) => ({ export const flowSetRecent = (recent: IFlowState["recent"]) => ({
recent, recent,
type: FLOW_ACTIONS.SET_RECENT, type: FLOW_ACTIONS.SET_RECENT
}); });
export const flowSetUpdated = (updated: IFlowState['updated']) => ({ export const flowSetUpdated = (updated: IFlowState["updated"]) => ({
updated, updated,
type: FLOW_ACTIONS.SET_UPDATED, type: FLOW_ACTIONS.SET_UPDATED
}); });
export const flowSetCellView = (id: INode['id'], flow: INode['flow']) => ({ export const flowSetCellView = (id: INode["id"], flow: INode["flow"]) => ({
type: FLOW_ACTIONS.SET_CELL_VIEW, type: FLOW_ACTIONS.SET_CELL_VIEW,
id, id,
flow, flow
});
export const flowSetRange = (range: IFlowState["range"]) => ({
range,
type: FLOW_ACTIONS.SET_RANGE
});
export const flowGetMore = () => ({
type: FLOW_ACTIONS.GET_MORE
});
export const flowSetFlow = (data: Partial<IFlowState>) => ({
type: FLOW_ACTIONS.SET_FLOW,
data
}); });

View file

@ -1,11 +1,16 @@
import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api'; import {
import { INode, IResultWithStatus } from '../types'; api,
import { API } from '~/constants/api'; configWithToken,
import { flowSetCellView } from '~/redux/flow/actions'; resultMiddleware,
errorMiddleware
} from "~/utils/api";
import { INode, IResultWithStatus } from "../types";
import { API } from "~/constants/api";
import { flowSetCellView } from "~/redux/flow/actions";
export const postNode = ({ export const postNode = ({
access, access,
node, node
}: { }: {
access: string; access: string;
node: INode; node: INode;
@ -15,22 +20,22 @@ export const postNode = ({
.then(resultMiddleware) .then(resultMiddleware)
.catch(errorMiddleware); .catch(errorMiddleware);
export const getNodes = ({ // export const getNodes = ({
skip = 0, // from = null
}: { // }: {
skip: number; // from: string;
}): Promise<IResultWithStatus<{ nodes: INode[] }>> => // }): Promise<IResultWithStatus<{ nodes: INode[] }>> =>
api // api
.get(API.NODE.GET, { params: { skip } }) // .get(API.NODE.GET, { params: { from } })
.then(resultMiddleware) // .then(resultMiddleware)
.catch(errorMiddleware); // .catch(errorMiddleware);
export const postCellView = ({ export const postCellView = ({
id, id,
flow, flow,
access, access
}: ReturnType<typeof flowSetCellView> & { access: string }): Promise< }: ReturnType<typeof flowSetCellView> & { access: string }): Promise<
IResultWithStatus<{ is_liked: INode['is_liked'] }> IResultWithStatus<{ is_liked: INode["is_liked"] }>
> => > =>
api api
.post(API.NODE.SET_CELL_VIEW(id), { flow }, configWithToken(access)) .post(API.NODE.SET_CELL_VIEW(id), { flow }, configWithToken(access))

View file

@ -1,10 +1,13 @@
const prefix = 'FLOW.'; const prefix = "FLOW.";
export const FLOW_ACTIONS = { export const FLOW_ACTIONS = {
GET_FLOW: `${prefix}GET_FLOW`, GET_FLOW: `${prefix}GET_FLOW`,
SET_FLOW: `${prefix}SET_FLOW`,
SET_NODES: `${prefix}SET_NODES`, SET_NODES: `${prefix}SET_NODES`,
SET_HEROES: `${prefix}SET_HEROES`, SET_HEROES: `${prefix}SET_HEROES`,
SET_RECENT: `${prefix}SET_RECENT`, SET_RECENT: `${prefix}SET_RECENT`,
SET_UPDATED: `${prefix}SET_UPDATED`, SET_UPDATED: `${prefix}SET_UPDATED`,
SET_RANGE: `${prefix}SET_RANGE`,
SET_CELL_VIEW: `${prefix}SET_CELL_VIEW`, SET_CELL_VIEW: `${prefix}SET_CELL_VIEW`,
GET_MORE: `${prefix}GET_MORE`
}; };

View file

@ -1,23 +1,53 @@
import assocPath from 'ramda/es/assocPath'; import assocPath from "ramda/es/assocPath";
import { FLOW_ACTIONS } from './constants'; import { FLOW_ACTIONS } from "./constants";
import { flowSetNodes, flowSetHeroes, flowSetRecent, flowSetUpdated } from './actions'; import {
import { IFlowState } from './reducer'; flowSetNodes,
flowSetHeroes,
flowSetRecent,
flowSetUpdated,
flowSetRange,
flowSetFlow
} from "./actions";
import { IFlowState } from "./reducer";
const setNodes = (state: IFlowState, { nodes }: ReturnType<typeof flowSetNodes>) => const setNodes = (
assocPath(['nodes'], nodes, state); state: IFlowState,
{ nodes }: ReturnType<typeof flowSetNodes>
) => assocPath(["nodes"], nodes, state);
const setHeroes = (state: IFlowState, { heroes }: ReturnType<typeof flowSetHeroes>) => const setHeroes = (
assocPath(['heroes'], heroes, state); state: IFlowState,
{ heroes }: ReturnType<typeof flowSetHeroes>
) => assocPath(["heroes"], heroes, state);
const setRecent = (state: IFlowState, { recent }: ReturnType<typeof flowSetRecent>) => const setRecent = (
assocPath(['recent'], recent, state); state: IFlowState,
{ recent }: ReturnType<typeof flowSetRecent>
) => assocPath(["recent"], recent, state);
const setUpdated = (state: IFlowState, { updated }: ReturnType<typeof flowSetUpdated>) => const setUpdated = (
assocPath(['updated'], updated, state); state: IFlowState,
{ updated }: ReturnType<typeof flowSetUpdated>
) => assocPath(["updated"], updated, state);
const setRange = (
state: IFlowState,
{ range }: ReturnType<typeof flowSetRange>
) => assocPath(["range"], range, state);
const setFlow = (
state: IFlowState,
{ data }: ReturnType<typeof flowSetFlow>
): IFlowState => ({
...state,
...data
});
export const FLOW_HANDLERS = { export const FLOW_HANDLERS = {
[FLOW_ACTIONS.SET_NODES]: setNodes, [FLOW_ACTIONS.SET_NODES]: setNodes,
[FLOW_ACTIONS.SET_HEROES]: setHeroes, [FLOW_ACTIONS.SET_HEROES]: setHeroes,
[FLOW_ACTIONS.SET_RECENT]: setRecent, [FLOW_ACTIONS.SET_RECENT]: setRecent,
[FLOW_ACTIONS.SET_UPDATED]: setUpdated, [FLOW_ACTIONS.SET_UPDATED]: setUpdated,
[FLOW_ACTIONS.SET_RANGE]: setRange,
[FLOW_ACTIONS.SET_FLOW]: setFlow
}; };

View file

@ -1,6 +1,6 @@
import { createReducer } from '~/utils/reducer'; import { createReducer } from "~/utils/reducer";
import { INode, IError } from '../types'; import { INode, IError } from "../types";
import { FLOW_HANDLERS } from './handlers'; import { FLOW_HANDLERS } from "./handlers";
export type IFlowState = Readonly<{ export type IFlowState = Readonly<{
is_loading: boolean; is_loading: boolean;
@ -8,6 +8,7 @@ export type IFlowState = Readonly<{
heroes: Partial<INode>[]; heroes: Partial<INode>[];
recent: Partial<INode>[]; recent: Partial<INode>[];
updated: Partial<INode>[]; updated: Partial<INode>[];
range: [string, string];
error: IError; error: IError;
}>; }>;
@ -16,8 +17,9 @@ const INITIAL_STATE: IFlowState = {
heroes: [], heroes: [],
recent: [], recent: [],
updated: [], updated: [],
range: [null, null], // drop it, we use realtime range calc
is_loading: false, is_loading: false,
error: null, error: null
}; };
export default createReducer(INITIAL_STATE, FLOW_HANDLERS); export default createReducer(INITIAL_STATE, FLOW_HANDLERS);

View file

@ -1,56 +1,78 @@
import { takeLatest, call, put, select } from 'redux-saga/effects'; import {
import { REHYDRATE } from 'redux-persist'; takeLatest,
import { FLOW_ACTIONS } from './constants'; call,
import { getNodes } from '../node/api'; put,
select,
takeLeading,
delay
} from "redux-saga/effects";
import { REHYDRATE } from "redux-persist";
import { FLOW_ACTIONS } from "./constants";
import { getNodes } from "../node/api";
import { import {
flowSetNodes, flowSetNodes,
flowSetCellView, flowSetCellView,
flowSetHeroes, flowSetHeroes,
flowSetRecent, flowSetRecent,
flowSetUpdated, flowSetUpdated,
} from './actions'; flowSetFlow
import { IResultWithStatus, INode } from '../types'; } from "./actions";
import { selectFlowNodes } from './selectors'; import { IResultWithStatus } from "../types";
import { reqWrapper } from '../auth/sagas'; import { selectFlowNodes } from "./selectors";
import { postCellView } from './api'; import { reqWrapper } from "../auth/sagas";
import { IFlowState } from './reducer'; import { postCellView } from "./api";
import { IFlowState } from "./reducer";
function* onGetFlow() { function* onGetFlow() {
yield put(flowSetFlow({ is_loading: true }));
const { const {
data: { nodes = [], heroes = [], recent = [], updated = [] }, data: { nodes = [], heroes = [], recent = [], updated = [], mode }
}: IResultWithStatus<{ }: IResultWithStatus<{
nodes: IFlowState['nodes']; nodes: IFlowState["nodes"];
heroes: IFlowState['heroes']; heroes: IFlowState["heroes"];
recent: IFlowState['recent']; recent: IFlowState["recent"];
updated: IFlowState['updated']; updated: IFlowState["updated"];
mode: string;
}> = yield call(reqWrapper, getNodes, {}); }> = yield call(reqWrapper, getNodes, {});
// if (!nodes || !nodes.length) { yield put(flowSetFlow({ is_loading: false, nodes }));
// yield put(flowSetNodes([]));
// yield put(flowSetHeroes([]));
// yield put(flowSetRecent([]));
// yield put(flowSetUpdated([]));
// return;
// }
yield put(flowSetNodes(nodes)); if (heroes.length) yield put(flowSetHeroes(heroes));
yield put(flowSetHeroes(heroes)); if (recent.length) yield put(flowSetRecent(recent));
yield put(flowSetRecent(recent)); if (updated.length) yield put(flowSetUpdated(updated));
yield put(flowSetUpdated(updated));
document.getElementById('main_loader').style.display = 'none'; document.getElementById("main_loader").style.display = "none";
} }
function* onSetCellView({ id, flow }: ReturnType<typeof flowSetCellView>) { function* onSetCellView({ id, flow }: ReturnType<typeof flowSetCellView>) {
const nodes = yield select(selectFlowNodes); const nodes = yield select(selectFlowNodes);
yield put(flowSetNodes(nodes.map(node => (node.id === id ? { ...node, flow } : node)))); yield put(
flowSetNodes(nodes.map(node => (node.id === id ? { ...node, flow } : node)))
);
const { data, error } = yield call(reqWrapper, postCellView, { id, flow }); const { data, error } = yield call(reqWrapper, postCellView, { id, flow });
}
console.log({ data, error }); function* getMore() {
yield put(flowSetFlow({ is_loading: true }));
const nodes: IFlowState["nodes"] = yield select(selectFlowNodes);
const from =
nodes && nodes[nodes.length - 1] && nodes[nodes.length - 1].created_at;
const { error, data } = yield call(reqWrapper, getNodes, { from });
if (error || !data || !data.nodes) return;
yield put(
flowSetFlow({ is_loading: false, nodes: [...nodes, ...data.nodes] })
);
yield delay(data.nodes.length > 0 ? 2000 : 30000);
} }
export default function* nodeSaga() { export default function* nodeSaga() {
yield takeLatest([FLOW_ACTIONS.GET_FLOW, REHYDRATE], onGetFlow); yield takeLatest([FLOW_ACTIONS.GET_FLOW, REHYDRATE], onGetFlow);
yield takeLatest(FLOW_ACTIONS.SET_CELL_VIEW, onSetCellView); yield takeLatest(FLOW_ACTIONS.SET_CELL_VIEW, onSetCellView);
yield takeLeading(FLOW_ACTIONS.GET_MORE, getMore);
} }

View file

@ -1,12 +1,17 @@
import { api, configWithToken, resultMiddleware, errorMiddleware } from '~/utils/api'; import {
import { INode, IResultWithStatus, IComment } from '../types'; api,
import { API } from '~/constants/api'; configWithToken,
import { nodeUpdateTags, nodeLike, nodeStar } from './actions'; resultMiddleware,
import { INodeState } from './reducer'; errorMiddleware
} from "~/utils/api";
import { INode, IResultWithStatus, IComment } from "../types";
import { API } from "~/constants/api";
import { nodeUpdateTags, nodeLike, nodeStar } from "./actions";
import { INodeState } from "./reducer";
export const postNode = ({ export const postNode = ({
access, access,
node, node
}: { }: {
access: string; access: string;
node: INode; node: INode;
@ -18,20 +23,20 @@ export const postNode = ({
// .then(console.log); // .then(console.log);
export const getNodes = ({ export const getNodes = ({
skip = 0, from = null,
access, access
}: { }: {
skip?: number; from?: string;
access: string; access: string;
}): Promise<IResultWithStatus<{ nodes: INode[] }>> => }): Promise<IResultWithStatus<{ nodes: INode[] }>> =>
api api
.get(API.NODE.GET, configWithToken(access, { params: { skip } })) .get(API.NODE.GET, configWithToken(access, { params: { from } }))
.then(resultMiddleware) .then(resultMiddleware)
.catch(errorMiddleware); .catch(errorMiddleware);
export const getNode = ({ export const getNode = ({
id, id,
access, access
}: { }: {
id: string | number; id: string | number;
access: string; access: string;
@ -44,7 +49,7 @@ export const getNode = ({
export const postNodeComment = ({ export const postNodeComment = ({
id, id,
data, data,
access, access
}: { }: {
access: string; access: string;
id: number; id: number;
@ -58,11 +63,11 @@ export const postNodeComment = ({
export const getNodeComments = ({ export const getNodeComments = ({
id, id,
access, access,
order = 'ASC', order = "ASC"
}: { }: {
id: number; id: number;
access: string; access: string;
order: 'ASC' | 'DESC'; order: "ASC" | "DESC";
}): Promise<IResultWithStatus<{ comments: Comment[] }>> => }): Promise<IResultWithStatus<{ comments: Comment[] }>> =>
api api
.get(API.NODE.COMMENT(id), configWithToken(access, { params: { order } })) .get(API.NODE.COMMENT(id), configWithToken(access, { params: { order } }))
@ -71,11 +76,11 @@ export const getNodeComments = ({
export const getNodeRelated = ({ export const getNodeRelated = ({
id, id,
access, access
}: { }: {
id: number; id: number;
access: string; access: string;
}): Promise<IResultWithStatus<{ related: INodeState['related'] }>> => }): Promise<IResultWithStatus<{ related: INodeState["related"] }>> =>
api api
.get(API.NODE.RELATED(id), configWithToken(access)) .get(API.NODE.RELATED(id), configWithToken(access))
.then(resultMiddleware) .then(resultMiddleware)
@ -84,7 +89,7 @@ export const getNodeRelated = ({
export const updateNodeTags = ({ export const updateNodeTags = ({
id, id,
tags, tags,
access, access
}: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise< }: ReturnType<typeof nodeUpdateTags> & { access: string }): Promise<
IResultWithStatus<{ node: INode }> IResultWithStatus<{ node: INode }>
> => > =>
@ -95,9 +100,9 @@ export const updateNodeTags = ({
export const postNodeLike = ({ export const postNodeLike = ({
id, id,
access, access
}: ReturnType<typeof nodeLike> & { access: string }): Promise< }: ReturnType<typeof nodeLike> & { access: string }): Promise<
IResultWithStatus<{ is_liked: INode['is_liked'] }> IResultWithStatus<{ is_liked: INode["is_liked"] }>
> => > =>
api api
.post(API.NODE.POST_LIKE(id), {}, configWithToken(access)) .post(API.NODE.POST_LIKE(id), {}, configWithToken(access))
@ -106,9 +111,9 @@ export const postNodeLike = ({
export const postNodeStar = ({ export const postNodeStar = ({
id, id,
access, access
}: ReturnType<typeof nodeStar> & { access: string }): Promise< }: ReturnType<typeof nodeStar> & { access: string }): Promise<
IResultWithStatus<{ is_liked: INode['is_liked'] }> IResultWithStatus<{ is_liked: INode["is_liked"] }>
> => > =>
api api
.post(API.NODE.POST_STAR(id), {}, configWithToken(access)) .post(API.NODE.POST_STAR(id), {}, configWithToken(access))

View file

@ -1,19 +1,23 @@
import { IFile } from '~/redux/types'; import { IFile } from "~/redux/types";
import formatDistanceToNow from 'date-fns/formatDistanceToNow'; import formatDistanceToNow from "date-fns/formatDistanceToNow";
import { ru } from 'date-fns/locale'; import { ru } from "date-fns/locale";
import Axios from 'axios'; import Axios from "axios";
import { PRESETS } from '~/constants/urls'; import { PRESETS } from "~/constants/urls";
export const getStyle = (oElm: any, strCssRule: string) => { export const getStyle = (oElm: any, strCssRule: string) => {
if (document.defaultView && document.defaultView.getComputedStyle) { if (document.defaultView && document.defaultView.getComputedStyle) {
return document.defaultView.getComputedStyle(oElm, '').getPropertyValue(strCssRule); return document.defaultView
.getComputedStyle(oElm, "")
.getPropertyValue(strCssRule);
} }
if (oElm.currentStyle) { if (oElm.currentStyle) {
return oElm.currentStyle[strCssRule.replace(/-(\w)/g, (strMatch, p1) => p1.toUpperCase())]; return oElm.currentStyle[
strCssRule.replace(/-(\w)/g, (strMatch, p1) => p1.toUpperCase())
];
} }
return ''; return "";
}; };
function polarToCartesian(centerX, centerY, radius, angleInDegrees) { function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
@ -21,7 +25,7 @@ function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
return { return {
x: centerX + radius * Math.cos(angleInRadians), x: centerX + radius * Math.cos(angleInRadians),
y: centerY + radius * Math.sin(angleInRadians), y: centerY + radius * Math.sin(angleInRadians)
}; };
} }
@ -38,10 +42,10 @@ export const describeArc = (
const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1; const largeArcFlag = endAngle - startAngle <= 180 ? 0 : 1;
return [ return [
'M', "M",
start.x, start.x,
start.y, start.y,
'A', "A",
radius, radius,
radius, radius,
0, 0,
@ -49,63 +53,76 @@ export const describeArc = (
0, 0,
end.x, end.x,
end.y, end.y,
'L', "L",
x, x,
y, y,
'L', "L",
start.x, start.x,
start.y, start.y
].join(' '); ].join(" ");
}; };
export const getURL = (file: Partial<IFile>, size?: typeof PRESETS[keyof typeof PRESETS]) => { export const getURL = (
file: Partial<IFile>,
size?: typeof PRESETS[keyof typeof PRESETS]
) => {
if (!file || !file.url) return null; if (!file || !file.url) return null;
if (size) { if (size) {
return file.url return file.url
.replace('REMOTE_CURRENT://', `${process.env.REMOTE_CURRENT}cache/${size}/`) .replace(
.replace('REMOTE_OLD://', process.env.REMOTE_OLD); "REMOTE_CURRENT://",
`${process.env.REMOTE_CURRENT}cache/${size}/`
)
.replace("REMOTE_OLD://", process.env.REMOTE_OLD);
} }
return file.url return file.url
.replace('REMOTE_CURRENT://', process.env.REMOTE_CURRENT) .replace("REMOTE_CURRENT://", process.env.REMOTE_CURRENT)
.replace('REMOTE_OLD://', process.env.REMOTE_OLD); .replace("REMOTE_OLD://", process.env.REMOTE_OLD);
}; };
export const formatText = (text: string): string => export const formatText = (text: string): string =>
!text !text
? '' ? ""
: text : text
.replace(/(\n{2,})/gi, '\n') .replace(/\n{1,}/gim, "\n")
.replace(/</g, '&lt;') .replace(/</g, "&lt;")
.replace(/>/g, '&gt;') .replace(/>/g, "&gt;")
.replace( .replace(
/~([\wа-яА-Я\-]+)/giu, /~([\wа-яА-Я\-]+)/giu,
'<span class="username" onClick="window.postMessage({ type: \'username\', username: \'$1\'});">~$1</span>' "<span class=\"username\" onClick=\"window.postMessage({ type: 'username', username: '$1'});\">~$1</span>"
) )
.replace(/:\/\//gim, ':|--|') .replace(/:\/\//gim, ":|--|")
.replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>') .replace(/(\/\/[^\n]+)/gim, '<span class="grey">$1</span>')
.replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>') .replace(/(\/\*[\s\S]*?\*\/)/gim, '<span class="grey">$1</span>')
.replace(/:\|--\|/gim, '://') .replace(/:\|--\|/gim, "://")
.split('\n') .split("\n")
.filter(el => el.trim().length)
.map(el => `<p>${el}</p>`) .map(el => `<p>${el}</p>`)
.join(''); .join("");
export const formatCommentText = (author: string, text: string): string => export const formatCommentText = (author: string, text: string): string =>
text text
? formatText(text).replace( ? formatText(text).replace(
/^<p>/, /^<p>/,
author ? `<p><b class="comment-author">${author}: </b>` : '<p>' author ? `<p><b class="comment-author">${author}: </b>` : "<p>"
) )
: ''; : "";
export const formatCellText = (text: string): string => formatText(text); export const formatCellText = (text: string): string => formatText(text);
export const getPrettyDate = (date: string): string => export const getPrettyDate = (date: string): string =>
formatDistanceToNow(new Date(date), { locale: ru, includeSeconds: true, addSuffix: true }); formatDistanceToNow(new Date(date), {
locale: ru,
includeSeconds: true,
addSuffix: true
});
export const getYoutubeTitle = async (id: string) => { export const getYoutubeTitle = async (id: string) => {
Axios.get(`http://youtube.com/get_video_info?video_id=${id}`).then(console.log); Axios.get(`http://youtube.com/get_video_info?video_id=${id}`).then(
console.log
);
}; };
(<any>window).getYoutubeTitle = getYoutubeTitle; (<any>window).getYoutubeTitle = getYoutubeTitle;