import * as React from "react";
import * as NavigationData from "~/common/navigation-data";
import * as Actions from "~/common/actions";
import * as State from "~/common/state";
import * as Credentials from "~/common/credentials";
import * as Validations from "~/common/validations";
import * as System from "~/components/system";
// NOTE(jim):
// Scenes each have an ID and can be navigated to with _handleAction
import SceneDeals from "~/scenes/SceneDeals";
import SceneEditAccount from "~/scenes/SceneEditAccount";
import SceneFile from "~/scenes/SceneFile";
import SceneFilesFolder from "~/scenes/SceneFilesFolder";
import SceneHome from "~/scenes/SceneHome";
import SceneSettings from "~/scenes/SceneSettings";
import SceneWallet from "~/scenes/SceneWallet";
import SceneSlates from "~/scenes/SceneSlates";
import SceneLocalData from "~/scenes/SceneLocalData";
import SceneSettingsDeveloper from "~/scenes/SceneSettingsDeveloper";
import SceneSignIn from "~/scenes/SceneSignIn";
import SceneSlate from "~/scenes/SceneSlate";
import SceneActivity from "~/scenes/SceneActivity";
// NOTE(jim):
// Sidebars each have a decorator and can be shown to with _handleAction
import SidebarCreateSlate from "~/components/sidebars/SidebarCreateSlate";
import SidebarCreateWalletAddress from "~/components/sidebars/SidebarCreateWalletAddress";
import SidebarWalletSendFunds from "~/components/sidebars/SidebarWalletSendFunds";
import SidebarFileStorageDeal from "~/components/sidebars/SidebarFileStorageDeal";
import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket";
import SidebarDragDropNotice from "~/components/sidebars/SidebarDragDropNotice";
import SidebarSingleSlateSettings from "~/components/sidebars/SidebarSingleSlateSettings";
// NOTE(jim):
// Core components to the application structure.
import ApplicationNavigation from "~/components/core/ApplicationNavigation";
import ApplicationHeader from "~/components/core/ApplicationHeader";
import ApplicationLayout from "~/components/core/ApplicationLayout";
import WebsitePrototypeWrapper from "~/components/core/WebsitePrototypeWrapper";
import Cookies from "universal-cookie";
const cookies = new Cookies();
const SIDEBARS = {
SIDEBAR_FILE_STORAGE_DEAL: ,
SIDEBAR_WALLET_SEND_FUNDS: ,
SIDEBAR_CREATE_WALLET_ADDRESS: ,
SIDEBAR_ADD_FILE_TO_BUCKET: ,
SIDEBAR_CREATE_SLATE: ,
SIDEBAR_DRAG_DROP_NOTICE: ,
SIDEBAR_SINGLE_SLATE_SETTINGS: ,
};
const SCENES = {
HOME: ,
WALLET: ,
FOLDER: ,
FILE: ,
SLATE: ,
DEALS: ,
SETTINGS: ,
SETTINGS_DEVELOPER: ,
EDIT_ACCOUNT: ,
SLATES: ,
LOCAL_DATA: ,
ACTIVITY: ,
};
export default class ApplicationPage extends React.Component {
state = {
selected: State.getSelectedState(this.props.viewer),
viewer: State.getInitialState(this.props.viewer),
history: [{ id: 1, scrollTop: 0, data: null }],
currentIndex: 0,
data: null,
sidebar: null,
sidebarLoading: false,
online: null,
};
async componentDidMount() {
window.addEventListener("dragenter", this._handleDragEnter);
window.addEventListener("dragleave", this._handleDragLeave);
window.addEventListener("dragover", this._handleDragOver);
window.addEventListener("drop", this._handleDrop);
window.addEventListener("online", this._handleOnlineStatus);
window.addEventListener("offline", this._handleOnlineStatus);
}
componentWillUnmount() {
window.removeEventListener("dragenter", this._handleDragEnter);
window.removeEventListener("dragleave", this._handleDragLeave);
window.removeEventListener("dragover", this._handleDragOver);
window.removeEventListener("drop", this._handleDrop);
}
_handleOnlineStatus = async () => {
window.alert(navigator.onLine ? "online" : "offline");
this.setState({ online: navigator.onLine });
};
_handleRegisterFile = ({ fileLoading }) => {
return this.setState({
fileLoading,
});
};
_handleUploadFile = async ({ file, slate }) => {
let formData = new FormData();
const HEIC2ANY = require("heic2any");
// TODO(jim): Put this somewhere else to handle conversion cases.
if (file.type.startsWith("image/heic")) {
const converted = await HEIC2ANY({
blob: file,
toType: "image/png",
quality: 1,
});
formData.append("data", converted);
} else {
formData.append("data", file);
}
const upload = (path) =>
new Promise((resolve, reject) => {
const XHR = new XMLHttpRequest();
XHR.open("post", path, true);
XHR.onerror = (event) => {
console.log(event);
};
// NOTE(jim): UPLOADS ONLY.
XHR.upload.addEventListener(
"progress",
(event) => {
if (event.lengthComputable) {
console.log("FILE UPLOAD PROGRESS", event);
this.setState({
fileLoading: {
...this.state.fileLoading,
[`${file.lastModified}-${file.name}`]: {
name: file.name,
loaded: event.loaded,
total: event.total,
},
},
});
}
},
false
);
XHR.onloadend = (event) => {
console.log("FILE UPLOAD END", event);
try {
return resolve(JSON.parse(event.target.response));
} catch (e) {
return resolve({
error: "SERVER_UPLOAD_ERROR",
});
}
};
XHR.send(formData);
});
const json = await upload(`/api/data/${file.name}`);
console.log(json);
if (!json || json.error || !json.data) {
this.setState({
fileLoading: {
...this.state.fileLoading,
[`${file.lastModified}-${file.name}`]: {
name: file.name,
failed: true,
},
},
});
return !json ? { error: "NO_RESPONSE" } : json;
}
if (slate) {
const addResponse = await fetch(`/api/slates/add-url`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ slate, data: { ...json.data } }),
});
if (!addResponse || addResponse.error) {
console.log(addResponse.error);
alert("TODO: Adding an image to Slate went wrong.");
}
}
return json;
};
_handleDragEnter = (e) => {
if (this.state.sidebar) {
return;
}
e.preventDefault();
this._handleAction({
type: "SIDEBAR",
value: "SIDEBAR_DRAG_DROP_NOTICE",
});
};
_handleDragLeave = (e) => {
e.preventDefault();
};
_handleDragOver = (e) => {
e.preventDefault();
};
_handleSidebarLoading = (sidebarLoading) => this.setState({ sidebarLoading });
_handleDrop = async (e) => {
e.preventDefault();
this.setState({ fileLoading: true });
// TODO(jim):
// Refactor later
const navigation = NavigationData.generate(this.state.viewer);
const next = this.state.history[this.state.currentIndex];
const current = NavigationData.getCurrentById(navigation, next.id);
let slate;
if (current.target && current.target.slatename) {
slate = { ...current.target, id: current.target.slateId };
}
const files = [];
let fileLoading = {};
if (e.dataTransfer.items) {
for (var i = 0; i < e.dataTransfer.items.length; i++) {
if (e.dataTransfer.items[i].kind === "file") {
var file = e.dataTransfer.items[i].getAsFile();
if (Validations.isFileTypeAllowed(file.type)) {
this._handleAction({
type: "SIDEBAR",
value: "SIDEBAR_ADD_FILE_TO_BUCKET",
data: slate,
});
files.push(file);
fileLoading[`${file.lastModified}-${file.name}`] = {
name: file.name,
loaded: 0,
total: file.size,
};
}
}
}
}
if (!files.length) {
alert("TODO: Files not supported error");
return;
}
// NOTE(jim): Stages each file.
this._handleRegisterFile({ fileLoading });
// NOTE(jim): Uploads each file.
for (let i = 0; i < files.length; i++) {
await this._handleUploadFile({ file: files[i], slate });
}
// NOTE(jim): Rehydrates user.
await this.rehydrate({ resetFiles: true });
};
rehydrate = async (options) => {
const response = await Actions.hydrateAuthenticatedUser();
console.log("REHYDRATION CALL", response);
if (!response || response.error) {
return null;
}
const updates = {
viewer: State.getInitialState(response.data),
selected: State.getSelectedState(response.data),
};
if (options && options.resetFiles) {
updates.fileLoading = null;
updates.sidebar = null;
}
this.setState(updates);
return { rehydrated: true };
};
_handleSubmit = async (data) => {
let response;
if (data.type === "CREATE_SLATE") {
response = await Actions.createSlate({
name: data.name,
});
}
if (data.type === "CREATE_WALLET_ADDRESS") {
response = await Actions.updateViewer({
type: "CREATE_FILECOIN_ADDRESS",
address: {
name: data.name,
type: data.wallet_type,
makeDefault: data.makeDefault,
},
});
}
if (data.type === "SEND_WALLET_ADDRESS_FILECOIN") {
response = await Actions.sendFilecoin({
source: data.source,
target: data.target,
amount: data.amount,
});
}
console.log({ response });
await this.rehydrate();
this._handleDismissSidebar();
return response;
};
_handleCancel = () => {
this._handleDismissSidebar();
};
_handleDeleteYourself = async () => {
// TODO(jim):
// Put this somewhere better for messages.
const message = "Do you really want to delete your account? It will be permanently removed";
if (!window.confirm(message)) {
return false;
}
let response = await Actions.deleteViewer();
console.log("DELETE_VIEWER", response);
await this._handleSignOut();
};
_handleAuthenticate = async (state) => {
// NOTE(jim): Kills existing session cookie if there is one.
const jwt = cookies.get(Credentials.session.key);
if (jwt) {
cookies.remove(Credentials.session.key);
}
// NOTE(jim): Acts as our existing username exists check.
// If the user exists, move on the sign in anyways.
let response = await Actions.createUser(state);
console.log("CREATE_USER", response);
response = await Actions.signIn(state);
if (response.error) {
console.log("SIGN IN ERROR", response);
return null;
}
if (response.token) {
// NOTE(jim):
// + One week.
// + Only requests to the same site.
// + Not using sessionStorage so the cookie doesn't leave when the browser dies.
cookies.set(Credentials.session.key, response.token, true, {
path: "/",
maxAge: 3600 * 24 * 7,
sameSite: "strict",
});
}
return await this.rehydrate();
};
_handleSignOut = () => {
const jwt = cookies.get(Credentials.session.key);
if (jwt) {
cookies.remove(Credentials.session.key);
window.location.reload();
}
};
_handleViewerChange = (e) => {
this.setState({
viewer: { ...this.state.viewer, [e.target.name]: e.target.value },
});
};
_handleSelectedChange = (e) => {
this.setState({
selected: { ...this.state.selected, [e.target.name]: e.target.value },
});
};
_handleDismissSidebar = () => {
this.setState({ sidebar: null, sidebarLoading: false, data: null });
};
_handleAction = (options) => {
console.log(options);
if (options.type === "NAVIGATE") {
return this._handleNavigateTo({ id: options.value }, options.data);
}
if (options.type === "NEW_WINDOW") {
return window.open(options.value);
}
if (options.type === "ACTION") {
return alert(JSON.stringify(options));
}
if (options.type === "DOWNLOAD") {
return alert(JSON.stringify(options));
}
if (options.type === "SIDEBAR") {
return this.setState({
sidebar: SIDEBARS[options.value],
data: options.data,
});
}
return alert(JSON.stringify(options));
};
_handleNavigateTo = (next, data = null) => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
this.state.history[this.state.currentIndex].data = data;
if (this.state.currentIndex !== this.state.history.length - 1) {
const adjustedArray = [...this.state.history];
adjustedArray.length = this.state.currentIndex + 1;
return this.setState(
{
history: [...adjustedArray, next],
currentIndex: this.state.currentIndex + 1,
data,
sidebar: null,
},
() => window.scrollTo(0, 0)
);
}
this.setState(
{
history: [...this.state.history, next],
currentIndex: this.state.currentIndex + 1,
data,
sidebar: null,
},
() => window.scrollTo(0, 0)
);
};
_handleBack = () => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
const next = this.state.history[this.state.currentIndex - 1];
this.setState(
{
currentIndex: this.state.currentIndex - 1,
sidebar: null,
data: { ...next.data },
},
() => {
console.log({ next });
window.scrollTo(0, next.scrollTop);
}
);
};
_handleForward = () => {
this.state.history[this.state.currentIndex].scrollTop = window.scrollY;
const next = this.state.history[this.state.currentIndex + 1];
this.setState(
{
currentIndex: this.state.currentIndex + 1,
sidebar: null,
data: { ...next.data },
},
() => {
console.log({ next });
window.scrollTo(0, next.scrollTop);
}
);
};
render() {
// TODO(colin): Populate this.
console.log({ analytics: this.props.analytics });
// NOTE(jim): Not authenticated.
if (!this.state.viewer) {
return (
);
}
// NOTE(jim): Authenticated.
const navigation = NavigationData.generate(this.state.viewer);
const next = this.state.history[this.state.currentIndex];
const current = NavigationData.getCurrentById(navigation, next.id);
const navigationElement = (
);
let headerElement = (
);
if (current.target.decorator === "FILE") {
headerElement = null;
}
const scene = React.cloneElement(SCENES[current.target.decorator], {
current: current.target,
data: this.state.data,
viewer: this.state.viewer,
selected: this.state.selected,
onNavigateTo: this._handleNavigateTo,
onSelectedChange: this._handleSelectedChange,
onViewerChange: this._handleViewerChange,
onDeleteYourself: this._handleDeleteYourself,
onAction: this._handleAction,
onBack: this._handleBack,
onForward: this._handleForward,
onRehydrate: this.rehydrate,
});
let sidebarElement;
if (this.state.sidebar) {
sidebarElement = React.cloneElement(this.state.sidebar, {
selected: this.state.selected,
viewer: this.state.viewer,
data: this.state.data,
fileLoading: this.state.fileLoading,
sidebarLoading: this.state.sidebarLoading,
onSelectedChange: this._handleSelectedChange,
onSubmit: this._handleSubmit,
onCancel: this._handleCancel,
onRegisterFile: this._handleRegisterFile,
onUploadFile: this._handleUploadFile,
onSidebarLoading: this._handleSidebarLoading,
onAction: this._handleAction,
onRehydrate: this.rehydrate,
});
}
const title = `Slate : ${current.target.pageTitle}`;
const description = "This is an early preview.";
const url = "https://slate.host/_";
return (
{scene}
);
}
}