slate/components/core/Slate.js

301 lines
7.1 KiB
JavaScript
Raw Normal View History

2020-07-30 04:00:45 +03:00
import * as React from "react";
import * as Constants from "~/common/constants";
import * as SVG from "~/common/svg";
2020-08-27 07:24:49 +03:00
import * as Actions from "~/common/actions";
2020-07-30 04:00:45 +03:00
2020-08-22 20:30:03 +03:00
import { Responsive, WidthProvider } from "react-grid-layout";
2020-07-30 04:00:45 +03:00
import { css } from "@emotion/react";
2020-08-22 20:30:03 +03:00
import { LoaderSpinner } from "~/components/system/components/Loaders";
2020-09-12 01:25:33 +03:00
import { dispatchCustomEvent } from "~/common/custom-events";
2020-07-30 04:00:45 +03:00
2020-08-09 01:19:35 +03:00
import SlateMediaObjectPreview from "~/components/core/SlateMediaObjectPreview";
2020-08-27 07:24:49 +03:00
import CircleButtonGray from "~/components/core/CircleButtonGray";
2020-08-09 01:19:35 +03:00
2020-08-22 20:30:03 +03:00
// NOTE(jim): I broke my own rules to do this. Sorry.
const STYLES_ITEM = css`
2020-07-30 04:00:45 +03:00
display: flex;
2020-08-22 20:30:03 +03:00
align-items: center;
justify-content: center;
position: relative;
width: 100%;
2020-08-02 09:41:18 +03:00
2020-08-22 20:30:03 +03:00
:hover {
figure {
visibility: visible;
opacity: 1;
}
2020-08-27 07:24:49 +03:00
img,
article {
opacity: 0.4;
}
2020-08-22 20:30:03 +03:00
}
`;
const STYLES_CONTAINER = css`
padding: 24px;
`;
const STYLES_ACTIONS = css`
2020-08-25 08:31:35 +03:00
z-index: ${Constants.zindex.navigation};
2020-08-22 20:30:03 +03:00
bottom: 16px;
right: 8px;
position: fixed;
2020-08-22 20:30:03 +03:00
flex-direction: column;
display: flex;
`;
const STYLES_BUTTON = css`
opacity: 0;
visibility: hidden;
transition: 200ms ease all;
position: absolute;
2020-08-27 07:24:49 +03:00
margin: auto;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
2020-08-22 20:30:03 +03:00
`;
const STYLES_ACTION_BUTTON = css`
font-family: ${Constants.font.code};
font-size: 10px;
text-transform: uppercase;
user-select: none;
height: 32px;
padding: 0 16px 0 16px;
border-radius: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
z-index: ${Constants.zindex.modal};
background: ${Constants.system.pitchBlack};
transition: 200ms ease all;
color: ${Constants.system.white};
cursor: pointer;
margin: auto;
margin: 4px 16px 4px 16px;
flex-shrink: 0;
text-decoration: none;
2020-09-08 01:38:55 +03:00
2020-08-22 20:30:03 +03:00
:hover {
background-color: ${Constants.system.black};
2020-07-30 04:00:45 +03:00
}
2020-08-09 01:19:35 +03:00
`;
2020-07-30 04:00:45 +03:00
2020-08-22 20:30:03 +03:00
const ResponsiveReactGridLayout = WidthProvider(Responsive);
const COLUMN_MAP = { lg: 12, md: 8, sm: 6, xs: 4, xxs: 2 };
export const generateLayout = (items) => {
if (!items) {
return [];
}
if (!items.length) {
return [];
}
2020-08-22 20:30:03 +03:00
return items.map((item, i) => {
var y = Math.ceil(Math.random() * 4) + 1;
return {
x: (i * 2) % 10,
y: 0,
w: 2,
h: 2,
minW: 2,
minH: 2,
// NOTE(jim): Library quirk thats required.
i: i.toString(),
};
});
};
2020-07-30 04:00:45 +03:00
export default class Slate extends React.Component {
2020-08-22 20:30:03 +03:00
static defaultProps = {
onLayoutChange: () => {},
};
state = {
currentBreakpoint: "lg",
compactType: "vertical",
};
2020-08-22 12:32:40 +03:00
2020-08-22 20:30:03 +03:00
_handleResetLayout = () => {
2020-08-22 12:32:40 +03:00
if (!this.props.editing) {
2020-08-22 20:30:03 +03:00
return null;
2020-08-22 12:32:40 +03:00
}
2020-08-22 20:30:03 +03:00
if (!window.confirm("Are you sure you want to reset your layout?")) {
return null;
2020-08-22 12:32:40 +03:00
}
2020-08-22 20:30:03 +03:00
const layouts = { lg: generateLayout(this.props.items) };
this.props.onLayoutChange(null, layouts);
2020-08-22 12:32:40 +03:00
};
2020-08-22 20:30:03 +03:00
_handleSaveLayout = async () => {
2020-08-22 12:32:40 +03:00
if (!this.props.editing) {
2020-08-22 20:30:03 +03:00
return null;
2020-08-22 12:32:40 +03:00
}
2020-08-22 20:30:03 +03:00
await this.props.onLayoutSave();
};
2020-08-27 07:24:49 +03:00
_handleSelect = (e, index) => {
// TODO(jim): Test this again in React 17
e.preventDefault();
e.stopPropagation();
this.props.onSelect(index);
};
_handleDeepLink = async (e, object) => {
// TODO(jim): Test this again in React 17
e.preventDefault();
e.stopPropagation();
const response = await Actions.getSlateBySlatename({
query: object.deeplink,
deeplink: true,
});
2020-09-13 03:42:46 +03:00
if (!response) {
2020-09-12 01:25:33 +03:00
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble connecting right now and could not locate that slate. Please try again later",
},
},
});
return;
}
if (response.error) {
dispatchCustomEvent({
name: "create-alert",
detail: { alert: { decorator: response.decorator } },
});
2020-09-08 01:47:54 +03:00
return;
2020-08-27 07:24:49 +03:00
}
2020-09-13 03:42:46 +03:00
if (!response.data || !response.data.slate) {
2020-09-12 01:25:33 +03:00
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "We could not locate that slate. Please try again later",
},
},
});
2020-09-08 01:47:54 +03:00
return;
2020-08-27 07:24:49 +03:00
}
return window.open(
`/${response.data.slate.user.username}/${response.data.slate.slatename}`
);
};
2020-08-22 20:30:03 +03:00
generateDOM = () => {
return this.props.layouts.lg.map((each, index) => {
const data = this.props.items[each.i];
if (!data) {
return <div key={index} />;
}
2020-08-22 20:30:03 +03:00
return (
<div key={index} css={STYLES_ITEM}>
2020-09-01 02:09:57 +03:00
<SlateMediaObjectPreview
2020-09-05 07:45:50 +03:00
charCap={70}
2020-09-01 02:09:57 +03:00
type={data.type}
url={data.url}
title={data.title || data.name}
/>
2020-08-27 07:24:49 +03:00
<figure css={STYLES_BUTTON}>
<CircleButtonGray
style={{ margin: 8 }}
2020-08-27 07:24:49 +03:00
onMouseUp={(e) => this._handleSelect(e, index)}
onTouchEnd={(e) => this._handleSelect(e, index)}
>
2020-08-22 20:30:03 +03:00
<SVG.Eye height="16px" />
2020-08-27 07:24:49 +03:00
</CircleButtonGray>
{data.deeplink ? (
<CircleButtonGray
style={{ margin: 8 }}
2020-08-27 07:24:49 +03:00
onMouseUp={(e) => this._handleDeepLink(e, data)}
onTouchEnd={(e) => this._handleDeepLink(e, data)}
>
<SVG.DeepLink height="16px" />
2020-08-27 07:24:49 +03:00
</CircleButtonGray>
) : null}
2020-08-22 20:30:03 +03:00
</figure>
</div>
);
});
};
onBreakpointChange = (breakpoint) => {
this.setState({
currentBreakpoint: breakpoint,
});
};
onLayoutChange = (layout, layouts) => {
this.props.onLayoutChange(layout, layouts);
2020-08-22 12:32:40 +03:00
};
2020-07-30 04:00:45 +03:00
render() {
return (
2020-08-22 20:30:03 +03:00
<div css={STYLES_CONTAINER}>
<ResponsiveReactGridLayout
columns={COLUMN_MAP}
layouts={this.props.layouts}
isDraggable={!!this.props.editing}
isResizable={!!this.props.editing}
onBreakpointChange={this.onBreakpointChange}
onLayoutChange={this.onLayoutChange}
measureBeforeMount={false}
useCSSTransforms={false}
compactType={this.state.compactType}
preventCollision={false}
margin={[24, 24]}
>
2020-08-22 20:30:03 +03:00
{this.generateDOM()}
</ResponsiveReactGridLayout>
2020-09-03 09:00:02 +03:00
2020-08-22 20:30:03 +03:00
{this.props.editing ? (
<div css={STYLES_ACTIONS}>
<span css={STYLES_ACTION_BUTTON} onClick={this._handleResetLayout}>
Reset Layout
</span>
2020-08-25 08:11:47 +03:00
<span
css={STYLES_ACTION_BUTTON}
2020-09-03 09:00:02 +03:00
onMouseUp={this._handleSaveLayout}
onTouchEnd={this._handleSaveLayout}
2020-08-25 08:11:47 +03:00
style={{
backgroundColor:
this.props.saving === "IDLE" ? Constants.system.brand : null,
}}
>
{this.props.saving === "SAVING" ? (
2020-08-22 20:30:03 +03:00
<LoaderSpinner style={{ height: 16, width: 16 }} />
) : this.props.saving === "IDLE" ? (
2020-08-25 08:11:47 +03:00
"Save"
2020-08-22 20:30:03 +03:00
) : (
"Saved"
)}
</span>
</div>
) : null}
2020-07-30 04:00:45 +03:00
</div>
);
}
}