Doodle3D-Transform/src/js/actions/files.js

263 lines
8.5 KiB
JavaScript

import { createUniqueName, sketchDataToDoc, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT } from 'src/js/utils/saveUtils.js';
import sketchDataToJSON from '@doodle3d/doodle3d-core/lib/shape/sketchDataToJSON';
import { JSONToBlob } from '@doodle3d/doodle3d-core/lib/utils/binaryUtils';
import JSZip from 'jszip';
import * as actions from './index.js';
import { currentFileName, } from 'src/js/reducers/index.js';
import { notification } from './index.js';
import * as envs from 'src/js/constants/envs.js';
import { saveAs } from 'file-saver';
import seed from 'pouchdb-seed-design';
import { createPromiseAction } from 'redux-promise-action';
import PouchDB from 'pouchdb';
import * as notificationActions from 'react-notification-system-redux';
import JSONToSketchData from '@doodle3d/doodle3d-core/lib/shape/JSONToSketchData';
import { VERSION } from '@doodle3d/doodle3d-core/lib/constants/general.js';
import createSceneData from '@doodle3d/doodle3d-core/lib/d3/createSceneData';
import { generateThumb } from '@doodle3d/doodle3d-core/lib/utils/generateThumb.js';
import { blobToJSON } from '@doodle3d/doodle3d-core/lib/utils/binaryUtils.js';
export const SKETCH_EXPORT = 'SKETCH_EXPORT';
export const ALL_SKETCHES_EXPORT = 'ALL_SKETCHES_EXPORT';
export const OPEN_FILE = 'OPEN_FILE';
export const openFile = (data, name, id) => {
return (dispatch) => {
dispatch({ type: OPEN_FILE, name, data, id });
dispatch(actions.sketcher.openSketch({ data }));
};
};
export const saveFile = (name) => {
return async (dispatch, getState) => {
const state = getState();
const currentFileID = state.files.id;
const savedFile = currentFileID !== null; // saved files always have an id
const changedName = currentFileName(state) !== name;
const overrideId = (!savedFile || changedName) ? null : currentFileID;
const sketcherState = getState().sketcher.present;
const doc = await sketchDataToDoc(name, sketcherState);
return dispatch(actions.files.saveDoodle(name, doc, overrideId));
};
};
export const downloadAllSketches = () => {
// this function can be alot more optimized
// pouch db actually returns attachments as blobs
// when setting sketch to true this attachment is automatically converted to sketch data
// in the for loop the sketch data is converted back to blob
return async (dispatch, getState) => {
const names = [];
const zip = new JSZip();
const { value: files } = await dispatch(actions.files.loadAll({
include_docs: true,
attachments: true,
binary: true
}));
for (const { name, _attachments: { sketch: { data } }, updatedOn } of files) {
const uniqueName = createUniqueName(name || 'Doodle', names);
zip.file(`${uniqueName}.doodle3d`, data, { binary: true, date: new Date(updatedOn) });
names.push(uniqueName);
}
const dataBlob = await zip.generateAsync({ type: 'blob' });
return dispatch({
type: ALL_SKETCHES_EXPORT,
payload: dispatch(saveAs(dataBlob, 'My Doodles.zip'))
}).catch(error => {
dispatch(notification.error({ title: 'Saving doodle failed' }));
throw error;
});
};
};
export const openFileSelector = () => {
return async (dispatch, getState) => {
const files = await window.showOpenFilePicker({ multiple: true });
for (let file of files) {
await loadFile(dispatch, file.name, () => file.getFile());
}
};
async function loadFile(dispatch, fileName, getData) {
switch (fileName.match(/\.[0-9a-z]+$/i)[0].toUpperCase()) {
case ".ZIP":
let zip = await JSZip.loadAsync(await getData());
for (let fileName in zip.files) {
await loadFile(dispatch, fileName, () => zip.file(fileName).async("blob"));
}
break;
case ".DOODLE3D":
const sketchData = await getData();
const sketcherState = await createSceneData(await JSONToSketchData(await blobToJSON(sketchData)));
const imgBlob = await generateThumb(sketcherState, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 'blob');
const sketchBlob = new Blob([sketchData], { type: 'application/json' });
const doc = {
name: fileName,
appVersion: VERSION,
_attachments: {
img: { content_type: imgBlob.type, data: imgBlob },
sketch: { content_type: sketchBlob.type, data: sketchBlob }
}
};
await dispatch(actions.files.saveDoodle(fileName, doc, null));
break;
}
}
}
export const downloadSketch = (doc) => {
return (dispatch, getState) => {
console.log(doc);
const { name, _attachments: { sketch: { data } }, updatedOn } = doc;
const fileName = `${name || 'Doodle'}.doodle3d`;
return dispatch({
type: SKETCH_EXPORT,
payload: dispatch(saveAs(data, fileName))
}).catch(error => {
dispatch(notification.error({ title: 'Downloading doodle failed' }));
throw error;
});
};
};
export const downloadCurrentSketch = (name) => {
return (dispatch, getState) => {
const state = getState();
const fileName = `${name || 'Doodle'}.doodle3d`;
const json = sketchDataToJSON(state.sketcher.present);
const blob = JSONToBlob(json);
return dispatch({
type: SKETCH_EXPORT,
payload: dispatch(saveAs(blob, fileName))
}).catch(error => {
dispatch(notification.error({ title: 'Downloading doodle failed' }));
throw error;
});
};
};
let db;
let seedingDesignDoc;
export function init() {
return async (dispatch, getState) => {
db = new PouchDB('doodle3d-files');
seedingDesignDoc = seed(db, {
sketches: {
views: {
updatedOn: {
map: function (doc) {
if (doc.updatedOn) {
emit(doc.updatedOn);
}
}.toString()
},
name: {
map: function (doc) {
if (doc.name) {
emit(doc.name);
}
}.toString()
}
}
}
});
};
}
export const LOAD_GALLERY = 'LOAD_GALLERY';
export const loadGallery = createPromiseAction(async (dispatch, getState, page = 0, pageLength = 10, type = 'updatedOn', desc = true) => {
console.log("db", db);
console.log("seedingDesignDoc", seedingDesignDoc);
await seedingDesignDoc;
return db.query(`sketches/${type}`, {
include_docs: true,
attachments: true,
binary: true,
descending: desc,
limit: pageLength,
skip: page * pageLength
});
}, LOAD_GALLERY);
export const REMOVE_DOODLE = 'REMOVE_DOODLE';
export const removeDoodle = createPromiseAction(async (dispatch, getState, id) => {
const doc = await db.get(id);
const { _id, _rev } = doc;
await db.put({ _id, _rev, _deleted: true });
return doc;
}, REMOVE_DOODLE, {
onSuccess: ({ name }) => notificationActions.success({ position: 'tc', title: `successfully deleted doodle: ${name}` }),
onError: () => notificationActions.error({ position: 'tc', title: `failed to delete doodle` })
});
export const SAVE_DOODLE = 'SAVE_DOODLE';
export const saveDoodle = createPromiseAction(async (dispatch, getState, name, doc, overrideId) => {
doc.updatedOn = Date.now();
if (overrideId) {
const oldDoc = await db.get(overrideId);
doc = {
...doc,
_id: oldDoc._id,
_rev: oldDoc._rev
};
} else {
doc.createdOn = Date.now();
}
const { id, ok } = overrideId ? await db.put(doc) : await db.post(doc);
if (!ok) new Error('Error updating doc');
return { id, name };
}, SAVE_DOODLE, {
onSuccess: ({ name }) => notificationActions.success({ position: 'tc', title: `successfully saved doodle: ${name}` }),
onError: () => notificationActions.error({ position: 'tc', title: `failed to save doodle` })
});
export const LOAD_ALL = 'LOAD_ALL';
export const loadAll = createPromiseAction(async (dispatch, getState, options = {}) => {
const { rows } = await db.query('sketches/name', options);
return rows.map(({ doc }) => doc);
}, LOAD_ALL);
export const LOAD_ALL_NAMES = 'LOAD_ALL_NAMES';
export const loadAllNames = createPromiseAction(async () => {
const { rows } = await db.query('sketches/name', { include_docs: true });
return rows.map(({ doc }) => doc.name);
}, LOAD_ALL_NAMES);
export const LOAD_NUM_FILES = 'LOAD_NUM_FILES';
export const loadNumFiles = createPromiseAction(async () => {
const { rows } = await db.query('sketches/name');
return rows.length;
}, LOAD_NUM_FILES);
export const OPEN_CONTEXT_MENU = 'OPEN_CONTEXT_MENU';
export function openContextMenu(id) {
return { type: OPEN_CONTEXT_MENU, id };
}
export const CLOSE_CONTEXT_MENU = 'CLOSE_CONTEXT_MENU';
export function closeContextMenu() {
return { type: CLOSE_CONTEXT_MENU };
}