├── .npmignore
├── .gitignore
├── assets
├── logo.jpg
├── play_mov.gif
├── screen_1.jpg
├── screen_2.jpg
├── screen_3.jpg
└── screen_4.jpg
├── docs
├── build
│ ├── sp-ico.png
│ ├── bg-space.png
│ ├── sp-ico@2x.png
│ └── PhotoLayoutEditor.js.LICENSE.txt
├── index.css
└── index.html
├── src
├── PhotoLayoutEditor
│ ├── style
│ │ ├── images
│ │ │ ├── bg-space.png
│ │ │ ├── sp-ico.png
│ │ │ └── sp-ico@2x.png
│ │ ├── app.scss
│ │ ├── components
│ │ │ ├── base.scss
│ │ │ ├── grid.scss
│ │ │ ├── cropper.scss
│ │ │ ├── toolbar.scss
│ │ │ └── side.scss
│ │ ├── lib.scss
│ │ └── icons.scss
│ ├── lib
│ │ ├── object.js
│ │ ├── color.js
│ │ ├── index.js
│ │ ├── Canvas.js
│ │ ├── number.js
│ │ ├── Keyboard.js
│ │ ├── resamplingImage.js
│ │ ├── util.js
│ │ └── uploader.js
│ ├── actions
│ │ ├── core.js
│ │ ├── index.js
│ │ ├── cropper.js
│ │ ├── side.js
│ │ ├── types.js
│ │ └── body.js
│ ├── API
│ │ ├── index.js
│ │ ├── Cropper.js
│ │ ├── Util.js
│ │ └── Grid.js
│ ├── Container
│ │ ├── Body
│ │ │ ├── index.js
│ │ │ ├── Toolbar
│ │ │ │ ├── Button.js
│ │ │ │ └── EditLayoutSetting.js
│ │ │ └── GridLayout
│ │ │ │ └── index.js
│ │ ├── Side
│ │ │ ├── ToggleSideButton
│ │ │ │ └── index.js
│ │ │ ├── Items
│ │ │ │ ├── Item.js
│ │ │ │ └── index.js
│ │ │ ├── Navigation
│ │ │ │ └── index.js
│ │ │ ├── selectItems.js
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── Cropper
│ │ │ └── index.js
│ ├── reducers
│ │ ├── defaults.js
│ │ ├── index.js
│ │ ├── cropper.js
│ │ ├── core.js
│ │ ├── side.js
│ │ └── body.js
│ └── index.js
├── production
│ └── index.js
└── development
│ ├── index.html
│ ├── index.scss
│ └── util.js
├── babel.config.js
├── lib
├── lib
│ ├── object.js
│ ├── color.js
│ ├── Canvas.js
│ ├── number.js
│ ├── index.js
│ ├── util.js
│ ├── resamplingImage.js
│ ├── uploader.js
│ └── Keyboard.js
├── API
│ ├── index.js
│ └── Cropper.js
├── reducers
│ ├── defaults.js
│ ├── index.js
│ ├── cropper.js
│ ├── side.js
│ └── core.js
├── Container
│ ├── Body
│ │ ├── Toolbar
│ │ │ └── Button.js
│ │ └── index.js
│ └── Side
│ │ ├── selectItems.js
│ │ ├── ToggleSideButton
│ │ └── index.js
│ │ ├── Items
│ │ ├── index.js
│ │ └── Item.js
│ │ └── Navigation
│ │ └── index.js
├── actions
│ ├── core.js
│ ├── cropper.js
│ ├── index.js
│ ├── types.js
│ ├── side.js
│ └── body.js
└── index.js
├── LICENSE
├── package.json
├── webpack.config.js
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | assets/
3 | docs/
4 |
5 | .DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 |
4 | .DS_Store
5 | yarn.lock
--------------------------------------------------------------------------------
/assets/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/logo.jpg
--------------------------------------------------------------------------------
/assets/play_mov.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/play_mov.gif
--------------------------------------------------------------------------------
/assets/screen_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/screen_1.jpg
--------------------------------------------------------------------------------
/assets/screen_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/screen_2.jpg
--------------------------------------------------------------------------------
/assets/screen_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/screen_3.jpg
--------------------------------------------------------------------------------
/assets/screen_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/assets/screen_4.jpg
--------------------------------------------------------------------------------
/docs/build/sp-ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/docs/build/sp-ico.png
--------------------------------------------------------------------------------
/docs/build/bg-space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/docs/build/bg-space.png
--------------------------------------------------------------------------------
/docs/build/sp-ico@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/docs/build/sp-ico@2x.png
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/images/bg-space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/src/PhotoLayoutEditor/style/images/bg-space.png
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/images/sp-ico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/src/PhotoLayoutEditor/style/images/sp-ico.png
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/images/sp-ico@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/redgoose-dev/react-photo-layout-editor/HEAD/src/PhotoLayoutEditor/style/images/sp-ico@2x.png
--------------------------------------------------------------------------------
/src/production/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PhotoLayoutEditor from '../PhotoLayoutEditor';
3 | import '../PhotoLayoutEditor/style/app.scss';
4 |
5 | export default PhotoLayoutEditor;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/object.js:
--------------------------------------------------------------------------------
1 | /**
2 | * is array
3 | *
4 | * @param {Array} arr
5 | * @return {Boolean}
6 | */
7 | export function isArray(arr=[])
8 | {
9 | return !!(arr && arr.length);
10 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/core.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 |
4 | export function init(api, preference, element)
5 | {
6 | return {
7 | type: types.INIT_PLE,
8 | api,
9 | preference,
10 | element,
11 | };
12 | }
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 |
4 | const presets = [
5 | '@babel/preset-env',
6 | '@babel/preset-react',
7 | ];
8 | const plugins = [];
9 |
10 | return {
11 | presets,
12 | plugins,
13 | };
14 | };
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/index.js:
--------------------------------------------------------------------------------
1 | import * as core from './core';
2 | import * as body from './body';
3 | import * as cropper from './cropper';
4 | import * as side from './side';
5 | import * as types from './types';
6 |
7 |
8 | export {
9 | core,
10 | cropper,
11 | side,
12 | body,
13 | types,
14 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/app.scss:
--------------------------------------------------------------------------------
1 | @import '~react-grid-layout/css/styles.css';
2 | @import '~react-simple-colorpicker/src/ColorPicker/index.scss';
3 |
4 | @import "lib";
5 |
6 | @import "components/base";
7 | @import "components/grid";
8 | @import "components/toolbar";
9 | @import "components/side";
10 | @import "components/cropper";
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/cropper.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 |
4 | export function open(key, item)
5 | {
6 | return {
7 | type: types.CROPPER_OPEN,
8 | item,
9 | key
10 | };
11 | }
12 |
13 | export function close(key, position, size)
14 | {
15 | return {
16 | type: types.CROPPER_CLOSE,
17 | key,
18 | position,
19 | size
20 | };
21 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/API/index.js:
--------------------------------------------------------------------------------
1 | import Side from './Side';
2 | import Grid from './Grid';
3 | import Cropper from './Cropper';
4 | import Util from './Util';
5 |
6 |
7 | function API(store) {
8 | this.side = new Side(store);
9 | this.grid = new Grid(store);
10 | this.cropper = new Cropper(store);
11 | this.util = new Util(store);
12 | }
13 |
14 |
15 | export default API;
--------------------------------------------------------------------------------
/lib/lib/object.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.isArray = isArray;
7 |
8 | /**
9 | * is array
10 | *
11 | * @param {Array} arr
12 | * @return {Boolean}
13 | */
14 | function isArray() {
15 | var arr = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
16 | return !!(arr && arr.length);
17 | }
--------------------------------------------------------------------------------
/src/development/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Photo layout editor development page
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/build/PhotoLayoutEditor.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | Copyright (c) 2018 Jed Watson.
3 | Licensed under the MIT License (MIT), see
4 | http://jedwatson.github.io/classnames
5 | */
6 |
7 | /** @license React v16.13.1
8 | * react-is.production.min.js
9 | *
10 | * Copyright (c) Facebook, Inc. and its affiliates.
11 | *
12 | * This source code is licensed under the MIT license found in the
13 | * LICENSE file in the root directory of this source tree.
14 | */
15 |
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/color.js:
--------------------------------------------------------------------------------
1 | /**
2 | * rgba to hex
3 | *
4 | * @param {String} color
5 | * @return {String}
6 | */
7 | export function rgbToHex(color)
8 | {
9 | color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
10 | return (color && color.length === 4) ? "#" +
11 | ("0" + parseInt(color[1],10).toString(16)).slice(-2) +
12 | ("0" + parseInt(color[2],10).toString(16)).slice(-2) +
13 | ("0" + parseInt(color[3],10).toString(16)).slice(-2) : '';
14 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/index.js:
--------------------------------------------------------------------------------
1 | import * as util from './util';
2 | import * as number from './number';
3 | import * as object from './object';
4 | import * as color from './color';
5 | import uploader from './uploader';
6 | import makingImage from './makingImage';
7 | import Canvas from './Canvas';
8 | import resamplingImage from './resamplingImage';
9 |
10 |
11 | export {
12 | util,
13 | number,
14 | color,
15 | object,
16 | uploader,
17 | makingImage,
18 | Canvas,
19 | resamplingImage
20 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Body/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GridLayout from "./GridLayout";
3 | import Toolbar from './Toolbar';
4 |
5 |
6 | class Body extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | render() {
13 | return (
14 |
20 | );
21 | }
22 |
23 | }
24 | Body.displayName = 'Body';
25 |
26 |
27 | export default Body;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/Canvas.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Canvas
3 | */
4 | export default class Canvas {
5 |
6 | /**
7 | * constructor
8 | *
9 | * @param {Number} width
10 | * @param {Number} height
11 | * @param {String} bgColor
12 | */
13 | constructor(width=150, height=100, bgColor='#ffffff')
14 | {
15 | this.el = document.createElement('canvas');
16 | this.ctx = this.el.getContext('2d');
17 |
18 | this.el.width = width;
19 | this.el.height = height;
20 |
21 | this.ctx.fillStyle = bgColor;
22 | this.ctx.fillRect(0, 0, width, height);
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/lib/lib/color.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.rgbToHex = rgbToHex;
7 |
8 | /**
9 | * rgba to hex
10 | *
11 | * @param {String} color
12 | * @return {String}
13 | */
14 | function rgbToHex(color) {
15 | color = color.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);
16 | return color && color.length === 4 ? "#" + ("0" + parseInt(color[1], 10).toString(16)).slice(-2) + ("0" + parseInt(color[2], 10).toString(16)).slice(-2) + ("0" + parseInt(color[3], 10).toString(16)).slice(-2) : '';
17 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Body/Toolbar/Button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 |
5 | const Button = (props) => {
6 | return (
7 |
8 |
9 | {props.title}
10 |
11 | {!!props.children && (
12 |
{props.children}
13 | )}
14 |
15 | );
16 | };
17 |
18 |
19 | export default Button;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/components/base.scss:
--------------------------------------------------------------------------------
1 | /* popup mode */
2 | html.ple-popup-mode {overflow: hidden;}
3 |
4 |
5 | .ple-editor {
6 | position: relative;
7 | min-height: 420px;
8 | min-width: 640px;
9 | *, *:before, *:after {
10 | -webkit-box-sizing: border-box;
11 | -moz-box-sizing: border-box;
12 | box-sizing: border-box;
13 | }
14 |
15 | .ple-container {
16 | margin: 0;
17 | padding: 0;
18 | -webkit-transition: margin-right 1s ease-out;
19 | transition: margin-right .3s ease-out;
20 | }
21 | .ple-body {}
22 |
23 | &.ple-side-active {
24 | .ple-container {
25 | margin-right: $sidebar-width;
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/defaults.js:
--------------------------------------------------------------------------------
1 | export const setting = {
2 | base: {
3 | uploadScript: null,
4 | uploadParamsConvertFunc: null,
5 | updateStoreFunc: null,
6 | callback: {},
7 | },
8 | body: {
9 | setting: {
10 | width: 100,
11 | height: 100,
12 | column: 5,
13 | outerMargin: 10,
14 | innerMargin: 10,
15 | bgColor: 'rgba(255,255,255,1)',
16 | },
17 | blockColor: 'rgba(211,211,211,1)',
18 | grid: [],
19 | },
20 | side: {
21 | files: [],
22 | visible: true,
23 | //progressPercent: null,
24 | },
25 | };
26 |
27 |
28 | export const side = {
29 | files: {},
30 | visible: true,
31 | progressPercent: null,
32 | };
--------------------------------------------------------------------------------
/lib/API/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _Side = _interopRequireDefault(require("./Side"));
9 |
10 | var _Grid = _interopRequireDefault(require("./Grid"));
11 |
12 | var _Cropper = _interopRequireDefault(require("./Cropper"));
13 |
14 | var _Util = _interopRequireDefault(require("./Util"));
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
17 |
18 | function API(store) {
19 | this.side = new _Side["default"](store);
20 | this.grid = new _Grid["default"](store);
21 | this.cropper = new _Cropper["default"](store);
22 | this.util = new _Util["default"](store);
23 | }
24 |
25 | var _default = API;
26 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import * as core from './core';
4 | import * as body from './body';
5 | import side from './side';
6 | import * as cropper from './cropper';
7 |
8 |
9 | export default combineReducers({
10 |
11 | // settings
12 | setting: core.setting,
13 |
14 | // api
15 | api: core.api,
16 |
17 | // keyboard event
18 | keyboard: core.keyboard,
19 |
20 | // element
21 | element: core.element,
22 |
23 | // data tree
24 | tree: combineReducers({
25 | side,
26 | body: combineReducers({
27 | setting: body.setting,
28 | grid: body.grid,
29 | activeBlock: body.activeBlock
30 | }),
31 | cropper: combineReducers({
32 | visible: cropper.visible,
33 | item: cropper.item,
34 | key: cropper.key
35 | })
36 | })
37 |
38 | });
--------------------------------------------------------------------------------
/lib/reducers/defaults.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.side = exports.setting = void 0;
7 | var setting = {
8 | base: {
9 | uploadScript: null,
10 | uploadParamsConvertFunc: null,
11 | updateStoreFunc: null,
12 | callback: {}
13 | },
14 | body: {
15 | setting: {
16 | width: 100,
17 | height: 100,
18 | column: 5,
19 | outerMargin: 10,
20 | innerMargin: 10,
21 | bgColor: 'rgba(255,255,255,1)'
22 | },
23 | blockColor: 'rgba(211,211,211,1)',
24 | grid: []
25 | },
26 | side: {
27 | files: [],
28 | visible: true //progressPercent: null,
29 |
30 | }
31 | };
32 | exports.setting = setting;
33 | var side = {
34 | files: {},
35 | visible: true,
36 | progressPercent: null
37 | };
38 | exports.side = side;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/cropper.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 |
3 |
4 | export function visible(state=false, action)
5 | {
6 | switch(action.type)
7 | {
8 | case types.CROPPER_OPEN:
9 | return true;
10 |
11 | case types.CROPPER_CLOSE:
12 | return false;
13 |
14 | default:
15 | return state;
16 | }
17 | }
18 |
19 | export function item(state=null, action)
20 | {
21 | switch(action.type)
22 | {
23 | case types.CROPPER_OPEN:
24 | return action.item;
25 |
26 | case types.CROPPER_CLOSE:
27 | return null;
28 |
29 | default:
30 | return state;
31 | }
32 | }
33 |
34 | export function key(state=null, action)
35 | {
36 | switch(action.type)
37 | {
38 | case types.CROPPER_OPEN:
39 | return action.key;
40 |
41 | case types.CROPPER_CLOSE:
42 | return null;
43 |
44 | default:
45 | return state;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/ToggleSideButton/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import classNames from 'classnames';
3 |
4 |
5 | class ToggleSideButton extends Component {
6 |
7 | render() {
8 | const { props } = this;
9 |
10 | return (
11 |
12 |
13 |
21 | Toggle sidebar
22 |
23 |
24 |
25 | );
26 | }
27 |
28 | }
29 | ToggleSideButton.displayName = 'ToggleSideButton';
30 | ToggleSideButton.defaultProps = {
31 | show: false,
32 | onClick: function() {}
33 | };
34 |
35 |
36 | export default ToggleSideButton;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/side.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 |
4 | // control visible side bar
5 | export function visible(sw)
6 | {
7 | return {
8 | type: types.SIDE_VISIBLE,
9 | value: sw,
10 | };
11 | }
12 |
13 | // toggle side bar
14 | export function toggle()
15 | {
16 | return {
17 | type: types.SIDE_TOGGLE,
18 | };
19 | }
20 |
21 | export function addFiles(files=[])
22 | {
23 | return {
24 | type: types.SIDE_ADD_FILES,
25 | files,
26 | };
27 | }
28 |
29 | export function removeFiles(keys=[])
30 | {
31 | return {
32 | type: types.SIDE_REMOVE_FILES,
33 | keys,
34 | };
35 | }
36 |
37 | export function updateSelected(value)
38 | {
39 | return {
40 | type: types.SIDE_UPDATE_SELECTED,
41 | value
42 | };
43 | }
44 |
45 | export function updateProgress(percent)
46 | {
47 | return {
48 | type: types.SIDE_UPDATE_PROGRESS,
49 | value: percent
50 | };
51 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/API/Cropper.js:
--------------------------------------------------------------------------------
1 | import * as actions from '../actions';
2 |
3 |
4 | class Cropper {
5 |
6 | constructor(store)
7 | {
8 | this.store = store;
9 | }
10 |
11 | /**
12 | * open cropper
13 | *
14 | * @param {Number} key
15 | */
16 | open(key)
17 | {
18 | const state = this.store.getState();
19 | let item = null;
20 |
21 | try {
22 | item = state.tree.body.grid[key];
23 | if (!item.image) throw 'Not found image in item';
24 | } catch(e) {
25 | alert(e);
26 | return;
27 | }
28 |
29 | this.store.dispatch(actions.cropper.open(key, item));
30 | };
31 |
32 | /**
33 | * close cropper
34 | *
35 | * @param {Number} key
36 | * @param {String} position
37 | * @param {String} size
38 | */
39 | close(key, position, size)
40 | {
41 | this.store.dispatch(actions.cropper.close(key, position, size));
42 | }
43 |
44 | }
45 |
46 |
47 | export default Cropper;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/lib.scss:
--------------------------------------------------------------------------------
1 | // Variables
2 | $font-eng: 'Arial', sans-serif;
3 |
4 | $col-key: #626164;
5 | $col-blur: #999999;
6 | $col-bg-blur: #eeeeee;
7 | $col-active: #20B292;
8 |
9 | $sidebar-width: 240px;
10 | $sidebar-toggle-speed : .3s;
11 |
12 |
13 | // icons
14 | @import "icons";
15 |
16 |
17 | // placeholders
18 | %blind {
19 | position: absolute; overflow: hidden; visibility: hidden;
20 | width: 0; height: 0; font-size: 0; line-height: 0;
21 | }
22 | %icon-close {
23 | position: relative;
24 | display: inline-block;
25 | text-indent: -9999px;
26 | transform: rotate(45deg);
27 | &:before, &:after {
28 | content: ''; position: absolute; display: block;
29 | background: #222;
30 | }
31 | &:before {width: 2px; height: 100%; margin-left: -1px; left: 50%; top: 0;}
32 | &:after {height: 2px; width: 100%; margin-top: -1px; top: 50%; left: 0;}
33 | }
34 | %transparent-background {
35 | background: $col-bg-blur url('./images/bg-space.png') 0 0 repeat;
36 | }
--------------------------------------------------------------------------------
/src/development/index.scss:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 15px;
3 | font-family: sans-serif;
4 | }
5 |
6 | // control
7 | .api-control {
8 | margin: 30px 0 0;
9 | border-top: 1px solid #999;
10 | > section {
11 | margin: 20px 0;
12 | > h1 {
13 | font-size: 20px;
14 | color: #222;
15 | margin: 0 0 5px;
16 | padding: 0 0 0 5px;
17 | }
18 | }
19 |
20 | nav {
21 | font-size: 14px;
22 | padding: 3px 10px;
23 | background: #f1f1f1;
24 | p {
25 | margin: 8px 0;
26 | }
27 | button {
28 | padding: 8px 12px;
29 | margin: 0 5px 4px 0;
30 | font-size: 11px;
31 | background: #999;
32 | font-family: 'Helvetica', Arial, sans-serif;
33 | color: #fff;
34 | font-weight: 400;
35 | border: none;
36 | text-transform: uppercase;
37 | cursor: pointer;
38 | border-radius: 0;
39 | }
40 | }
41 | }
42 |
43 |
44 | #makeImageArea {
45 | margin: 0;
46 | padding: 15px;
47 | min-height: 100px;
48 | border: 1px dashed #20B292;
49 | canvas, img {
50 | display: block;
51 | margin: 0 auto;
52 | border: 1px solid rgba(0,0,0,.05);
53 | }
54 | }
--------------------------------------------------------------------------------
/docs/index.css:
--------------------------------------------------------------------------------
1 | body {margin: 0; font-family: sans-serif;}
2 |
3 | /* header */
4 | .layout-header {padding: 20px 15px 30px;}
5 | .layout-header h1 {margin: 0;}
6 | .layout-header p {margin: 0; font-size: 13px;}
7 |
8 | /* control */
9 | .api-control {margin: 30px 15px 0; border-top: 1px solid #999;}
10 | .api-control > h1 {font-size: 20px; color: #222; margin: 0 0 5px; padding: 0 0 0 5px;}
11 | .api-control nav {font-size: 14px; padding: 3px 10px; background: #f1f1f1;}
12 | .api-control nav p {margin: 8px 0;}
13 | .api-control nav button {
14 | padding: 8px 12px; margin: 0 5px 4px 0;
15 | font-family: 'Helvetica', Arial, sans-serif; font-size: 11px; color: #fff; font-weight: 400;
16 | border: none; text-transform: uppercase; cursor: pointer; background: #999; border-radius: 0;
17 | }
18 | .api-control > section {margin: 20px 0;}
19 |
20 | /* make image */
21 | #makeImageArea {margin: 0; padding: 15px; min-height: 100px; border: 1px dashed #20B292;}
22 | #makeImageArea canvas,
23 | #makeImageArea img {
24 | display: block; margin: 0 auto; border: 1px solid rgba(0,0,0,.05);
25 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createStore } from 'redux';
3 | import { Provider } from 'react-redux';
4 | import Container from './Container';
5 | import reducers from './reducers';
6 | import API from './API';
7 |
8 |
9 | class PhotoLayoutEditor extends React.Component {
10 |
11 | constructor(props)
12 | {
13 | super(props);
14 | // set store
15 | this.store = createStore(reducers);
16 | // set api
17 | this.api = new API(this.store);
18 | }
19 |
20 | render()
21 | {
22 | const { props } = this;
23 |
24 | return (
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | }
32 |
33 | PhotoLayoutEditor.displayName = 'PhotoLayoutEditor';
34 | PhotoLayoutEditor.defaultProps = {
35 | body: {
36 | setting: {},
37 | blockColor: '#dddddd',
38 | grid: []
39 | },
40 | side: {
41 | files: [],
42 | visible: true
43 | },
44 | uploadScript: null,
45 | uploadParamsConvertFunc: null
46 | };
47 |
48 | export default PhotoLayoutEditor;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/types.js:
--------------------------------------------------------------------------------
1 | export const INIT_PLE = 'INIT_PLE';
2 |
3 | export const SIDE_VISIBLE = 'SIDE_VISIBLE';
4 | export const SIDE_ADD_FILES = 'SIDE_ADD_FILES';
5 | export const SIDE_REMOVE_FILES = 'SIDE_REMOVE_FILES';
6 | export const SIDE_UPDATE_SELECTED = 'SIDE_UPDATE_SELECTED';
7 | export const SIDE_TOGGLE = 'SIDE_TOGGLE';
8 | export const SIDE_UPDATE_PROGRESS = 'SIDE_UPDATE_PROGRESS';
9 |
10 | export const GRID_ATTACH_IMAGES = 'GRID_ATTACH_IMAGES';
11 | export const GRID_ATTACH_IMAGE = 'GRID_ATTACH_IMAGE';
12 |
13 | export const GRID_ADD_BLOCK = 'GRID_ADD_BLOCK';
14 | export const GRID_REMOVE_BLOCK = 'GRID_REMOVE_BLOCK';
15 | export const GRID_SHUFFLE_BLOCKS = 'GRID_SHUFFLE_BLOCKS';
16 | export const GRID_DUPLICATE_BLOCK = 'GRID_DUPLICATE_BLOCK';
17 | export const GRID_UPDATE_BLOCKS = 'GRID_UPDATE_BLOCKS';
18 | export const GRID_ACTIVE_BLOCK = 'GRID_ACTIVE_BLOCK';
19 | export const GRID_SETTING_UPDATE = 'GRID_SETTING_UPDATE';
20 | export const GRID_CHANGE_COLOR = 'GRID_CHANGE_COLOR';
21 |
22 | export const CROPPER_OPEN = 'CROPPER_OPEN';
23 | export const CROPPER_CLOSE = 'CROPPER_CLOSE';
--------------------------------------------------------------------------------
/lib/lib/Canvas.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
9 |
10 | /**
11 | * Canvas
12 | */
13 | var Canvas =
14 | /**
15 | * constructor
16 | *
17 | * @param {Number} width
18 | * @param {Number} height
19 | * @param {String} bgColor
20 | */
21 | function Canvas() {
22 | var width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 150;
23 | var height = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100;
24 | var bgColor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '#ffffff';
25 |
26 | _classCallCheck(this, Canvas);
27 |
28 | this.el = document.createElement('canvas');
29 | this.ctx = this.el.getContext('2d');
30 | this.el.width = width;
31 | this.el.height = height;
32 | this.ctx.fillStyle = bgColor;
33 | this.ctx.fillRect(0, 0, width, height);
34 | };
35 |
36 | exports["default"] = Canvas;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Redgoose
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/Container/Body/Toolbar/Button.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _react = _interopRequireDefault(require("react"));
9 |
10 | var _classnames = _interopRequireDefault(require("classnames"));
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
13 |
14 | var Button = function Button(props) {
15 | return /*#__PURE__*/_react["default"].createElement("div", {
16 | className: (0, _classnames["default"])('ple-toolbar__block', props.className)
17 | }, /*#__PURE__*/_react["default"].createElement("button", {
18 | type: "button",
19 | title: props.title,
20 | onClick: props.onClick
21 | }, /*#__PURE__*/_react["default"].createElement("i", {
22 | className: (0, _classnames["default"])('ple-sp-ico', 'ple-abs', props.iconClass)
23 | }, props.title)), !!props.children && /*#__PURE__*/_react["default"].createElement("div", {
24 | className: "ple-toolbar__pop"
25 | }, props.children));
26 | };
27 |
28 | var _default = Button;
29 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/number.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Random range
3 | * min~max 사이의 랜덤 정수를 반환한다.
4 | *
5 | * @param {Number} min
6 | * @param {Number} max
7 | * @param {Boolean} useDecimal
8 | * @return {Number}
9 | */
10 | export function randomRange(min, max, useDecimal=false)
11 | {
12 | if (useDecimal)
13 | {
14 | return Math.random() * (max - min) + min;
15 | }
16 | else
17 | {
18 | return Math.floor(Math.random() * (max - min + 1)) + min;
19 | }
20 | }
21 |
22 |
23 | /**
24 | * get ratio for resize
25 | * max와 min값의 차이의 비율을 가져온다.
26 | *
27 | * @param {Number} min
28 | * @param {Number} max
29 | * @return {Number}
30 | */
31 | export function getRatioForResize(min, max)
32 | {
33 | return (max > min) ? min / max : max / min;
34 | }
35 |
36 |
37 | /**
38 | * Get timestamp
39 | *
40 | * @return {Number}
41 | */
42 | export function getTimeStamp()
43 | {
44 | return Math.round(+new Date()/1000);
45 | }
46 |
47 |
48 | /**
49 | * get ratio
50 | *
51 | * @param {Number} w
52 | * @param {Number} h
53 | * @return {Number}
54 | */
55 | export function getRatio(w, h)
56 | {
57 | let result = parseInt(w) / parseInt(h);
58 | result = Math.round(result * 1000) / 1000;
59 | return result;
60 | }
61 |
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/Items/Item.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 | import * as lib from '../../../lib';
4 |
5 |
6 | class Item extends React.Component {
7 |
8 | render() {
9 | const { props } = this;
10 |
11 | let attr = Object.assign({},
12 | lib.util.isTouchDevice() ? {
13 | onTouchStart: props.onTouchStart,
14 | onTouchMove: props.onTouchMove,
15 | onTouchEnd: props.onTouchEnd
16 | } : {
17 | onDragStart: props.onDragStart,
18 | onDragEnd: props.onDragEnd
19 | });
20 |
21 | return (
22 |
23 |
32 |
33 | );
34 | }
35 | }
36 | Item.displayName = 'Item';
37 | Item.defaultProps = {
38 | image: null, // image
39 | id: null, // id
40 | active: null, // active item
41 | onClick: null, // on click item
42 | onDragStart: null, // on drag start
43 | onDragEnd: null, // on drag end
44 | onTouchStart: null, // on touch start
45 | onTouchMove: null, // on touch move
46 | onTouchEnd: null, // on touch end
47 | };
48 |
49 |
50 | export default Item;
51 |
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/components/grid.scss:
--------------------------------------------------------------------------------
1 | /* Grid */
2 | .ple-grid {
3 | background: #fff;
4 | margin: 0 auto;
5 | box-shadow: 0 2px 4px rgba(0,0,0,.2);
6 | min-height: 40px;
7 | &__wrap {
8 | padding: 30px 30px;
9 | @extend %transparent-background;
10 | }
11 | &__body {
12 | position: relative;
13 | margin: 0 auto;
14 | }
15 | .react-grid-item {
16 | cursor: pointer; overflow: hidden;
17 | &.ple-grid__item-active {
18 | position: relative;
19 | &:after {
20 | content: ''; position: absolute;
21 | left: 0; right: 0; top: 0; bottom: 0;
22 | border: 4px solid $col-active;
23 | pointer-events: none;
24 | }
25 | }
26 | &.ple-grid__item-hover {
27 | position: relative;
28 | &:after {
29 | content: ''; position: absolute;
30 | left: 0; right: 0; top: 0; bottom: 0;
31 | background: $col-active;
32 | opacity: .3;
33 | pointer-events: none;
34 | border: 5px solid red;
35 | }
36 | }
37 | figure {
38 | margin: 0;
39 | width: 100%; height: 100%;
40 | background-repeat: no-repeat;
41 | }
42 | &.react-grid-placeholder {
43 | background: $col-active;
44 | }
45 | & .react-resizable-handle {
46 | &:after {
47 | right: 5px;
48 | bottom: 5px;
49 | width: 8px;
50 | height: 8px;
51 | border-right: 2px solid rgba(0, 0, 0, 0.4);
52 | border-bottom: 2px solid rgba(0, 0, 0, 0.4);
53 | }
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/lib/lib/number.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.randomRange = randomRange;
7 | exports.getRatioForResize = getRatioForResize;
8 | exports.getTimeStamp = getTimeStamp;
9 | exports.getRatio = getRatio;
10 |
11 | /**
12 | * Random range
13 | * min~max 사이의 랜덤 정수를 반환한다.
14 | *
15 | * @param {Number} min
16 | * @param {Number} max
17 | * @param {Boolean} useDecimal
18 | * @return {Number}
19 | */
20 | function randomRange(min, max) {
21 | var useDecimal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
22 |
23 | if (useDecimal) {
24 | return Math.random() * (max - min) + min;
25 | } else {
26 | return Math.floor(Math.random() * (max - min + 1)) + min;
27 | }
28 | }
29 | /**
30 | * get ratio for resize
31 | * max와 min값의 차이의 비율을 가져온다.
32 | *
33 | * @param {Number} min
34 | * @param {Number} max
35 | * @return {Number}
36 | */
37 |
38 |
39 | function getRatioForResize(min, max) {
40 | return max > min ? min / max : max / min;
41 | }
42 | /**
43 | * Get timestamp
44 | *
45 | * @return {Number}
46 | */
47 |
48 |
49 | function getTimeStamp() {
50 | return Math.round(+new Date() / 1000);
51 | }
52 | /**
53 | * get ratio
54 | *
55 | * @param {Number} w
56 | * @param {Number} h
57 | * @return {Number}
58 | */
59 |
60 |
61 | function getRatio(w, h) {
62 | var result = parseInt(w) / parseInt(h);
63 | result = Math.round(result * 1000) / 1000;
64 | return result;
65 | }
--------------------------------------------------------------------------------
/src/development/util.js:
--------------------------------------------------------------------------------
1 | export function pickImages(count=3)
2 | {
3 | const sampleImages = [
4 | 'https://images.unsplash.com/photo-1561271657-fbad0db4caaa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2292&q=60',
5 | 'https://images.unsplash.com/photo-1561214380-cdcaa684cf52?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1400&q=60',
6 | 'https://images.unsplash.com/photo-1561187273-0d2494d76346?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1000&q=60',
7 | 'https://images.unsplash.com/photo-1561266569-ffd2ec021489?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2234&q=60',
8 | 'https://images.unsplash.com/photo-1561087867-203d3c5344d6?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2250&q=60',
9 | 'https://images.unsplash.com/photo-1561148755-03553117df6d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2251&q=60',
10 | 'https://images.unsplash.com/photo-1561155654-20461b26ee4b?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=934&q=60',
11 | 'https://images.unsplash.com/photo-1561084195-ee7372303a19?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2251&q=60',
12 | ];
13 | let images = Object.assign([], sampleImages);
14 | let result = [];
15 |
16 | (function get() {
17 | if (count <= 0) return;
18 | result.push(images.splice(Math.floor(Math.random() * images.length), 1)[0]);
19 | count--;
20 | get();
21 | if (count >= 1) get();
22 | })();
23 |
24 | return result;
25 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/actions/body.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 |
4 | export function addBlock(value)
5 | {
6 | return {
7 | type: types.GRID_ADD_BLOCK,
8 | value: value,
9 | };
10 | }
11 |
12 | export function removeBlock(keys) {
13 | return {
14 | type: types.GRID_REMOVE_BLOCK,
15 | keys,
16 | };
17 | }
18 |
19 | export function shuffleBlocks(options)
20 | {
21 | return {
22 | type: types.GRID_SHUFFLE_BLOCKS,
23 | value: options,
24 | }
25 | }
26 |
27 | export function duplicateBlock(keys)
28 | {
29 | return {
30 | type: types.GRID_DUPLICATE_BLOCK,
31 | keys
32 | };
33 | }
34 |
35 | export function updateBlocks(blocks)
36 | {
37 | return {
38 | type: types.GRID_UPDATE_BLOCKS,
39 | value: blocks,
40 | }
41 | }
42 |
43 | export function activeBlock(keys)
44 | {
45 | return {
46 | type: types.GRID_ACTIVE_BLOCK,
47 | value: keys
48 | };
49 | }
50 |
51 | export function changeColorBlock(keys, color)
52 | {
53 | return {
54 | type: types.GRID_CHANGE_COLOR,
55 | keys,
56 | color,
57 | }
58 | }
59 |
60 | export function updateSetting(value)
61 | {
62 | return {
63 | type: types.GRID_SETTING_UPDATE,
64 | value: value,
65 | };
66 | }
67 |
68 | export function attachImages(images, cols, activeBlocks)
69 | {
70 | return {
71 | type: types.GRID_ATTACH_IMAGES,
72 | value: images,
73 | columns: cols,
74 | activeBlocks: activeBlocks,
75 | }
76 | }
77 |
78 | export function attachImage(keys, image)
79 | {
80 | return {
81 | type: types.GRID_ATTACH_IMAGE,
82 | keys,
83 | image,
84 | };
85 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/Keyboard.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery/dist/jquery.slim';
2 |
3 | import * as libs from '../lib';
4 |
5 |
6 | export default class Keyboard {
7 |
8 | constructor()
9 | {
10 | this.eventName = `PLE_${libs.number.getTimeStamp()}`;
11 | this.code = null;
12 | this.keyName = null;
13 | this.names = {
14 | 17: 'CTRL',
15 | 18: 'ALT',
16 | 91: 'CMD',
17 | 93: 'CMD',
18 | 16: 'SHIFT',
19 | };
20 |
21 | // init key down event
22 | $(window).on(`keydown.${this.eventName}`, (e) => this._keyDown(e));
23 | }
24 |
25 | /**
26 | * apply
27 | *
28 | * @param {Number} code
29 | */
30 | apply(code)
31 | {
32 | this.code = code;
33 | this.keyName = this.names[this.code] || null;
34 | }
35 |
36 | /**
37 | * key down event
38 | *
39 | * @param {Event} e
40 | */
41 | _keyDown(e)
42 | {
43 | // apply keyCode
44 | this.apply(e.keyCode);
45 |
46 | // set events
47 | $(window)
48 | .on(`keyup.${this.eventName}`, () => this._keyUp())
49 | .on(`contextmenu.${this.eventName}`, () => this._keyUp())
50 | .on(`blur.${this.eventName}`, () => this._keyUp())
51 | .off(`keydown.${this.eventName}`);
52 | }
53 |
54 | /**
55 | * key up event
56 | *
57 | */
58 | _keyUp()
59 | {
60 | // apply keyCode
61 | this.apply(null);
62 |
63 | // set events
64 | $(window)
65 | .on(`keydown.${this.eventName}`, (e) => this._keyDown(e))
66 | .off(`contextmenu.${this.eventName} keyup.${this.eventName} blur.${this.eventName}`);
67 | }
68 |
69 | /**
70 | * destroy event
71 | *
72 | */
73 | destroy()
74 | {
75 | $(window).off(`keydown.${this.eventName} contextmenu.${this.eventName} keyup.${this.eventName} blur.${this.eventName}`);
76 | }
77 |
78 | }
--------------------------------------------------------------------------------
/lib/actions/core.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.init = init;
9 |
10 | var types = _interopRequireWildcard(require("./types"));
11 |
12 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
13 |
14 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15 |
16 | function init(api, preference, element) {
17 | return {
18 | type: types.INIT_PLE,
19 | api: api,
20 | preference: preference,
21 | element: element
22 | };
23 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/Items/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Item from './Item';
3 |
4 |
5 | class Items extends React.Component {
6 |
7 | render() {
8 | const { props } = this;
9 |
10 | return (
11 |
12 |
13 |
14 | {Object.keys(props.files).map((o) => {
15 | return (
16 | - props.onSelect(parseInt(o))}
26 | active={props.files[o].active}/>
27 | );
28 | })}
29 | {props.progress !== null && (
30 |
31 |
32 |
35 | {`${props.progress}%`}
36 |
37 |
38 | )}
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | }
46 | Items.displayName = 'Items';
47 | Items.defaultProps = {
48 | files: [], // files
49 | onSelect: (key) => {}, // on select event
50 | onDragStart: null, // on drag start
51 | onDragEnd: null, // on drag end
52 | onTouchStart: null, // on touch start
53 | onTouchMove: null, // on touch move
54 | onTouchEnd: null, // on touch end
55 | progress: null, // on progress number
56 | };
57 |
58 |
59 | export default Items;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/core.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 | import Keyboard from '../lib/Keyboard';
3 | import * as defaults from './defaults';
4 |
5 |
6 | export function setting(state=null, action)
7 | {
8 | let newState = Object.assign({}, state);
9 |
10 | switch (action.type)
11 | {
12 | case types.INIT_PLE:
13 | newState = Object.assign({}, {
14 | base: {
15 | ...defaults.setting.base,
16 | uploadScript: action.preference.uploadScript || defaults.setting.base.uploadScript,
17 | uploadParamsConvertFunc: action.preference.uploadParamsConvertFunc || defaults.setting.base.uploadParamsConvertFunc,
18 | updateStoreFunc: action.preference.updateStoreFunc || defaults.setting.base.updateStoreFunc,
19 | callback: action.preference.callback || defaults.setting.base.callback,
20 | },
21 | side: {
22 | ...defaults.setting.side,
23 | ...action.preference.side
24 | },
25 | body: {
26 | ...defaults.setting.body,
27 | ...action.preference.body,
28 | }
29 | });
30 | return newState;
31 |
32 | default:
33 | return state;
34 | }
35 | }
36 |
37 | export function api(state=null, action)
38 | {
39 | switch (action.type)
40 | {
41 | case types.INIT_PLE:
42 | return action.api;
43 |
44 | default:
45 | return state;
46 | }
47 | }
48 |
49 | export function keyboard(state=null, action)
50 | {
51 | switch (action.type)
52 | {
53 | case types.INIT_PLE:
54 | return new Keyboard();
55 |
56 | default:
57 | return state;
58 | }
59 | }
60 |
61 | export function element(state=null, action)
62 | {
63 | switch (action.type)
64 | {
65 | case types.INIT_PLE:
66 | return action.element;
67 |
68 | default:
69 | return state;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/actions/cropper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.open = open;
9 | exports.close = close;
10 |
11 | var types = _interopRequireWildcard(require("./types"));
12 |
13 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
14 |
15 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
16 |
17 | function open(key, item) {
18 | return {
19 | type: types.CROPPER_OPEN,
20 | item: item,
21 | key: key
22 | };
23 | }
24 |
25 | function close(key, position, size) {
26 | return {
27 | type: types.CROPPER_CLOSE,
28 | key: key,
29 | position: position,
30 | size: size
31 | };
32 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/Navigation/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 |
4 | class SideNavigation extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 |
9 | this.comps = {
10 | inputFile: null,
11 | };
12 | this.state = {
13 | timestamp : Date.now(),
14 | };
15 | }
16 |
17 | /**
18 | * Upload images
19 | *
20 | * @param {Event} e
21 | */
22 | upload(e) {
23 | this.props.onUpload(e.target.files);
24 |
25 | this.setState({
26 | timestamp : Date.now()
27 | });
28 | }
29 |
30 | render() {
31 | const { props, state, comps } = this;
32 |
33 | return (
34 |
35 |
36 |
37 | Moving the image to grid block
38 |
39 |
40 | Toggle all select
41 |
42 |
43 | { comps.inputFile = r; }}
45 | type="file"
46 | onChange={(e) => this.upload(e)} multiple />
47 | upload images
48 |
49 |
50 | remove images
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | }
58 | SideNavigation.displayName = 'Navigation';
59 | SideNavigation.defaultProps = {
60 | onRemove: function() {},
61 | onToggleSelect: function() {},
62 | onAttach: function() {},
63 | onUpload: function() {},
64 | };
65 |
66 |
67 | export default SideNavigation;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/side.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 | import * as defaults from './defaults';
3 |
4 |
5 | let nextFileId = 0;
6 |
7 |
8 | /**
9 | * Reducers
10 | */
11 |
12 | // base
13 | export default function base(state=defaults.side, action)
14 | {
15 | let newState = Object.assign({}, state);
16 | let files = {};
17 |
18 | switch (action.type) {
19 | case types.INIT_PLE:
20 | try {
21 | action.preference.side.files.forEach(o => {
22 | files[nextFileId++] = { image: o, active: false }
23 | });
24 | return {
25 | ...state,
26 | ...action.preference.side,
27 | files: {
28 | ...state.files,
29 | ...files,
30 | },
31 | };
32 | } catch(e) {
33 | return state;
34 | }
35 |
36 | case types.SIDE_VISIBLE:
37 | return {
38 | ...state,
39 | visible: action.value,
40 | };
41 |
42 | case types.SIDE_TOGGLE:
43 | return {
44 | ...state,
45 | visible: !state.visible,
46 | };
47 |
48 | case types.SIDE_ADD_FILES:
49 | action.files.forEach((o) => {
50 | files[nextFileId++] = { image: o, active: false }
51 | });
52 | return {
53 | ...newState,
54 | files: {
55 | ...newState.files,
56 | ...files,
57 | }
58 | };
59 |
60 | case types.SIDE_REMOVE_FILES:
61 | if (!action.keys.length) return state;
62 | action.keys.forEach(o => {
63 | delete newState.files[o];
64 | });
65 | return newState;
66 |
67 | case types.SIDE_UPDATE_SELECTED:
68 | if (!(action.value && Object.keys(action.value).length)) return state;
69 | Object.keys(action.value).forEach(k => {
70 | if (!newState.files[k]) return;
71 | newState.files[k].active = action.value[k].active;
72 | });
73 | return newState;
74 |
75 | case types.SIDE_UPDATE_PROGRESS:
76 | return {
77 | ...state,
78 | progressPercent: action.value
79 | };
80 |
81 | default:
82 | return state;
83 | }
84 | }
--------------------------------------------------------------------------------
/lib/actions/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.types = exports.side = exports.cropper = exports.body = exports.core = void 0;
9 |
10 | var core = _interopRequireWildcard(require("./core"));
11 |
12 | exports.core = core;
13 |
14 | var body = _interopRequireWildcard(require("./body"));
15 |
16 | exports.body = body;
17 |
18 | var cropper = _interopRequireWildcard(require("./cropper"));
19 |
20 | exports.cropper = cropper;
21 |
22 | var side = _interopRequireWildcard(require("./side"));
23 |
24 | exports.side = side;
25 |
26 | var types = _interopRequireWildcard(require("./types"));
27 |
28 | exports.types = types;
29 |
30 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
31 |
32 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import classNames from 'classnames';
4 | import * as actions from '../actions';
5 | import Body from './Body';
6 | import Side from './Side';
7 |
8 |
9 | let ready = false;
10 |
11 |
12 | class Container extends React.Component {
13 |
14 | constructor(props)
15 | {
16 | super(props);
17 | this.el = null;
18 | }
19 |
20 | componentDidUpdate(prevProps, prevState, snapshot)
21 | {
22 | const { props } = this;
23 | if ((!!props.setting.base.updateStoreFunc && typeof props.setting.base.updateStoreFunc === 'function') && (
24 | prevProps.tree.side.visible !== props.tree.side.visible ||
25 | prevProps.tree.side.files !== props.tree.side.files ||
26 | prevProps.tree.body.setting !== props.tree.body.setting ||
27 | prevProps.tree.body.grid !== props.tree.body.grid
28 | )) {
29 | props.setting.base.updateStoreFunc();
30 | }
31 | if (!ready && !!props.setting.base.callback.init && typeof props.setting.base.callback.init === 'function')
32 | {
33 | props.setting.base.callback.init();
34 | ready = true;
35 | }
36 | }
37 |
38 | componentDidMount()
39 | {
40 | const { props } = this;
41 |
42 | props.dispatch(actions.core.init(
43 | props.parent.api,
44 | props.parent.preference || { side: {}, body: {} },
45 | this.el
46 | ));
47 | }
48 |
49 | render()
50 | {
51 | const { props } = this;
52 |
53 | return (
54 | { this.el = r; }}
56 | className={classNames('ple-editor', { 'ple-side-active': props.tree.side.visible })}>
57 | {props.setting && (
58 |
59 |
60 |
61 |
62 | )}
63 |
64 | );
65 | }
66 |
67 | }
68 | Container.displayName = 'Container';
69 | Container.defaultProps = {
70 | parent: {},
71 | dispatch: null,
72 | tree: {},
73 | setting: null,
74 | };
75 |
76 |
77 | export default connect((state) => Object.assign({}, state, {}))(Container);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-photo-layout-editor",
3 | "version": "1.2.8",
4 | "description": "Photo layout editor for react",
5 | "main": "lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/redgoose-dev/react-photo-layout-editor.git"
9 | },
10 | "author": {
11 | "name": "redgoose",
12 | "email": "scripter@me.com",
13 | "url": "https://redgoose.me"
14 | },
15 | "license": "MIT",
16 | "keywords": [
17 | "Photo",
18 | "layout",
19 | "editor",
20 | "redgoose",
21 | "goose"
22 | ],
23 | "bugs": {
24 | "url": "https://github.com/redgoose-dev/react-photo-layout-editor/issues"
25 | },
26 | "homepage": "https://github.com/redgoose-dev/react-photo-layout-editor",
27 | "scripts": {
28 | "dev": "webpack serve --inline --progress --mode development",
29 | "prebuild": "rm -rf docs/build/ && rm -rf build/ && babel ./src/PhotoLayoutEditor --out-dir ./lib",
30 | "build": "webpack --mode production",
31 | "postbuild": "cp -r build/ docs/build/",
32 | "version-patch": "npm version patch"
33 | },
34 | "dependencies": {
35 | "classnames": "^2.3.1",
36 | "jquery": "^3.6.0",
37 | "prop-types": "^15.7.2",
38 | "react": "^17.0.2",
39 | "react-dom": "^17.0.2",
40 | "react-grid-layout": "^1.2.4",
41 | "react-redux": "^7.2.3",
42 | "react-simple-colorpicker": "github:redgoose-dev/react-simple-colorpicker",
43 | "redux": "^4.0.5"
44 | },
45 | "devDependencies": {
46 | "@babel/cli": "^7.13.14",
47 | "@babel/core": "^7.13.15",
48 | "@babel/preset-env": "^7.13.15",
49 | "@babel/preset-react": "^7.13.13",
50 | "babel-loader": "^8.2.2",
51 | "css-loader": "^5.2.1",
52 | "css-minimizer-webpack-plugin": "^2.0.0",
53 | "file-loader": "^6.2.0",
54 | "html-loader": "^2.1.2",
55 | "html-webpack-plugin": "^5.3.1",
56 | "mini-css-extract-plugin": "^1.4.1",
57 | "node-sass": "^5.0.0",
58 | "optimize-css-assets-webpack-plugin": "^5.0.4",
59 | "sass-loader": "^10.1.1",
60 | "terser-webpack-plugin": "^5.1.1",
61 | "webpack": "^5.31.2",
62 | "webpack-cli": "^4.6.0",
63 | "webpack-dev-server": "^3.11.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/lib/actions/types.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.CROPPER_CLOSE = exports.CROPPER_OPEN = exports.GRID_CHANGE_COLOR = exports.GRID_SETTING_UPDATE = exports.GRID_ACTIVE_BLOCK = exports.GRID_UPDATE_BLOCKS = exports.GRID_DUPLICATE_BLOCK = exports.GRID_SHUFFLE_BLOCKS = exports.GRID_REMOVE_BLOCK = exports.GRID_ADD_BLOCK = exports.GRID_ATTACH_IMAGE = exports.GRID_ATTACH_IMAGES = exports.SIDE_UPDATE_PROGRESS = exports.SIDE_TOGGLE = exports.SIDE_UPDATE_SELECTED = exports.SIDE_REMOVE_FILES = exports.SIDE_ADD_FILES = exports.SIDE_VISIBLE = exports.INIT_PLE = void 0;
7 | var INIT_PLE = 'INIT_PLE';
8 | exports.INIT_PLE = INIT_PLE;
9 | var SIDE_VISIBLE = 'SIDE_VISIBLE';
10 | exports.SIDE_VISIBLE = SIDE_VISIBLE;
11 | var SIDE_ADD_FILES = 'SIDE_ADD_FILES';
12 | exports.SIDE_ADD_FILES = SIDE_ADD_FILES;
13 | var SIDE_REMOVE_FILES = 'SIDE_REMOVE_FILES';
14 | exports.SIDE_REMOVE_FILES = SIDE_REMOVE_FILES;
15 | var SIDE_UPDATE_SELECTED = 'SIDE_UPDATE_SELECTED';
16 | exports.SIDE_UPDATE_SELECTED = SIDE_UPDATE_SELECTED;
17 | var SIDE_TOGGLE = 'SIDE_TOGGLE';
18 | exports.SIDE_TOGGLE = SIDE_TOGGLE;
19 | var SIDE_UPDATE_PROGRESS = 'SIDE_UPDATE_PROGRESS';
20 | exports.SIDE_UPDATE_PROGRESS = SIDE_UPDATE_PROGRESS;
21 | var GRID_ATTACH_IMAGES = 'GRID_ATTACH_IMAGES';
22 | exports.GRID_ATTACH_IMAGES = GRID_ATTACH_IMAGES;
23 | var GRID_ATTACH_IMAGE = 'GRID_ATTACH_IMAGE';
24 | exports.GRID_ATTACH_IMAGE = GRID_ATTACH_IMAGE;
25 | var GRID_ADD_BLOCK = 'GRID_ADD_BLOCK';
26 | exports.GRID_ADD_BLOCK = GRID_ADD_BLOCK;
27 | var GRID_REMOVE_BLOCK = 'GRID_REMOVE_BLOCK';
28 | exports.GRID_REMOVE_BLOCK = GRID_REMOVE_BLOCK;
29 | var GRID_SHUFFLE_BLOCKS = 'GRID_SHUFFLE_BLOCKS';
30 | exports.GRID_SHUFFLE_BLOCKS = GRID_SHUFFLE_BLOCKS;
31 | var GRID_DUPLICATE_BLOCK = 'GRID_DUPLICATE_BLOCK';
32 | exports.GRID_DUPLICATE_BLOCK = GRID_DUPLICATE_BLOCK;
33 | var GRID_UPDATE_BLOCKS = 'GRID_UPDATE_BLOCKS';
34 | exports.GRID_UPDATE_BLOCKS = GRID_UPDATE_BLOCKS;
35 | var GRID_ACTIVE_BLOCK = 'GRID_ACTIVE_BLOCK';
36 | exports.GRID_ACTIVE_BLOCK = GRID_ACTIVE_BLOCK;
37 | var GRID_SETTING_UPDATE = 'GRID_SETTING_UPDATE';
38 | exports.GRID_SETTING_UPDATE = GRID_SETTING_UPDATE;
39 | var GRID_CHANGE_COLOR = 'GRID_CHANGE_COLOR';
40 | exports.GRID_CHANGE_COLOR = GRID_CHANGE_COLOR;
41 | var CROPPER_OPEN = 'CROPPER_OPEN';
42 | exports.CROPPER_OPEN = CROPPER_OPEN;
43 | var CROPPER_CLOSE = 'CROPPER_CLOSE';
44 | exports.CROPPER_CLOSE = CROPPER_CLOSE;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/selectItems.js:
--------------------------------------------------------------------------------
1 | let firstSelectIdx = null;
2 |
3 |
4 | /**
5 | * Change active
6 | *
7 | * @param {Number} key
8 | * @param {Object} item
9 | * @param {Number} start
10 | * @param {Number} end
11 | * @param {String} type press key name and select type
12 | * @return {Object}
13 | */
14 | function changeActive(key, item, start, end, type)
15 | {
16 | switch(type)
17 | {
18 | case 'add':
19 | return {
20 | key: key,
21 | active: (key === end) ? !item.active : item.active
22 | };
23 | case 'range':
24 | start = start || 0;
25 | if (start < end)
26 | {
27 | return {
28 | key: key,
29 | active: key >= start && key <= end
30 | };
31 | }
32 | else
33 | {
34 | return {
35 | key: key,
36 | active: key <= start && key >= end
37 | };
38 | }
39 | return item;
40 | }
41 |
42 | // not found type
43 | if (key === end)
44 | {
45 | return { key: key, active: !item.active };
46 | }
47 | else
48 | {
49 | return { key: key, active: false };
50 | }
51 | }
52 |
53 |
54 | /**
55 | * select items
56 | *
57 | * @param {Object} props
58 | * @param {Number} key
59 | * @return {Object}
60 | */
61 | function selectItems(props, key)
62 | {
63 | const { keyName } = props.keyboard;
64 | const { files } = props.tree.side;
65 | let type = null;
66 | let selected = false;
67 | let items = {};
68 |
69 | if (keyName !== 'SHIFT')
70 | {
71 | let currentItem = null;
72 | Object.keys(props.tree.side.files).forEach(k => {
73 | let obj = props.tree.side.files[k];
74 | if (parseInt(k) === key)
75 | {
76 | currentItem = obj;
77 | return false;
78 | }
79 | });
80 | firstSelectIdx = (currentItem.active === true) ? null : key;
81 | if (!firstSelectIdx && currentItem.active === true)
82 | {
83 | firstSelectIdx = currentItem.key;
84 | }
85 | }
86 |
87 | switch (keyName) {
88 | case 'CTRL':
89 | case 'CMD':
90 | type = 'add';
91 | break;
92 | case 'SHIFT':
93 | type = 'range';
94 | }
95 |
96 | Object.keys(files).forEach(k => {
97 | let obj = changeActive(parseInt(k), files[k], firstSelectIdx, key, type);
98 | if (obj.active)
99 | {
100 | selected = true;
101 | }
102 | items[k] = obj;
103 | });
104 |
105 | if (!selected)
106 | {
107 | firstSelectIdx = null;
108 | }
109 |
110 | return items;
111 | }
112 |
113 |
114 | export default selectItems;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/icons.scss:
--------------------------------------------------------------------------------
1 | // init icon
2 | %sp-ico {
3 | background: transparent url('./images/sp-ico.png') no-repeat;
4 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 1.33dppx) {
5 | background-image: url('./images/sp-ico@2x.png');
6 | background-size: 200px 100px;
7 | }
8 | }
9 | .ple-sp-ico {
10 | display: inline-block; text-align: left; text-indent: -9999px; overflow: hidden;
11 | @extend %sp-ico;
12 | }
13 |
14 |
15 | @mixin sp-size($width, $height) {
16 | width: $width; height: $height;
17 | &.ple-abs {margin-left: -#{$width/2}; margin-top: -#{$height/2};}
18 | }
19 |
20 |
21 | // set icon classes
22 | .ple-ico-reply {
23 | background-position: 0 0;
24 | @include sp-size(16px, 13px);
25 | }
26 | .ple-ico-select {
27 | background-position: -26px 0;
28 | @include sp-size(16px, 16px);
29 | }
30 | .ple-ico-upload {
31 | background-position: -52px 0;
32 | @include sp-size(12px, 15px);
33 | }
34 | .ple-ico-trash {
35 | background-position: -74px 0;
36 | @include sp-size(12px, 16px);
37 | }
38 | .ple-ico-arrow-left {
39 | background-position: -96px 0;
40 | @include sp-size(7px, 12px);
41 | }
42 | .ple-ico-arrow-right {
43 | background-position: -112px 0;
44 | @include sp-size(7px, 12px);
45 | }
46 | .ple-ico-empty {
47 | background-position: -130px 0;
48 | @include sp-size(16px, 16px);
49 | }
50 | .ple-ico-palette {
51 | background-position: -156px 0;
52 | @include sp-size(18px, 18px);
53 | }
54 |
55 | .ple-ico-setting {
56 | background-position: 0 -36px;
57 | @include sp-size(16px, 16px);
58 | }
59 | .ple-ico-arrow-random {
60 | background-position: -26px -36px;
61 | @include sp-size(16px, 16px);
62 | }
63 | .ple-ico-plus {
64 | background-position: -52px -36px;
65 | @include sp-size(16px, 16px);
66 | }
67 | .ple-ico-pencil {
68 | background-position: -78px -36px;
69 | @include sp-size(16px, 16px);
70 | }
71 | .ple-ico-duplicate {
72 | background-position: -104px -36px;
73 | @include sp-size(14px, 16px);
74 | }
75 | .ple-ico-close {
76 | background-position: -128px -36px;
77 | @include sp-size(14px, 14px);
78 | }
79 | .ple-ico-resize {
80 | background-position: -152px -36px;
81 | @include sp-size(14px, 14px);
82 | }
83 | .ple-ico-reduction {
84 | background-position: -176px -36px;
85 | @include sp-size(16px, 16px);
86 | }
87 |
88 | .ple-ico-clamp {
89 | background-position: 0 -72px;
90 | @include sp-size(20px, 20px);
91 | }
92 | .ple-ico-close2 {
93 | background-position: -30px -72px;
94 | @include sp-size(24px, 24px);
95 | }
--------------------------------------------------------------------------------
/lib/reducers/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _redux = require("redux");
11 |
12 | var core = _interopRequireWildcard(require("./core"));
13 |
14 | var body = _interopRequireWildcard(require("./body"));
15 |
16 | var _side = _interopRequireDefault(require("./side"));
17 |
18 | var cropper = _interopRequireWildcard(require("./cropper"));
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
21 |
22 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
23 |
24 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25 |
26 | var _default = (0, _redux.combineReducers)({
27 | // settings
28 | setting: core.setting,
29 | // api
30 | api: core.api,
31 | // keyboard event
32 | keyboard: core.keyboard,
33 | // element
34 | element: core.element,
35 | // data tree
36 | tree: (0, _redux.combineReducers)({
37 | side: _side["default"],
38 | body: (0, _redux.combineReducers)({
39 | setting: body.setting,
40 | grid: body.grid,
41 | activeBlock: body.activeBlock
42 | }),
43 | cropper: (0, _redux.combineReducers)({
44 | visible: cropper.visible,
45 | item: cropper.item,
46 | key: cropper.key
47 | })
48 | })
49 | });
50 |
51 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/resamplingImage.js:
--------------------------------------------------------------------------------
1 | import * as lib from './';
2 |
3 | // default options
4 | const defaultOptions = {
5 | image: null,
6 | width: 100,
7 | height: 100,
8 | reSampleCount: 1,
9 | bgColor: 'rgba(255,255,255,1)',
10 | cw: 0,
11 | ch: 0,
12 | cx: 0,
13 | cy: 0,
14 | dw: 0,
15 | dh: 0,
16 | dx: 0,
17 | dy: 0,
18 | };
19 |
20 |
21 | /**
22 | * resize
23 | *
24 | * @param {Object} options
25 | * @param {Number} count
26 | * @param {Canvas} parentCanvas
27 | * @return {Promise}
28 | */
29 | function resize(options, count, parentCanvas)
30 | {
31 | return new Promise(resolve => {
32 | function func(count, parentCanvas)
33 | {
34 | const max = Math.pow(2, count);
35 | let canvasForResize = new lib.Canvas(
36 | options.width * max,
37 | options.height * max,
38 | options.bgColor
39 | );
40 |
41 | canvasForResize.ctx.drawImage(
42 | parentCanvas.el,
43 | 0,
44 | 0,
45 | parentCanvas.el.width * 0.5,
46 | parentCanvas.el.height * 0.5
47 | );
48 |
49 | if (count > 0)
50 | {
51 | func(count - 1, canvasForResize);
52 | }
53 | else
54 | {
55 | resolve(canvasForResize);
56 | }
57 | }
58 |
59 | func(count - 1, parentCanvas);
60 | });
61 | }
62 |
63 |
64 | /**
65 | * resampling image
66 | *
67 | * @param {Object} options
68 | * @return {Promise}
69 | */
70 | export default function resamplingImage(options)
71 | {
72 | // assign options
73 | options = Object.assign({}, defaultOptions, options);
74 |
75 | return new Promise((resolve, reject) => {
76 | if (!options.image) reject('not found image');
77 |
78 | // set reSample count
79 | options.reSampleCount = Math.min(4, options.reSampleCount);
80 | options.reSampleCount = Math.max(0, options.reSampleCount);
81 | const reSampleMax = Math.pow(2, options.reSampleCount);
82 |
83 | // set canvas
84 | const canvas = new lib.Canvas(
85 | options.width * reSampleMax,
86 | options.height * reSampleMax,
87 | options.bgColor
88 | );
89 |
90 | canvas.ctx.drawImage(
91 | options.image,
92 | options.cx, // cx
93 | options.cy, // cy
94 | options.cw, // cw
95 | options.ch, // ch
96 | options.dx * reSampleMax, // dx
97 | options.dy * reSampleMax, // dy
98 | options.dw * reSampleMax, // dw
99 | options.dh * reSampleMax, // dh
100 | );
101 |
102 | if (options.reSampleCount > 0)
103 | {
104 | resize(options, options.reSampleCount, canvas).then(resolve);
105 | }
106 | else
107 | {
108 | resolve(canvas);
109 | }
110 | });
111 |
112 | }
--------------------------------------------------------------------------------
/lib/actions/side.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.visible = visible;
9 | exports.toggle = toggle;
10 | exports.addFiles = addFiles;
11 | exports.removeFiles = removeFiles;
12 | exports.updateSelected = updateSelected;
13 | exports.updateProgress = updateProgress;
14 |
15 | var types = _interopRequireWildcard(require("./types"));
16 |
17 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
18 |
19 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
20 |
21 | // control visible side bar
22 | function visible(sw) {
23 | return {
24 | type: types.SIDE_VISIBLE,
25 | value: sw
26 | };
27 | } // toggle side bar
28 |
29 |
30 | function toggle() {
31 | return {
32 | type: types.SIDE_TOGGLE
33 | };
34 | }
35 |
36 | function addFiles() {
37 | var files = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
38 | return {
39 | type: types.SIDE_ADD_FILES,
40 | files: files
41 | };
42 | }
43 |
44 | function removeFiles() {
45 | var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
46 | return {
47 | type: types.SIDE_REMOVE_FILES,
48 | keys: keys
49 | };
50 | }
51 |
52 | function updateSelected(value) {
53 | return {
54 | type: types.SIDE_UPDATE_SELECTED,
55 | value: value
56 | };
57 | }
58 |
59 | function updateProgress(percent) {
60 | return {
61 | type: types.SIDE_UPDATE_PROGRESS,
62 | value: percent
63 | };
64 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * is touch device
3 | *
4 | * @returns {boolean}
5 | */
6 | export function isTouchDevice()
7 | {
8 | return (
9 | ('ontouchstart' in window) ||
10 | (navigator.MaxTouchPoints > 0) ||
11 | (navigator.msMaxTouchPoints > 0)
12 | );
13 | }
14 |
15 |
16 | /**
17 | * Sleep
18 | *
19 | * @param {Number} time
20 | * @param {String} id
21 | * @return {Promise}
22 | */
23 | export function sleep(time, id='pleTimer')
24 | {
25 | return new Promise((resolve) => {
26 | window[id] = setTimeout(resolve, time);
27 | });
28 | }
29 |
30 |
31 | /**
32 | * Get image size
33 | *
34 | * @param {String} src
35 | * @return {Promise}
36 | */
37 | export function getImageSize(src)
38 | {
39 | return new Promise((resolve, reject) => {
40 | if (!(src && typeof src === 'string')) reject();
41 |
42 | let img = document.createElement('img');
43 |
44 | img.onload = function()
45 | {
46 | resolve({
47 | width: img.naturalWidth,
48 | height: img.naturalHeight,
49 | ratio: img.naturalHeight / img.naturalWidth,
50 | });
51 | };
52 |
53 | img.onerror = function()
54 | {
55 | reject();
56 | };
57 |
58 | img.src = src;
59 | });
60 | }
61 |
62 |
63 | /**
64 | * get css prefix
65 | *
66 | * @variation {Object}
67 | */
68 | export const cssPrefix = (function()
69 | {
70 | const styles = window.getComputedStyle(document.documentElement, '');
71 | const pre = (Array.prototype.slice
72 | .call(styles)
73 | .join('')
74 | .match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
75 | )[1];
76 | const dom = ('WebKit|Moz|MS|O').match(new RegExp('(' + pre + ')', 'i'))[1];
77 |
78 | return {
79 | dom: dom,
80 | lowercase: pre,
81 | css: `-${pre}-`,
82 | js: pre[0].toUpperCase() + pre.substr(1)
83 | };
84 | })();
85 |
86 |
87 | /**
88 | * check support css
89 | *
90 | * @param {String} key
91 | * @param {String} value
92 | * @return {Boolean}
93 | */
94 | export function checkSupportCss(key, value)
95 | {
96 | if (CSS && CSS.supports)
97 | {
98 | return CSS.supports(key, value);
99 | }
100 | }
101 |
102 | /**
103 | * load image
104 | *
105 | * @param {String} src
106 | * @return {Promise}
107 | */
108 | export function loadImage(src)
109 | {
110 | return new Promise((resolve) => {
111 | if (!src)
112 | {
113 | resolve(null);
114 | return;
115 | }
116 |
117 | let image = new Image();
118 |
119 | image.onload = function(e)
120 | {
121 | resolve(image);
122 | };
123 | image.onError = function(e)
124 | {
125 | resolve(null);
126 | };
127 |
128 | image.crossOrigin = 'Anonymous';
129 | image.src = src;
130 | });
131 | }
--------------------------------------------------------------------------------
/lib/Container/Side/selectItems.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 | var firstSelectIdx = null;
8 | /**
9 | * Change active
10 | *
11 | * @param {Number} key
12 | * @param {Object} item
13 | * @param {Number} start
14 | * @param {Number} end
15 | * @param {String} type press key name and select type
16 | * @return {Object}
17 | */
18 |
19 | function changeActive(key, item, start, end, type) {
20 | switch (type) {
21 | case 'add':
22 | return {
23 | key: key,
24 | active: key === end ? !item.active : item.active
25 | };
26 |
27 | case 'range':
28 | start = start || 0;
29 |
30 | if (start < end) {
31 | return {
32 | key: key,
33 | active: key >= start && key <= end
34 | };
35 | } else {
36 | return {
37 | key: key,
38 | active: key <= start && key >= end
39 | };
40 | }
41 |
42 | return item;
43 | } // not found type
44 |
45 |
46 | if (key === end) {
47 | return {
48 | key: key,
49 | active: !item.active
50 | };
51 | } else {
52 | return {
53 | key: key,
54 | active: false
55 | };
56 | }
57 | }
58 | /**
59 | * select items
60 | *
61 | * @param {Object} props
62 | * @param {Number} key
63 | * @return {Object}
64 | */
65 |
66 |
67 | function selectItems(props, key) {
68 | var keyName = props.keyboard.keyName;
69 | var files = props.tree.side.files;
70 | var type = null;
71 | var selected = false;
72 | var items = {};
73 |
74 | if (keyName !== 'SHIFT') {
75 | var currentItem = null;
76 | Object.keys(props.tree.side.files).forEach(function (k) {
77 | var obj = props.tree.side.files[k];
78 |
79 | if (parseInt(k) === key) {
80 | currentItem = obj;
81 | return false;
82 | }
83 | });
84 | firstSelectIdx = currentItem.active === true ? null : key;
85 |
86 | if (!firstSelectIdx && currentItem.active === true) {
87 | firstSelectIdx = currentItem.key;
88 | }
89 | }
90 |
91 | switch (keyName) {
92 | case 'CTRL':
93 | case 'CMD':
94 | type = 'add';
95 | break;
96 |
97 | case 'SHIFT':
98 | type = 'range';
99 | }
100 |
101 | Object.keys(files).forEach(function (k) {
102 | var obj = changeActive(parseInt(k), files[k], firstSelectIdx, key, type);
103 |
104 | if (obj.active) {
105 | selected = true;
106 | }
107 |
108 | items[k] = obj;
109 | });
110 |
111 | if (!selected) {
112 | firstSelectIdx = null;
113 | }
114 |
115 | return items;
116 | }
117 |
118 | var _default = selectItems;
119 | exports["default"] = _default;
--------------------------------------------------------------------------------
/lib/reducers/cropper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.visible = visible;
9 | exports.item = item;
10 | exports.key = key;
11 |
12 | var types = _interopRequireWildcard(require("../actions/types"));
13 |
14 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
15 |
16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
17 |
18 | function visible() {
19 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
20 | var action = arguments.length > 1 ? arguments[1] : undefined;
21 |
22 | switch (action.type) {
23 | case types.CROPPER_OPEN:
24 | return true;
25 |
26 | case types.CROPPER_CLOSE:
27 | return false;
28 |
29 | default:
30 | return state;
31 | }
32 | }
33 |
34 | function item() {
35 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
36 | var action = arguments.length > 1 ? arguments[1] : undefined;
37 |
38 | switch (action.type) {
39 | case types.CROPPER_OPEN:
40 | return action.item;
41 |
42 | case types.CROPPER_CLOSE:
43 | return null;
44 |
45 | default:
46 | return state;
47 | }
48 | }
49 |
50 | function key() {
51 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
52 | var action = arguments.length > 1 ? arguments[1] : undefined;
53 |
54 | switch (action.type) {
55 | case types.CROPPER_OPEN:
56 | return action.key;
57 |
58 | case types.CROPPER_CLOSE:
59 | return null;
60 |
61 | default:
62 | return state;
63 | }
64 | }
--------------------------------------------------------------------------------
/lib/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | Object.defineProperty(exports, "uploader", {
9 | enumerable: true,
10 | get: function get() {
11 | return _uploader["default"];
12 | }
13 | });
14 | Object.defineProperty(exports, "makingImage", {
15 | enumerable: true,
16 | get: function get() {
17 | return _makingImage["default"];
18 | }
19 | });
20 | Object.defineProperty(exports, "Canvas", {
21 | enumerable: true,
22 | get: function get() {
23 | return _Canvas["default"];
24 | }
25 | });
26 | Object.defineProperty(exports, "resamplingImage", {
27 | enumerable: true,
28 | get: function get() {
29 | return _resamplingImage["default"];
30 | }
31 | });
32 | exports.color = exports.object = exports.number = exports.util = void 0;
33 |
34 | var util = _interopRequireWildcard(require("./util"));
35 |
36 | exports.util = util;
37 |
38 | var number = _interopRequireWildcard(require("./number"));
39 |
40 | exports.number = number;
41 |
42 | var object = _interopRequireWildcard(require("./object"));
43 |
44 | exports.object = object;
45 |
46 | var color = _interopRequireWildcard(require("./color"));
47 |
48 | exports.color = color;
49 |
50 | var _uploader = _interopRequireDefault(require("./uploader"));
51 |
52 | var _makingImage = _interopRequireDefault(require("./makingImage"));
53 |
54 | var _Canvas = _interopRequireDefault(require("./Canvas"));
55 |
56 | var _resamplingImage = _interopRequireDefault(require("./resamplingImage"));
57 |
58 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
59 |
60 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
61 |
62 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/lib/uploader.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery/dist/jquery.slim';
2 |
3 |
4 | /**
5 | * local upload
6 | *
7 | * @param {File} file
8 | * @return {Promise}
9 | */
10 | function local(file)
11 | {
12 | const defer = $.Deferred();
13 | const reader = new FileReader();
14 |
15 | reader.addEventListener('load', function(e) {
16 | defer.resolve({
17 | name: file.name,
18 | type: file.type,
19 | size: file.size,
20 | url: e.target.result,
21 | });
22 | });
23 | reader.addEventListener('error', function(e) {
24 | defer.reject(e.target);
25 | });
26 |
27 | reader.readAsDataURL(file);
28 |
29 | return defer.promise();
30 | }
31 |
32 | /**
33 | * external upload
34 | *
35 | * @param {String} script
36 | * @param {File} file
37 | * @return {Promise}
38 | */
39 | function external(script, file)
40 | {
41 | const defer = $.Deferred();
42 | const xhr = new XMLHttpRequest();
43 |
44 | if (!(typeof FormData === 'function' || typeof FormData === 'object'))
45 | {
46 | defer.reject('not support browser', file);
47 | return null;
48 | }
49 | const formData = new FormData();
50 |
51 | function onLoad(e)
52 | {
53 | try {
54 | const result = JSON.parse(e.target.responseText);
55 | if (result.state === 'success')
56 | {
57 | defer.resolve(result.data);
58 | }
59 | else
60 | {
61 | throw 'server error';
62 | }
63 | } catch(e) {
64 | defer.reject(e);
65 | }
66 | }
67 |
68 | function onProgress(event)
69 | {
70 | const { loaded, total } = event;
71 | defer.notify('progress', loaded, total);
72 | }
73 |
74 | formData.append('files[]', file);
75 | xhr.open('post', script, true);
76 |
77 | xhr.addEventListener('load', onLoad);
78 | xhr.upload.addEventListener('progress', onProgress);
79 |
80 | xhr.send(formData);
81 |
82 | // start queue
83 | defer.notify('start');
84 |
85 | return defer.promise();
86 | }
87 |
88 |
89 | /**
90 | * upload multiple files
91 | *
92 | */
93 | export default function uploader(files, script)
94 | {
95 | const defer = $.Deferred();
96 | const total = files.length;
97 | let count = 0;
98 |
99 | function upload()
100 | {
101 | if (count >= total)
102 | {
103 | defer.resolve();
104 | return;
105 | }
106 |
107 | if (script)
108 | {
109 | external(script, files[count])
110 | .done((data) => {
111 | defer.notify('done', { count, data });
112 | count++;
113 | upload();
114 | })
115 | .progress((state, loaded, total) => {
116 | defer.notify(
117 | state,
118 | state === 'progress' && { loaded, total },
119 | );
120 | })
121 | .fail((error) => {
122 | defer.reject(error);
123 | });
124 | }
125 | else
126 | {
127 | local(files[count])
128 | .done((data) => {
129 | defer.notify('done', { count, data });
130 | count++;
131 | upload();
132 | })
133 | .fail(() => {});
134 | }
135 | }
136 |
137 | upload(0);
138 |
139 | return defer.promise();
140 | }
--------------------------------------------------------------------------------
/lib/lib/util.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.isTouchDevice = isTouchDevice;
7 | exports.sleep = sleep;
8 | exports.getImageSize = getImageSize;
9 | exports.checkSupportCss = checkSupportCss;
10 | exports.loadImage = loadImage;
11 | exports.cssPrefix = void 0;
12 |
13 | /**
14 | * is touch device
15 | *
16 | * @returns {boolean}
17 | */
18 | function isTouchDevice() {
19 | return 'ontouchstart' in window || navigator.MaxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
20 | }
21 | /**
22 | * Sleep
23 | *
24 | * @param {Number} time
25 | * @param {String} id
26 | * @return {Promise}
27 | */
28 |
29 |
30 | function sleep(time) {
31 | var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'pleTimer';
32 | return new Promise(function (resolve) {
33 | window[id] = setTimeout(resolve, time);
34 | });
35 | }
36 | /**
37 | * Get image size
38 | *
39 | * @param {String} src
40 | * @return {Promise}
41 | */
42 |
43 |
44 | function getImageSize(src) {
45 | return new Promise(function (resolve, reject) {
46 | if (!(src && typeof src === 'string')) reject();
47 | var img = document.createElement('img');
48 |
49 | img.onload = function () {
50 | resolve({
51 | width: img.naturalWidth,
52 | height: img.naturalHeight,
53 | ratio: img.naturalHeight / img.naturalWidth
54 | });
55 | };
56 |
57 | img.onerror = function () {
58 | reject();
59 | };
60 |
61 | img.src = src;
62 | });
63 | }
64 | /**
65 | * get css prefix
66 | *
67 | * @variation {Object}
68 | */
69 |
70 |
71 | var cssPrefix = function () {
72 | var styles = window.getComputedStyle(document.documentElement, '');
73 | var pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || styles.OLink === '' && ['', 'o'])[1];
74 | var dom = 'WebKit|Moz|MS|O'.match(new RegExp('(' + pre + ')', 'i'))[1];
75 | return {
76 | dom: dom,
77 | lowercase: pre,
78 | css: "-".concat(pre, "-"),
79 | js: pre[0].toUpperCase() + pre.substr(1)
80 | };
81 | }();
82 | /**
83 | * check support css
84 | *
85 | * @param {String} key
86 | * @param {String} value
87 | * @return {Boolean}
88 | */
89 |
90 |
91 | exports.cssPrefix = cssPrefix;
92 |
93 | function checkSupportCss(key, value) {
94 | if (CSS && CSS.supports) {
95 | return CSS.supports(key, value);
96 | }
97 | }
98 | /**
99 | * load image
100 | *
101 | * @param {String} src
102 | * @return {Promise}
103 | */
104 |
105 |
106 | function loadImage(src) {
107 | return new Promise(function (resolve) {
108 | if (!src) {
109 | resolve(null);
110 | return;
111 | }
112 |
113 | var image = new Image();
114 |
115 | image.onload = function (e) {
116 | resolve(image);
117 | };
118 |
119 | image.onError = function (e) {
120 | resolve(null);
121 | };
122 |
123 | image.crossOrigin = 'Anonymous';
124 | image.src = src;
125 | });
126 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/components/cropper.scss:
--------------------------------------------------------------------------------
1 | /* Popup - Cropper */
2 | .ple-cropper {
3 | position: absolute;
4 | z-index: 99999;
5 | top: 0;
6 | left: 0;
7 | right: 0;
8 | bottom: 0;
9 |
10 | &__bg {
11 | display: block;
12 | position: fixed;
13 | z-index: 1;
14 | top: 0;
15 | left: 0;
16 | right: 0;
17 | bottom: 0;
18 | background: #000;
19 | background: rgba(0,0,0,.86);
20 | }
21 | &__wrap {
22 | position: absolute;
23 | z-index: 2;
24 | left: 10px;
25 | top: 10px;
26 | width: 100px;
27 | height: 100px;
28 | }
29 | &__nav {
30 | $self: &;
31 | position: absolute;
32 | right: -50px;
33 | top: 0;
34 | button {
35 | position: relative;
36 | display: block;
37 | margin: 0 0 1px;
38 | width: 40px;
39 | height: 40px;
40 | background: #888;
41 | text-align: center;
42 | text-indent: -9999px;
43 | border: none;
44 | cursor: pointer;
45 | border-radius: 0;
46 | i {
47 | position: absolute;
48 | display: block;
49 | left: 50%;
50 | top: 50%;
51 | }
52 | {$self}-active {
53 | background: $col-active;
54 | }
55 | }
56 | }
57 | }
58 |
59 |
60 | .ple-cropperBlock {
61 | position: relative;
62 | width: 100%;
63 | height: 100%;
64 | margin: 0;
65 | background: #fff;
66 |
67 | &__image {
68 | display: block;
69 | position: absolute;
70 | left: 0;
71 | top: 0;
72 | &-cover {
73 | right: 0;
74 | bottom: 0;
75 | background-size: cover;
76 | background-position: 50% 50%;
77 | }
78 | &-resize {
79 | width: 100%;
80 | height: 100%;
81 | overflow: hidden;
82 | img {
83 | display: block;
84 | width: 100%;
85 | }
86 | }
87 | }
88 |
89 | &__control {
90 | display: none;
91 | position: absolute;
92 | left: 0;
93 | top: 0;
94 | width: 0;
95 | height: 0;
96 | cursor: move;
97 | &:before {
98 | content: '';
99 | display: block;
100 | position: absolute;
101 | left: 0;
102 | right: 0;
103 | top: 0;
104 | bottom: 0;
105 | border: 1px dashed #000;
106 | }
107 | &:after {
108 | content: '';
109 | display: block;
110 | position: absolute;
111 | left: 1px;
112 | top: 1px;
113 | right: -1px;
114 | bottom: -1px;
115 | border: 1px dashed #fff;
116 | }
117 | &-active {
118 | display: block;
119 | }
120 | }
121 | &__resize {
122 | position: absolute;
123 | display: block;
124 | z-index: 2;
125 | width: 34px;
126 | height: 34px;
127 | border: none;
128 | background: none;
129 | text-indent: -9999px;
130 | outline: 0;
131 |
132 | > i {
133 | display: block;
134 | position: absolute;
135 | left: 50%;
136 | top: 50%;
137 | }
138 | &-lt {
139 | left: -10px;
140 | top: -10px;
141 | cursor: nwse-resize;
142 | }
143 | &-rt {
144 | right: -11px;
145 | top: -10px;
146 | transform: rotate(90deg);
147 | cursor: nesw-resize;
148 | }
149 | &-lb {
150 | left: -10px;
151 | bottom: -11px;
152 | transform: rotate(270deg);
153 | cursor: nesw-resize;
154 | }
155 | &-rb {
156 | right: -12px;
157 | bottom: -12px;
158 | transform: rotate(180deg);
159 | cursor: nwse-resize;
160 | }
161 | }
162 | }
--------------------------------------------------------------------------------
/lib/actions/body.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.addBlock = addBlock;
9 | exports.removeBlock = removeBlock;
10 | exports.shuffleBlocks = shuffleBlocks;
11 | exports.duplicateBlock = duplicateBlock;
12 | exports.updateBlocks = updateBlocks;
13 | exports.activeBlock = activeBlock;
14 | exports.changeColorBlock = changeColorBlock;
15 | exports.updateSetting = updateSetting;
16 | exports.attachImages = attachImages;
17 | exports.attachImage = attachImage;
18 |
19 | var types = _interopRequireWildcard(require("./types"));
20 |
21 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
22 |
23 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
24 |
25 | function addBlock(value) {
26 | return {
27 | type: types.GRID_ADD_BLOCK,
28 | value: value
29 | };
30 | }
31 |
32 | function removeBlock(keys) {
33 | return {
34 | type: types.GRID_REMOVE_BLOCK,
35 | keys: keys
36 | };
37 | }
38 |
39 | function shuffleBlocks(options) {
40 | return {
41 | type: types.GRID_SHUFFLE_BLOCKS,
42 | value: options
43 | };
44 | }
45 |
46 | function duplicateBlock(keys) {
47 | return {
48 | type: types.GRID_DUPLICATE_BLOCK,
49 | keys: keys
50 | };
51 | }
52 |
53 | function updateBlocks(blocks) {
54 | return {
55 | type: types.GRID_UPDATE_BLOCKS,
56 | value: blocks
57 | };
58 | }
59 |
60 | function activeBlock(keys) {
61 | return {
62 | type: types.GRID_ACTIVE_BLOCK,
63 | value: keys
64 | };
65 | }
66 |
67 | function changeColorBlock(keys, color) {
68 | return {
69 | type: types.GRID_CHANGE_COLOR,
70 | keys: keys,
71 | color: color
72 | };
73 | }
74 |
75 | function updateSetting(value) {
76 | return {
77 | type: types.GRID_SETTING_UPDATE,
78 | value: value
79 | };
80 | }
81 |
82 | function attachImages(images, cols, activeBlocks) {
83 | return {
84 | type: types.GRID_ATTACH_IMAGES,
85 | value: images,
86 | columns: cols,
87 | activeBlocks: activeBlocks
88 | };
89 | }
90 |
91 | function attachImage(keys, image) {
92 | return {
93 | type: types.GRID_ATTACH_IMAGE,
94 | keys: keys,
95 | image: image
96 | };
97 | }
--------------------------------------------------------------------------------
/lib/API/Cropper.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var actions = _interopRequireWildcard(require("../actions"));
11 |
12 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
13 |
14 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15 |
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
17 |
18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
19 |
20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
21 |
22 | var Cropper = /*#__PURE__*/function () {
23 | function Cropper(store) {
24 | _classCallCheck(this, Cropper);
25 |
26 | this.store = store;
27 | }
28 | /**
29 | * open cropper
30 | *
31 | * @param {Number} key
32 | */
33 |
34 |
35 | _createClass(Cropper, [{
36 | key: "open",
37 | value: function open(key) {
38 | var state = this.store.getState();
39 | var item = null;
40 |
41 | try {
42 | item = state.tree.body.grid[key];
43 | if (!item.image) throw 'Not found image in item';
44 | } catch (e) {
45 | alert(e);
46 | return;
47 | }
48 |
49 | this.store.dispatch(actions.cropper.open(key, item));
50 | }
51 | }, {
52 | key: "close",
53 | value:
54 | /**
55 | * close cropper
56 | *
57 | * @param {Number} key
58 | * @param {String} position
59 | * @param {String} size
60 | */
61 | function close(key, position, size) {
62 | this.store.dispatch(actions.cropper.close(key, position, size));
63 | }
64 | }]);
65 |
66 | return Cropper;
67 | }();
68 |
69 | var _default = Cropper;
70 | exports["default"] = _default;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const TerserJSPlugin = require('terser-webpack-plugin');
5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
6 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
7 |
8 | const config = (env, options) => {
9 | const isDev = options.mode === 'development';
10 |
11 | // base
12 | let out = {
13 | name: 'PhotoLayoutEditor',
14 | mode: isDev ? 'development' : 'production',
15 | resolve: {
16 | extensions: [ '.js', '.jsx' ],
17 | alias: {
18 | '~': path.resolve(__dirname, './src'),
19 | },
20 | },
21 | module: {
22 | rules: [
23 | {
24 | test: /\.(js|jsx)$/,
25 | exclude: /node_modules/,
26 | use: {
27 | loader: 'babel-loader',
28 | }
29 | },
30 | {
31 | test: /\.s?css$/,
32 | use: [
33 | MiniCssExtractPlugin.loader,
34 | 'css-loader',
35 | 'sass-loader',
36 | ].filter(Boolean),
37 | },
38 | {
39 | test: /\.(jpg|png|gif)$/,
40 | loader: 'file-loader',
41 | options: {
42 | publicPath: './',
43 | name: '[name].[ext]',
44 | limit: 10000,
45 | },
46 | },
47 | isDev && {
48 | test: /\.html$/,
49 | use: [
50 | {
51 | loader: "html-loader",
52 | options: { minimize: false }
53 | }
54 | ]
55 | },
56 | ].filter(Boolean),
57 | },
58 | plugins: [
59 | new MiniCssExtractPlugin({ filename: '[name].css' }),
60 | isDev && new HtmlWebpackPlugin({ template: './src/development/index.html' }),
61 | ].filter(Boolean),
62 | optimization: {},
63 | };
64 |
65 | if (isDev)
66 | {
67 | /**
68 | * Development
69 | */
70 | out.entry = {
71 | // app: './src/development/index.js',
72 | app: path.join(__dirname, 'src/development/index.js'),
73 | };
74 | out.output = {
75 | publicPath: '/',
76 | filename: '[name].js',
77 | chunkFilename: '[name].js',
78 | };
79 | out.devtool = 'inline-source-map';
80 | out.devServer = {
81 | hot: true,
82 | host: '0.0.0.0',
83 | port: options.port || 3000,
84 | stats: 'minimal',
85 | //before: require('./upload/script-node'),
86 | historyApiFallback: true,
87 | noInfo: true,
88 | };
89 | }
90 | else
91 | {
92 | /**
93 | * Production
94 | */
95 | out.entry = {
96 | PhotoLayoutEditor: './src/production/index.js',
97 | };
98 | out.output = {
99 | path: __dirname + '/build',
100 | filename: '[name].js',
101 | publicPath: './',
102 | library: '[name]',
103 | libraryTarget: 'umd',
104 | libraryExport: 'default'
105 | };
106 | out.externals = {
107 | 'jquery/dist/jquery.slim': 'jQuery',
108 | 'react': 'React',
109 | 'react-dom': 'ReactDOM'
110 | };
111 | out.optimization = {
112 | minimize: true,
113 | minimizer: [
114 | new TerserJSPlugin({}),
115 | new CssMinimizerPlugin(),
116 | ],
117 | };
118 | }
119 |
120 | return out;
121 | };
122 |
123 | module.exports = config;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/API/Util.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery/dist/jquery.slim';
2 | import * as actions from '../actions';
3 | import * as lib from '../lib';
4 | import { isArray } from "../lib/object";
5 |
6 |
7 | class Util {
8 |
9 | constructor(store)
10 | {
11 | this.store = store;
12 | }
13 |
14 | /**
15 | * Toggle side
16 | *
17 | * @param {Boolean|undefined} sw
18 | */
19 | toggleSide(sw=undefined)
20 | {
21 | try {
22 | const currentSw = this.store.getState().tree.side.visible;
23 | const targetSw = (typeof sw === 'undefined') ? !currentSw : sw;
24 |
25 | this.store.dispatch(actions.side.visible(targetSw));
26 | } catch(e) {
27 | console.warn('Error action', e)
28 | }
29 | };
30 |
31 | /**
32 | * export
33 | *
34 | * @param {String} type
35 | * @param {Boolean} isInsertImage
36 | * @return {Array|Object}
37 | */
38 | export(type=null, isInsertImage=true)
39 | {
40 | if (!type) return null;
41 |
42 | const state = this.store.getState();
43 |
44 | function side()
45 | {
46 | if (!isInsertImage) return [];
47 | let keys = state.api.side.getKeys('all');
48 | return state.api.side.getImages(keys);
49 | }
50 | function grid()
51 | {
52 | let result = state.api.grid.getBlocks('all');
53 | return Object.keys(result).sort().map(o => {
54 | delete result[o].indexPrefix;
55 | if (!isInsertImage && result[o] && result[o].image) delete result[o].image;
56 | return result[o];
57 | });
58 | }
59 | function preference()
60 | {
61 | return state.api.grid.getPreference();
62 | }
63 |
64 | switch(type)
65 | {
66 | case 'side':
67 | return side();
68 | case 'grid':
69 | return grid();
70 | case 'preference':
71 | return state.api.grid.getPreference();
72 | case 'all':
73 | return {
74 | side: side(),
75 | grid: grid(),
76 | preference: preference(),
77 | };
78 | break;
79 | }
80 |
81 | return null;
82 | }
83 |
84 | /**
85 | * import
86 | *
87 | * @param {Array|Object} value
88 | * @param {Boolean} replace
89 | */
90 | import(value=null, replace=false)
91 | {
92 | if (!value) return;
93 |
94 | const state = this.store.getState();
95 |
96 | function side(value=null)
97 | {
98 | if (replace) state.api.side.clear();
99 | state.api.side.add(value);
100 | }
101 | function grid(value=null)
102 | {
103 | if (replace)
104 | {
105 | let keys = state.api.grid.getKeys('all');
106 | state.api.grid.remove(keys);
107 | }
108 | state.api.grid.add(value);
109 | }
110 |
111 | if (value.side && isArray(value.side)) side(value.side);
112 | if (value.grid && isArray(value.grid)) grid(value.grid);
113 | if (value.preference && typeof value.preference === 'object') state.api.grid.setPreference(value.preference);
114 | }
115 |
116 | /**
117 | * make image
118 | *
119 | * @param {String} format (jpg|png)
120 | * @param {Number} quality
121 | * @param {Number} sampling
122 | * @param {String} output
123 | * @return {Promise}
124 | */
125 | makeImage(format='jpg', quality=.75, sampling=2, output=null)
126 | {
127 | const defer = $.Deferred();
128 | const state = this.store.getState();
129 | const { setting, grid } = state.tree.body;
130 |
131 | lib.makingImage(
132 | state.element.querySelector('.ple-grid'),
133 | { setting, grid },
134 | { format, quality, sampling, output }
135 | ).done((image) => {
136 | defer.resolve(image);
137 | }).progress((total, current, image) => {
138 | defer.notify(total, current, image);
139 | }).fail((error) => {
140 | defer.reject(error);
141 | });
142 |
143 | return defer.promise();
144 | }
145 |
146 | }
147 |
148 |
149 | export default Util;
150 |
--------------------------------------------------------------------------------
/lib/lib/resamplingImage.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = resamplingImage;
9 |
10 | var lib = _interopRequireWildcard(require("./"));
11 |
12 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
13 |
14 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
15 |
16 | // default options
17 | var defaultOptions = {
18 | image: null,
19 | width: 100,
20 | height: 100,
21 | reSampleCount: 1,
22 | bgColor: 'rgba(255,255,255,1)',
23 | cw: 0,
24 | ch: 0,
25 | cx: 0,
26 | cy: 0,
27 | dw: 0,
28 | dh: 0,
29 | dx: 0,
30 | dy: 0
31 | };
32 | /**
33 | * resize
34 | *
35 | * @param {Object} options
36 | * @param {Number} count
37 | * @param {Canvas} parentCanvas
38 | * @return {Promise}
39 | */
40 |
41 | function resize(options, count, parentCanvas) {
42 | return new Promise(function (resolve) {
43 | function func(count, parentCanvas) {
44 | var max = Math.pow(2, count);
45 | var canvasForResize = new lib.Canvas(options.width * max, options.height * max, options.bgColor);
46 | canvasForResize.ctx.drawImage(parentCanvas.el, 0, 0, parentCanvas.el.width * 0.5, parentCanvas.el.height * 0.5);
47 |
48 | if (count > 0) {
49 | func(count - 1, canvasForResize);
50 | } else {
51 | resolve(canvasForResize);
52 | }
53 | }
54 |
55 | func(count - 1, parentCanvas);
56 | });
57 | }
58 | /**
59 | * resampling image
60 | *
61 | * @param {Object} options
62 | * @return {Promise}
63 | */
64 |
65 |
66 | function resamplingImage(options) {
67 | // assign options
68 | options = Object.assign({}, defaultOptions, options);
69 | return new Promise(function (resolve, reject) {
70 | if (!options.image) reject('not found image'); // set reSample count
71 |
72 | options.reSampleCount = Math.min(4, options.reSampleCount);
73 | options.reSampleCount = Math.max(0, options.reSampleCount);
74 | var reSampleMax = Math.pow(2, options.reSampleCount); // set canvas
75 |
76 | var canvas = new lib.Canvas(options.width * reSampleMax, options.height * reSampleMax, options.bgColor);
77 | canvas.ctx.drawImage(options.image, options.cx, // cx
78 | options.cy, // cy
79 | options.cw, // cw
80 | options.ch, // ch
81 | options.dx * reSampleMax, // dx
82 | options.dy * reSampleMax, // dy
83 | options.dw * reSampleMax, // dw
84 | options.dh * reSampleMax // dh
85 | );
86 |
87 | if (options.reSampleCount > 0) {
88 | resize(options, options.reSampleCount, canvas).then(resolve);
89 | } else {
90 | resolve(canvas);
91 | }
92 | });
93 | }
--------------------------------------------------------------------------------
/lib/lib/uploader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = uploader;
7 |
8 | var _jquery = _interopRequireDefault(require("jquery/dist/jquery.slim"));
9 |
10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
11 |
12 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
13 |
14 | /**
15 | * local upload
16 | *
17 | * @param {File} file
18 | * @return {Promise}
19 | */
20 | function local(file) {
21 | var defer = _jquery["default"].Deferred();
22 |
23 | var reader = new FileReader();
24 | reader.addEventListener('load', function (e) {
25 | defer.resolve({
26 | name: file.name,
27 | type: file.type,
28 | size: file.size,
29 | url: e.target.result
30 | });
31 | });
32 | reader.addEventListener('error', function (e) {
33 | defer.reject(e.target);
34 | });
35 | reader.readAsDataURL(file);
36 | return defer.promise();
37 | }
38 | /**
39 | * external upload
40 | *
41 | * @param {String} script
42 | * @param {File} file
43 | * @return {Promise}
44 | */
45 |
46 |
47 | function external(script, file) {
48 | var defer = _jquery["default"].Deferred();
49 |
50 | var xhr = new XMLHttpRequest();
51 |
52 | if (!(typeof FormData === 'function' || (typeof FormData === "undefined" ? "undefined" : _typeof(FormData)) === 'object')) {
53 | defer.reject('not support browser', file);
54 | return null;
55 | }
56 |
57 | var formData = new FormData();
58 |
59 | function onLoad(e) {
60 | try {
61 | var result = JSON.parse(e.target.responseText);
62 |
63 | if (result.state === 'success') {
64 | defer.resolve(result.data);
65 | } else {
66 | throw 'server error';
67 | }
68 | } catch (e) {
69 | defer.reject(e);
70 | }
71 | }
72 |
73 | function onProgress(event) {
74 | var loaded = event.loaded,
75 | total = event.total;
76 | defer.notify('progress', loaded, total);
77 | }
78 |
79 | formData.append('files[]', file);
80 | xhr.open('post', script, true);
81 | xhr.addEventListener('load', onLoad);
82 | xhr.upload.addEventListener('progress', onProgress);
83 | xhr.send(formData); // start queue
84 |
85 | defer.notify('start');
86 | return defer.promise();
87 | }
88 | /**
89 | * upload multiple files
90 | *
91 | */
92 |
93 |
94 | function uploader(files, script) {
95 | var defer = _jquery["default"].Deferred();
96 |
97 | var total = files.length;
98 | var count = 0;
99 |
100 | function upload() {
101 | if (count >= total) {
102 | defer.resolve();
103 | return;
104 | }
105 |
106 | if (script) {
107 | external(script, files[count]).done(function (data) {
108 | defer.notify('done', {
109 | count: count,
110 | data: data
111 | });
112 | count++;
113 | upload();
114 | }).progress(function (state, loaded, total) {
115 | defer.notify(state, state === 'progress' && {
116 | loaded: loaded,
117 | total: total
118 | });
119 | }).fail(function (error) {
120 | defer.reject(error);
121 | });
122 | } else {
123 | local(files[count]).done(function (data) {
124 | defer.notify('done', {
125 | count: count,
126 | data: data
127 | });
128 | count++;
129 | upload();
130 | }).fail(function () {});
131 | }
132 | }
133 |
134 | upload(0);
135 | return defer.promise();
136 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Cropper/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import $ from 'jquery/dist/jquery.slim';
4 | import classNames from 'classnames';
5 | import Block from './Block';
6 | import * as lib from '../../lib';
7 |
8 |
9 | class Cropper extends React.Component {
10 |
11 | constructor(props)
12 | {
13 | super(props);
14 | const { cropper } = props.tree;
15 |
16 | this.imageMeta = null;
17 | this._block = null;
18 | this.$item = $(props.element)
19 | .find('.react-grid-item')
20 | .filter(`[data-key=${cropper.key}]`);
21 |
22 | this.state = {
23 | pending: true,
24 | position: cropper.item.image.position,
25 | size: cropper.item.image.size || 'cover',
26 | width: this.$item.width(),
27 | height: this.$item.height(),
28 | top: this.$item.position().top,
29 | left: this.$item.position().left
30 | };
31 | }
32 |
33 | componentDidMount()
34 | {
35 | const { props } = this;
36 | const { cropper } = props.tree;
37 |
38 | lib.util.getImageSize(cropper.item.image.src).then((res) => {
39 | this.imageMeta = res;
40 | this.setState({ pending: false });
41 | });
42 | }
43 |
44 | /**
45 | * close cropper
46 | * `cropper`를 닫고, 변경된 이미지를 `grid`로 보낸다.
47 | */
48 | _onClose()
49 | {
50 | const { props } = this;
51 | props.api.cropper.close(
52 | props.tree.cropper.key,
53 | this._block.state.position,
54 | this._block.state.size
55 | );
56 | }
57 |
58 | /**
59 | * toggle image type
60 | * 직접 리사이즈를 사용하는지 기본(꽉채우는..)타입으로 사용할건지 변경하는 액션
61 | */
62 | _toggleImageType()
63 | {
64 | const { state, props } = this;
65 |
66 | if (state.size === 'cover')
67 | {
68 | let targetSize = '';
69 | let ratio = 0;
70 | if (state.height > state.width)
71 | {
72 | ratio = lib.number.getRatioForResize(state.height, this.imageMeta.height);
73 | targetSize = `${parseInt(this.imageMeta.width * ratio)}px ${state.height}px`;
74 | }
75 | else
76 | {
77 | ratio = lib.number.getRatioForResize(state.width, this.imageMeta.width);
78 | targetSize = `${state.width}px ${parseInt(this.imageMeta.height * ratio)}px`;
79 | }
80 | this.setState({
81 | position: '0 0',
82 | size: targetSize,
83 | });
84 | }
85 | else
86 | {
87 | this.setState({
88 | position: `50% 50%`,
89 | size: 'cover',
90 | });
91 | }
92 | }
93 |
94 | /**
95 | * on update block
96 | *
97 | * @param {object} state
98 | */
99 | onUpdateBlock(state)
100 | {
101 | this.setState(state);
102 | }
103 |
104 | render()
105 | {
106 | const { state, props } = this;
107 |
108 | if (state.pending) return null;
109 |
110 | return (
111 |
112 |
113 |
121 | { this._block = r; }}
123 | src={props.tree.cropper.item.image.src}
124 | position={state.position}
125 | size={state.size}
126 | bgColor={props.tree.cropper.item.color || props.setting.body.blockColor}
127 | onUpdateBlock={(e) => this.onUpdateBlock(e)}/>
128 |
129 | this._onClose()}>
130 | Close cropper
131 |
132 | this._toggleImageType()}
135 | className={classNames({
136 | 'ple-cropper__nav-active': state.size !== 'cover'
137 | })}>
138 | Toggle background size type
139 |
140 |
141 |
142 |
143 | );
144 | }
145 |
146 | }
147 | Cropper.displayName = 'Cropper';
148 |
149 |
150 | export default connect((state) => Object.assign({}, state, {}))(Cropper);
--------------------------------------------------------------------------------
/lib/Container/Body/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | var _GridLayout = _interopRequireDefault(require("./GridLayout"));
13 |
14 | var _Toolbar = _interopRequireDefault(require("./Toolbar"));
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
17 |
18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
19 |
20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
21 |
22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
23 |
24 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
25 |
26 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
27 |
28 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
29 |
30 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
31 |
32 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
33 |
34 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
35 |
36 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
37 |
38 | var Body = /*#__PURE__*/function (_React$Component) {
39 | _inherits(Body, _React$Component);
40 |
41 | var _super = _createSuper(Body);
42 |
43 | function Body(props) {
44 | _classCallCheck(this, Body);
45 |
46 | return _super.call(this, props);
47 | }
48 |
49 | _createClass(Body, [{
50 | key: "render",
51 | value: function render() {
52 | return /*#__PURE__*/_react["default"].createElement("div", {
53 | className: "ple-container"
54 | }, /*#__PURE__*/_react["default"].createElement("div", {
55 | className: "ple-body"
56 | }, /*#__PURE__*/_react["default"].createElement(_Toolbar["default"], null), /*#__PURE__*/_react["default"].createElement(_GridLayout["default"], null)));
57 | }
58 | }]);
59 |
60 | return Body;
61 | }(_react["default"].Component);
62 |
63 | Body.displayName = 'Body';
64 | var _default = Body;
65 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/components/toolbar.scss:
--------------------------------------------------------------------------------
1 | /* Toolbar */
2 | .ple-toolbar {
3 | $toolbar: &;
4 | $height: 42px;
5 |
6 | position: relative;
7 | height: $height;
8 | &:after {
9 | content: '';
10 | display: block;
11 | clear: both;
12 | }
13 |
14 | &__wrap {
15 | position: absolute;
16 | height: $height;
17 | z-index: 11;
18 | width: 100%;
19 | background: #fff;
20 | box-shadow: 0 1px 4px rgba(0,0,0,.2);
21 | }
22 | &__pop {
23 | position: relative;
24 | display: none;
25 | }
26 | &__block {
27 | float: left;
28 | margin-right: 1px;
29 | > button {
30 | display: block;
31 | position: relative;
32 | margin: 0;
33 | padding: 0;
34 | width: $height;
35 | height: $height;
36 | border: none;
37 | background: $col-blur;
38 | cursor: pointer;
39 | outline: 0;
40 | border-radius: 0;
41 | i {
42 | position: absolute;
43 | display: block;
44 | left: 50%;
45 | top: 50%;
46 | }
47 | &:active {
48 | background: mix($col-key, #000, 98%);
49 | }
50 | }
51 | &-key {
52 | > button {
53 | background: $col-active;
54 | &:active {
55 | background: mix($col-active, #000, 75%);
56 | }
57 | }
58 | }
59 | &-active {
60 | > button {
61 | background: mix($col-key, #000, 98%);
62 | }
63 | #{$toolbar}__pop {
64 | display: block;
65 | }
66 | &.key {
67 | > button {
68 | background: mix($col-active, #000, 75%);
69 | }
70 | }
71 | }
72 | }
73 |
74 | .ple-colorPicker {
75 | &__wrap {
76 | position: relative;
77 | }
78 | &__body {
79 | position: absolute;
80 | left: -30px;
81 | top: 0;
82 | }
83 | }
84 | }
85 |
86 |
87 | .ple-edit-setting {
88 | form {
89 | position: absolute;
90 | left: 0;
91 | top: 0;
92 | z-index: 99999;
93 | min-width: 270px;
94 | background: #fff;
95 | box-shadow: 1px 1px 8px rgba(0,0,0,.4);
96 | fieldset {
97 | margin: 0;
98 | padding: 20px;
99 | border: none;
100 | }
101 | }
102 | legend {
103 | @extend %blind;
104 | }
105 | &__title {
106 | margin: 0 0 10px;
107 | font-size: 18px;
108 | color: $col-key;
109 | }
110 | &__form {
111 | > dl {
112 | display: flex;
113 | align-items: center;
114 | margin: 1px 0;
115 | > * {
116 | padding: 3px 0;
117 | }
118 | dt {
119 | width: 82px;
120 | font-size: 12px;
121 | color: #333;
122 | letter-spacing: -.2px;
123 | label {
124 | display: inline;
125 | }
126 | }
127 | dd {
128 | flex: 1;
129 | margin: 0;
130 | }
131 | }
132 | }
133 | &__buttons {
134 | &:after {
135 | content: '';
136 | display: block;
137 | clear: both;
138 | }
139 | span {
140 | display: block;
141 | width: 50%;
142 | float: left;
143 | &:first-child {
144 | padding-right: 1px;
145 | }
146 | }
147 | button {
148 | margin: 0;
149 | padding: 12px 0;
150 | width: 100%;
151 | font-size: 12px;
152 | color: #fff;
153 | background: $col-blur;
154 | border: none;
155 | cursor: pointer;
156 | -webkit-appearance: none;
157 | border-radius: 0;
158 | &.ple-submit {
159 | background: $col-key;
160 | }
161 | }
162 | }
163 | .ple-type-input {
164 | input[type=text],
165 | input[type=number] {
166 | box-sizing: border-box;
167 | margin: 0;
168 | padding: 6px 6px;
169 | font-size: 12px;
170 | color: #111;
171 | border: 1px solid #CACDCE;
172 | border-radius: 1px;
173 | box-shadow: inset 0 2px 3px rgba(0,0,0,.1);
174 | }
175 | span {
176 | padding-left: 5px;
177 | font-size: 12px; color: #333;
178 | }
179 | &-active {
180 | input[type=text],
181 | input[type=number] {
182 | border-color: $col-active !important;
183 | }
184 | }
185 | }
186 | .ple-type-checkbox {
187 | label {
188 | padding: 6px 12px 6px 0;
189 | display: inline-block;
190 | }
191 | input {
192 | margin: 0 0 0 4px;
193 | padding: 0;
194 | }
195 | span {
196 | padding-left: 6px;
197 | font-size: 12px; color: #333;
198 | }
199 | }
200 | }
201 |
202 |
203 | .ple-edit-bgColor {
204 | position: relative;
205 |
206 | &__inputBox {
207 | display: inline-block;
208 | width: 38px;
209 | height: 26px;
210 | border: 1px solid #CACDCE;
211 | padding: 4px;
212 | background: #fff;
213 | border-radius: 2px;
214 | }
215 | &__input {
216 | width: 100%;
217 | height: 100%;
218 | border: none;
219 | font-size: 0;
220 | margin: 0;
221 | padding: 0;
222 | border-radius: 0;
223 | vertical-align: top;
224 | cursor: pointer;
225 | }
226 | &__popup {
227 | position: relative;
228 | }
229 | &__picker {
230 | position: absolute;
231 | left: -10px;
232 | top: 0;
233 | .colorpicker {
234 | border: none;
235 | }
236 | }
237 | }
--------------------------------------------------------------------------------
/lib/reducers/side.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = base;
9 |
10 | var types = _interopRequireWildcard(require("../actions/types"));
11 |
12 | var defaults = _interopRequireWildcard(require("./defaults"));
13 |
14 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
15 |
16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
17 |
18 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
19 |
20 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
21 |
22 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
23 |
24 | var nextFileId = 0;
25 | /**
26 | * Reducers
27 | */
28 | // base
29 |
30 | function base() {
31 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : defaults.side;
32 | var action = arguments.length > 1 ? arguments[1] : undefined;
33 | var newState = Object.assign({}, state);
34 | var files = {};
35 |
36 | switch (action.type) {
37 | case types.INIT_PLE:
38 | try {
39 | action.preference.side.files.forEach(function (o) {
40 | files[nextFileId++] = {
41 | image: o,
42 | active: false
43 | };
44 | });
45 | return _objectSpread(_objectSpread(_objectSpread({}, state), action.preference.side), {}, {
46 | files: _objectSpread(_objectSpread({}, state.files), files)
47 | });
48 | } catch (e) {
49 | return state;
50 | }
51 |
52 | case types.SIDE_VISIBLE:
53 | return _objectSpread(_objectSpread({}, state), {}, {
54 | visible: action.value
55 | });
56 |
57 | case types.SIDE_TOGGLE:
58 | return _objectSpread(_objectSpread({}, state), {}, {
59 | visible: !state.visible
60 | });
61 |
62 | case types.SIDE_ADD_FILES:
63 | action.files.forEach(function (o) {
64 | files[nextFileId++] = {
65 | image: o,
66 | active: false
67 | };
68 | });
69 | return _objectSpread(_objectSpread({}, newState), {}, {
70 | files: _objectSpread(_objectSpread({}, newState.files), files)
71 | });
72 |
73 | case types.SIDE_REMOVE_FILES:
74 | if (!action.keys.length) return state;
75 | action.keys.forEach(function (o) {
76 | delete newState.files[o];
77 | });
78 | return newState;
79 |
80 | case types.SIDE_UPDATE_SELECTED:
81 | if (!(action.value && Object.keys(action.value).length)) return state;
82 | Object.keys(action.value).forEach(function (k) {
83 | if (!newState.files[k]) return;
84 | newState.files[k].active = action.value[k].active;
85 | });
86 | return newState;
87 |
88 | case types.SIDE_UPDATE_PROGRESS:
89 | return _objectSpread(_objectSpread({}, state), {}, {
90 | progressPercent: action.value
91 | });
92 |
93 | default:
94 | return state;
95 | }
96 | }
--------------------------------------------------------------------------------
/lib/lib/Keyboard.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _jquery = _interopRequireDefault(require("jquery/dist/jquery.slim"));
11 |
12 | var libs = _interopRequireWildcard(require("../lib"));
13 |
14 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
15 |
16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
17 |
18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
19 |
20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
21 |
22 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
23 |
24 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
25 |
26 | var Keyboard = /*#__PURE__*/function () {
27 | function Keyboard() {
28 | var _this = this;
29 |
30 | _classCallCheck(this, Keyboard);
31 |
32 | this.eventName = "PLE_".concat(libs.number.getTimeStamp());
33 | this.code = null;
34 | this.keyName = null;
35 | this.names = {
36 | 17: 'CTRL',
37 | 18: 'ALT',
38 | 91: 'CMD',
39 | 93: 'CMD',
40 | 16: 'SHIFT'
41 | }; // init key down event
42 |
43 | (0, _jquery["default"])(window).on("keydown.".concat(this.eventName), function (e) {
44 | return _this._keyDown(e);
45 | });
46 | }
47 | /**
48 | * apply
49 | *
50 | * @param {Number} code
51 | */
52 |
53 |
54 | _createClass(Keyboard, [{
55 | key: "apply",
56 | value: function apply(code) {
57 | this.code = code;
58 | this.keyName = this.names[this.code] || null;
59 | }
60 | /**
61 | * key down event
62 | *
63 | * @param {Event} e
64 | */
65 |
66 | }, {
67 | key: "_keyDown",
68 | value: function _keyDown(e) {
69 | var _this2 = this;
70 |
71 | // apply keyCode
72 | this.apply(e.keyCode); // set events
73 |
74 | (0, _jquery["default"])(window).on("keyup.".concat(this.eventName), function () {
75 | return _this2._keyUp();
76 | }).on("contextmenu.".concat(this.eventName), function () {
77 | return _this2._keyUp();
78 | }).on("blur.".concat(this.eventName), function () {
79 | return _this2._keyUp();
80 | }).off("keydown.".concat(this.eventName));
81 | }
82 | /**
83 | * key up event
84 | *
85 | */
86 |
87 | }, {
88 | key: "_keyUp",
89 | value: function _keyUp() {
90 | var _this3 = this;
91 |
92 | // apply keyCode
93 | this.apply(null); // set events
94 |
95 | (0, _jquery["default"])(window).on("keydown.".concat(this.eventName), function (e) {
96 | return _this3._keyDown(e);
97 | }).off("contextmenu.".concat(this.eventName, " keyup.").concat(this.eventName, " blur.").concat(this.eventName));
98 | }
99 | /**
100 | * destroy event
101 | *
102 | */
103 |
104 | }, {
105 | key: "destroy",
106 | value: function destroy() {
107 | (0, _jquery["default"])(window).off("keydown.".concat(this.eventName, " contextmenu.").concat(this.eventName, " keyup.").concat(this.eventName, " blur.").concat(this.eventName));
108 | }
109 | }]);
110 |
111 | return Keyboard;
112 | }();
113 |
114 | exports["default"] = Keyboard;
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | var _redux = require("redux");
13 |
14 | var _reactRedux = require("react-redux");
15 |
16 | var _Container = _interopRequireDefault(require("./Container"));
17 |
18 | var _reducers = _interopRequireDefault(require("./reducers"));
19 |
20 | var _API = _interopRequireDefault(require("./API"));
21 |
22 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
23 |
24 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
25 |
26 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
27 |
28 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
29 |
30 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
31 |
32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
33 |
34 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
35 |
36 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
37 |
38 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
39 |
40 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
41 |
42 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
43 |
44 | var PhotoLayoutEditor = /*#__PURE__*/function (_React$Component) {
45 | _inherits(PhotoLayoutEditor, _React$Component);
46 |
47 | var _super = _createSuper(PhotoLayoutEditor);
48 |
49 | function PhotoLayoutEditor(props) {
50 | var _this;
51 |
52 | _classCallCheck(this, PhotoLayoutEditor);
53 |
54 | _this = _super.call(this, props); // set store
55 |
56 | _this.store = (0, _redux.createStore)(_reducers["default"]); // set api
57 |
58 | _this.api = new _API["default"](_this.store);
59 | return _this;
60 | }
61 |
62 | _createClass(PhotoLayoutEditor, [{
63 | key: "render",
64 | value: function render() {
65 | var props = this.props;
66 | return /*#__PURE__*/_react["default"].createElement(_reactRedux.Provider, {
67 | store: this.store
68 | }, /*#__PURE__*/_react["default"].createElement(_Container["default"], {
69 | parent: {
70 | preference: props,
71 | api: this.api
72 | }
73 | }));
74 | }
75 | }]);
76 |
77 | return PhotoLayoutEditor;
78 | }(_react["default"].Component);
79 |
80 | PhotoLayoutEditor.displayName = 'PhotoLayoutEditor';
81 | PhotoLayoutEditor.defaultProps = {
82 | body: {
83 | setting: {},
84 | blockColor: '#dddddd',
85 | grid: []
86 | },
87 | side: {
88 | files: [],
89 | visible: true
90 | },
91 | uploadScript: null,
92 | uploadParamsConvertFunc: null
93 | };
94 | var _default = PhotoLayoutEditor;
95 | exports["default"] = _default;
--------------------------------------------------------------------------------
/lib/reducers/core.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports.setting = setting;
9 | exports.api = api;
10 | exports.keyboard = keyboard;
11 | exports.element = element;
12 |
13 | var types = _interopRequireWildcard(require("../actions/types"));
14 |
15 | var _Keyboard = _interopRequireDefault(require("../lib/Keyboard"));
16 |
17 | var defaults = _interopRequireWildcard(require("./defaults"));
18 |
19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
20 |
21 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
22 |
23 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
24 |
25 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
26 |
27 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
28 |
29 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
30 |
31 | function setting() {
32 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
33 | var action = arguments.length > 1 ? arguments[1] : undefined;
34 | var newState = Object.assign({}, state);
35 |
36 | switch (action.type) {
37 | case types.INIT_PLE:
38 | newState = Object.assign({}, {
39 | base: _objectSpread(_objectSpread({}, defaults.setting.base), {}, {
40 | uploadScript: action.preference.uploadScript || defaults.setting.base.uploadScript,
41 | uploadParamsConvertFunc: action.preference.uploadParamsConvertFunc || defaults.setting.base.uploadParamsConvertFunc,
42 | updateStoreFunc: action.preference.updateStoreFunc || defaults.setting.base.updateStoreFunc,
43 | callback: action.preference.callback || defaults.setting.base.callback
44 | }),
45 | side: _objectSpread(_objectSpread({}, defaults.setting.side), action.preference.side),
46 | body: _objectSpread(_objectSpread({}, defaults.setting.body), action.preference.body)
47 | });
48 | return newState;
49 |
50 | default:
51 | return state;
52 | }
53 | }
54 |
55 | function api() {
56 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
57 | var action = arguments.length > 1 ? arguments[1] : undefined;
58 |
59 | switch (action.type) {
60 | case types.INIT_PLE:
61 | return action.api;
62 |
63 | default:
64 | return state;
65 | }
66 | }
67 |
68 | function keyboard() {
69 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
70 | var action = arguments.length > 1 ? arguments[1] : undefined;
71 |
72 | switch (action.type) {
73 | case types.INIT_PLE:
74 | return new _Keyboard["default"]();
75 |
76 | default:
77 | return state;
78 | }
79 | }
80 |
81 | function element() {
82 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
83 | var action = arguments.length > 1 ? arguments[1] : undefined;
84 |
85 | switch (action.type) {
86 | case types.INIT_PLE:
87 | return action.element;
88 |
89 | default:
90 | return state;
91 | }
92 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/style/components/side.scss:
--------------------------------------------------------------------------------
1 | /* Sidebar */
2 | .ple-side {
3 | &__wrap {
4 | position: absolute;
5 | z-index: 100;
6 | right: 0;
7 | top: 0;
8 | bottom: 0;
9 | width: $sidebar-width;
10 | padding: 0;
11 | background: $col-key;
12 | -webkit-transform: translateX($sidebar-width);
13 | transform: translateX($sidebar-width);
14 | -webkit-transition: -webkit-transform 1s ease-out;
15 | transition: transform .3s ease-out;
16 | &-show {
17 | -webkit-transform: translateX(0);
18 | transform: translateX(0);
19 | }
20 | }
21 | &__toggle {
22 | position: absolute;
23 | left: -25px;
24 | top: 20px;
25 | z-index: 10;
26 | padding: 10px 5px;
27 | margin: 0;
28 | border: none;
29 | cursor: pointer;
30 | background: transparent;
31 | outline: 0;
32 | > span {
33 | display: block;
34 | width: 20px;
35 | height: 70px;
36 | background: #4e4e4e;
37 | border-top-left-radius: 3px;
38 | border-bottom-left-radius: 3px;
39 | box-shadow: inset -1px 0 6px rgba(0,0,0,.4);
40 | > i {
41 | display: block;
42 | position: absolute;
43 | left: 50%;
44 | top: 50%;
45 | }
46 | }
47 |
48 | &:active {
49 | > span {
50 | background: mix(#4e4e4e, #000, 80%);
51 | }
52 | }
53 | }
54 | &__background {
55 | position: absolute;
56 | left: 0;
57 | right: 20px;
58 | top: 0;
59 | bottom: 0;
60 | }
61 | &-active &__toggle i {
62 | width: 7px;
63 | height: 12px;
64 | background-position: -112px 0;
65 | &.ple-abs {
66 | margin-left: -4px;
67 | margin-top: -6px;
68 | }
69 | }
70 |
71 | .ple-sideNavigation {
72 | position: absolute;
73 | z-index: 2;
74 | left: 0;
75 | right: 0;
76 | top: 30px;
77 | &__wrap {
78 | margin: 0 15px;
79 | border-radius: 3px;
80 | background: #444;
81 | overflow: hidden;
82 | &:after {
83 | content: '';
84 | display: block;
85 | clear: both;
86 | }
87 | }
88 | button, span {
89 | margin: 0;
90 | padding: 0;
91 | position: relative;
92 | float: left;
93 | width: 25%;
94 | height: 36px;
95 | border-width: 0 0 0 1px;
96 | border-style: solid;
97 | border-color: #626164;
98 | border-color: rgba(255,255,255,.3);
99 | background: none;
100 | cursor: pointer;
101 | overflow: hidden;
102 | outline: 0;
103 | border-radius: 0;
104 | &:first-child {
105 | border-width: 0;
106 | }
107 | }
108 | button {
109 | &:active {
110 | background: mix(#444, #000, 80%);
111 | }
112 | }
113 | span {
114 | &:active {
115 | background: mix(#444, #000, 80%);
116 | }
117 | }
118 | input[type=file] {
119 | position: absolute;
120 | left: 0;
121 | top: 0;
122 | z-index: 3;
123 | width: 100%;
124 | height: 100%;
125 | opacity: 0;
126 | outline: none;
127 | white-space: nowrap;
128 | cursor: pointer;
129 | -webkit-user-select: none;
130 | &::-webkit-file-upload-button {
131 | visibility: hidden;
132 | }
133 | }
134 | i {
135 | display: block;
136 | position: absolute;
137 | left: 50%;
138 | top: 50%;
139 | }
140 | }
141 | .ple-sideItems {
142 | $item-height: 63px;
143 | height: 100%;
144 | margin: 0;
145 | padding: 75px 0 0;
146 | &__wrap {
147 | height: 100%;
148 | padding: 0 10px 20px;
149 | overflow-x: hidden;
150 | overflow-y: auto;
151 | -webkit-overflow-scrolling: touch;
152 | background: $col-key;
153 | }
154 | ul {
155 | margin: 0;
156 | padding: 0;
157 | list-style: none;
158 | display: -webkit-flex;
159 | display: flex;
160 | flex-flow: row wrap;
161 | }
162 | li {
163 | position: relative;
164 | width: 33.3333%;
165 | padding: 5px;
166 | }
167 | &__loading {}
168 | &__progress {
169 | position: relative;
170 | width: 100%;
171 | height: $item-height;
172 | background: #444;
173 | }
174 | &__bar {
175 | position: absolute;
176 | left: 0;
177 | right: 0;
178 | bottom: 0;
179 | background: $col-active;
180 | }
181 | &__percent {
182 | position: absolute;
183 | left: 0;
184 | right: 0;
185 | top: 50%;
186 | margin-top: -7px;
187 | font-size: 11px;
188 | color: #fff;
189 | text-align: center;
190 | line-height: 14px;
191 | }
192 | span {
193 | position: relative;
194 | display: block;
195 | margin: 0 auto;
196 | padding: 0;
197 | border: none;
198 | width: 100%;
199 | height: $item-height;
200 | background-size: cover;
201 | background-repeat: no-repeat;
202 | background-position: 50% 50%;
203 | background-color: #fff;
204 | cursor: pointer;
205 | text-indent: -9999px;
206 | outline: none;
207 | touch-action: pan-x;
208 | &.ple-sideItems__item-active {
209 | &:after {
210 | content: '';
211 | display: block;
212 | position: absolute;
213 | left: 0;
214 | right: 0;
215 | top: 0;
216 | bottom: 0;
217 | border: 4px solid $col-active;
218 | }
219 | }
220 | }
221 | }
222 |
223 | /* placeholder */
224 | &__placeholder {
225 | display: block;
226 | position: absolute;
227 | z-index: 10000;
228 | margin: 0;
229 | padding: 0;
230 | left: -999px;
231 | top: -999px;
232 | opacity: .8;
233 | background-size: cover;
234 | background-repeat: no-repeat;
235 | background-position: 50% 50%;
236 | background-color: #fff;
237 | text-indent: -9999px;
238 | cursor: pointer;
239 | border: none;
240 | }
241 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Body/GridLayout/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import ReactGridLayout from 'react-grid-layout';
4 | import classNames from 'classnames';
5 | import Cropper from '../../Cropper';
6 | import * as actions from '../../../actions';
7 | import * as libs from '../../../lib';
8 |
9 |
10 | let timeStamp = [];
11 |
12 |
13 | class GridLayout extends React.Component {
14 |
15 | constructor(props)
16 | {
17 | super(props);
18 | }
19 |
20 | /**
21 | * on select block
22 | *
23 | * @param {String} key
24 | */
25 | _selectBlock(key=null)
26 | {
27 | const { props } = this;
28 |
29 | if (key === null)
30 | {
31 | props.api.grid.select([]);
32 | return;
33 | }
34 |
35 | switch(props.keyboard.keyName)
36 | {
37 | case 'CMD':
38 | case 'CTRL':
39 | case 'SHIFT':
40 | if (libs.object.isArray(props.tree.body.activeBlock))
41 | {
42 | let newActiveBlock = Object.assign([], props.tree.body.activeBlock);
43 | if (newActiveBlock.indexOf(key) > -1)
44 | {
45 | newActiveBlock.splice(newActiveBlock.indexOf(key), 1);
46 | }
47 | else
48 | {
49 | newActiveBlock.push(key);
50 | }
51 | props.api.grid.select(newActiveBlock);
52 | return;
53 | }
54 | break;
55 | }
56 |
57 | props.api.grid.select([key]);
58 | }
59 |
60 | /**
61 | * On update blocks
62 | *
63 | * @param {String} type
64 | * @param {Array} layout
65 | * @param {HTMLElement} element
66 | */
67 | _updateBlocks(type, layout=null, element=null)
68 | {
69 | const { props } = this;
70 |
71 | /**
72 | * convert layout
73 | *
74 | * @return {Object}
75 | */
76 | function convertLayout()
77 | {
78 | let result = {};
79 | for (let i=0; i 400)
95 | {
96 | let newLayout = convertLayout();
97 | let newGrid = {};
98 | Object.keys(props.tree.body.grid).forEach(k => {
99 | newGrid[k] = {
100 | ...props.tree.body.grid[k],
101 | layout: {
102 | x: newLayout[k].x,
103 | y: newLayout[k].y,
104 | w: newLayout[k].w,
105 | h: newLayout[k].h,
106 | }
107 | };
108 | });
109 | props.dispatch(actions.body.updateBlocks(newGrid));
110 | }
111 | timeStamp = [];
112 | break;
113 | }
114 | }
115 |
116 | /**
117 | * Render item
118 | *
119 | * @param {String} k
120 | * @return {Component}
121 | */
122 | renderItem(k)
123 | {
124 | const { props } = this;
125 | const { activeBlock, grid } = props.tree.body;
126 | const item = grid[k];
127 |
128 | let key = `${item.indexPrefix}__${k}`;
129 | let active = !!(activeBlock && activeBlock.length && activeBlock.indexOf(k) > -1);
130 |
131 | return (
132 | {
137 | event.stopPropagation();
138 | this._selectBlock(k, !!item.image);
139 | }}
140 | style={{ backgroundColor: item.color || props.setting.body.blockColor }}
141 | className={classNames({ 'ple-grid__item-active': active })}>
142 | {item.image && (
143 |
149 | )}
150 |
151 | );
152 | }
153 |
154 | render()
155 | {
156 | const { props } = this;
157 | const { setting } = props.tree.body;
158 | const bodyWidth = (setting.width * setting.column) +
159 | (setting.innerMargin * (setting.column-1)) +
160 | (setting.outerMargin * 2);
161 |
162 | return (
163 | this._selectBlock()}>
164 |
165 | this._updateBlocks('start')}
173 | onDragStop={(layout, oldItem, newItem, placeholder, e, element) => this._updateBlocks('end', layout, element)}
174 | onResizeStart={() => this._updateBlocks('start')}
175 | onResizeStop={(layout, oldItem, newItem, placeholder, e, element) => this._updateBlocks('end', layout, element)}
176 | style={{
177 | width: `100%`,
178 | backgroundColor: setting.bgColor
179 | }}
180 | className="ple-grid">
181 | {Object.keys(props.tree.body.grid).map((k) => this.renderItem(k))}
182 |
183 | {props.tree.cropper.visible ? ( ) : null}
184 |
185 |
186 | );
187 | }
188 |
189 | }
190 | GridLayout.displayName = 'GridLayout';
191 | GridLayout.defaultProps = {
192 | tree: null,
193 | dispatch: null,
194 | };
195 |
196 |
197 | export default connect((state) => Object.assign({}, state, {}))(GridLayout);
--------------------------------------------------------------------------------
/lib/Container/Side/ToggleSideButton/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireWildcard(require("react"));
11 |
12 | var _classnames = _interopRequireDefault(require("classnames"));
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
15 |
16 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
17 |
18 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
19 |
20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
21 |
22 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
23 |
24 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
25 |
26 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
27 |
28 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
29 |
30 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
31 |
32 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
33 |
34 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
35 |
36 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
37 |
38 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
39 |
40 | var ToggleSideButton = /*#__PURE__*/function (_Component) {
41 | _inherits(ToggleSideButton, _Component);
42 |
43 | var _super = _createSuper(ToggleSideButton);
44 |
45 | function ToggleSideButton() {
46 | _classCallCheck(this, ToggleSideButton);
47 |
48 | return _super.apply(this, arguments);
49 | }
50 |
51 | _createClass(ToggleSideButton, [{
52 | key: "render",
53 | value: function render() {
54 | var props = this.props;
55 | return /*#__PURE__*/_react["default"].createElement("button", {
56 | type: "button",
57 | onClick: props.onClick,
58 | className: "ple-side__toggle"
59 | }, /*#__PURE__*/_react["default"].createElement("span", null, /*#__PURE__*/_react["default"].createElement("i", {
60 | className: (0, _classnames["default"])('ple-sp-ico', 'ple-abs', {
61 | 'ple-ico-arrow-right': props.show,
62 | 'ple-ico-arrow-left': !props.show
63 | })
64 | }, "Toggle sidebar")));
65 | }
66 | }]);
67 |
68 | return ToggleSideButton;
69 | }(_react.Component);
70 |
71 | ToggleSideButton.displayName = 'ToggleSideButton';
72 | ToggleSideButton.defaultProps = {
73 | show: false,
74 | onClick: function onClick() {}
75 | };
76 | var _default = ToggleSideButton;
77 | exports["default"] = _default;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-photo-layout-editor
2 |
3 |
4 |
5 |
6 |
7 | 사진 레이아웃을 편집하는 웹 프로그램입니다.
8 | This is photo layout editor for react
9 |
10 | 예전 Instagram blog( http://blog.instagram.com/ )에 있는 정렬된 이미지의 모습에 매료되어 저런 모습을 직접 편집하여 게시물로 올렸으면 좋겠다는 생각이 들어 만들게 되었습니다.
11 | 블럭을 드래그 앤 드롭으로 위치와 크기를 편집하여 모던하게 정렬된 이미지나 레이아웃 만들 수 있습니다.
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Demo
19 |
20 | 다음 링크를 통하여 기능을 체험해볼 수 있습니다.
21 |
22 | https://redgoose-dev.github.io/react-photo-layout-editor/
23 |
24 |
25 | ## Feature
26 |
27 | PLE는 이런 특징들을 가지고 있습니다.
28 |
29 | ### Management Images
30 |
31 | 사이드바에 이미지를 업로드하여 사진을 배치하기 전에 이미지를 담아둘 수 있습니다.
32 |
33 | 
34 |
35 | ### Edit Blocks
36 |
37 | 블럭의 갯수나 사이즈, 여백등을 조절할 수 있습니다.
38 |
39 | 
40 |
41 | ### Drag \& Drop
42 |
43 | 이미지를 드래그하여 이미지를 블럭에 넣거나 블럭의 위치를 옮기거나 수정할 수 있습니다.
44 |
45 | ### Edit image area
46 |
47 | 블럭을 선택하고 펜 모양의 툴바(edit block)를 선택하면 편집창이 뜨면서 영역을 변경할 수 있습니다.
48 |
49 | 
50 |
51 | ### Change color
52 |
53 | 블럭의 배경색을 바꿀 수 있습니다. 빈 블럭을 만들고 색상을 수정할 수 있습니다.
54 |
55 | 
56 |
57 |
58 | ## Installation
59 |
60 | cli로 설치할 프로젝트에서 다음과 같은 명령을 실행합니다.
61 |
62 | ### npm
63 |
64 | `npm install --save react-photo-layout-editor`
65 |
66 | ### yarn
67 |
68 | `yarn add react-photo-layout-editor`
69 |
70 |
71 | ## Usage
72 |
73 | 원하는곳에 컴포넌트를 삽입합니다.
74 | 한페이지에 단독으로 사용하는것을 권장합니다.
75 |
76 | ```
77 | import PhotoLayoutEditor from 'react-photo-layout-editor';
78 |
79 | { ple = r }}/>
80 | ```
81 |
82 |
83 | ## Properties
84 |
85 | 컴포넌트를 마운트할때 설정값을 정의할 수 있습니다.
86 |
87 | | Name | default | Type | Description |
88 | | ---- | ------- | ---- | ----------- |
89 | | body | {} | `object` | 툴바와 그리드를 편집하는 영역. 아래 [body 항목](https://github.com/redgoose-dev/react-photo-layout-editor#body)을 참고 |
90 | | side | {} | `object` | 이미지를 관리하는 사이드 팔레트 영역. 아래 [side 항목](https://github.com/redgoose-dev/react-photo-layout-editor#side)을 참고 |
91 | | uploadScript | null | `string` | 이미지를 서버로 업로드 처리하는 주소 |
92 | | uploadParamsConvertFunc | null | `function` | 이미지를 서버로 업로드하고 그 결과값을 받아 `side.files`에 이미지를 등록할 수 있도록 값을 변경하는 콜백함수 |
93 | | updateStoreFunc | null | `function` | store 데이터가 변경될때마다 실행하는 콜백함수 |
94 | | callback | {} | `object` | 여러가지 행동에 대한 콜백 함수들의 모음 |
95 |
96 | ### body
97 |
98 | 툴바와 그리드 편집 영역
99 |
100 | | Name | default | Type | Description |
101 | | ---- | ------- | ---- | ----------- |
102 | | setting | {} | `object` | 그리드 편집기의 설정값. [setting 섹션](https://github.com/redgoose-dev/react-photo-layout-editor#bodysetting) 참고 |
103 | | blockColor | rgba(211,211,211,1) | `string` | 블럭 하나의 기본 배경색 |
104 | | grid | [] | `array` | 블럭 데이터값 목록 |
105 |
106 | #### body.setting
107 |
108 | 그리드 편집 영역의 설정값
109 |
110 | | Name | default | Type | Description |
111 | | ---- | ------- | ---- | ----------- |
112 | | width | 100 | `number` | 기본 블럭 하나의 가로사이즈 |
113 | | height | 100 | `number` | 기본 블럭 하나의 세로사이즈 |
114 | | column | 5 | `number` | 한줄에 들어가는 기본 블럭의 갯수 |
115 | | outerMargin | 10 | `number` | 그리드 겉부분의 여백값 |
116 | | innerMargin | 10 | `number` | 블럭 사이의 여백값 |
117 | | bgColor | rgba(255,255,255,1) | `string` | 그리드 배경색 |
118 |
119 | ### side
120 |
121 | 이미지를 관리하는 사이드 팔레트 영역
122 |
123 | | Name | default | Type | Description |
124 | | ---- | ------- | ---- | ----------- |
125 | | files | [] | `array` | 이미지 목록 |
126 | | visible | true | `boolean` | 팔레트 표시유무 |
127 |
128 | ### callback
129 |
130 | 컴포넌트 콜백 함수들
131 |
132 | | Name | params | Description |
133 | | ---- | ------ | ----------- |
134 | | init | | 컴포넌트가 초기화 되었을때 호출합니다. |
135 | | sideUploadStart | | 사이드 영역 이미지를 업로드를 시작할때 호출합니다. |
136 | | sideUploadProgress | `loaded,total,percent` | 사이드 영역 이미지를 업로드중일때 호출합니다. |
137 | | sideUploadComplete | `res` | 사이드 영역 이미지 하나를 업로드가 끝나면 호출합니다. |
138 | | sideUploadCompleteAll | | 사이드 영역 모든 이미지 업로드를 완료하면 호출합니다. |
139 | | sideUploadFail | | 사이드 영역 이미지 업로드를 실패하면 호출합니다. |
140 | | sideRemove | `images` | 사이드 영역 이미지를 삭제할때 호출합니다. |
141 |
142 |
143 | ## API
144 |
145 | `PhotoLayoutEditor`를 컨트롤할 수 있습니다. 먼저 컴포넌트를 접근할 수 있도록 인스턴스 변수로 만들어줍니다.
146 | 다음 컴포넌트와 같이 `ref`를 이용하여 `ple`이름의 변수를 이용하여 API를 사용할 수있습니다.
147 |
148 | ```
149 | let ple = null;
150 | { ple = r }}/>
151 | ```
152 |
153 | 자세한 API의 내용은 다음 링크를 참고하세요.
154 |
155 | - [Side](https://github.com/redgoose-dev/react-photo-layout-editor/wiki/API.Side)
156 | - [Grid](https://github.com/redgoose-dev/react-photo-layout-editor/wiki/API.Grid)
157 | - [Cropper](https://github.com/redgoose-dev/react-photo-layout-editor/wiki/API.Cropper)
158 | - [Util](https://github.com/redgoose-dev/react-photo-layout-editor/wiki/API.Util)
159 |
160 |
161 | ## Development
162 |
163 | 이 프로그램을 개발하기 위하여 데모 페이지를 띄울 수 있습니다.
164 | 다음 과정을 통하여 브라우저에 개발에 사용되었던 데모 페이지를 열어볼 수 있습니다.
165 |
166 | 1. `git clone https://github.com/redgoose-dev/react-photo-layout-editor.git`
167 | 1. `cd react-photo-layout-editor`
168 | 1. `yarn install`
169 | 1. `yarn run dev`
170 | 1. in your browser, connect `http://localhost:3000`
171 |
--------------------------------------------------------------------------------
/lib/Container/Side/Items/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | var _Item = _interopRequireDefault(require("./Item"));
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
15 |
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
17 |
18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
19 |
20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
21 |
22 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
23 |
24 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
25 |
26 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
27 |
28 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
29 |
30 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
31 |
32 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
33 |
34 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
35 |
36 | var Items = /*#__PURE__*/function (_React$Component) {
37 | _inherits(Items, _React$Component);
38 |
39 | var _super = _createSuper(Items);
40 |
41 | function Items() {
42 | _classCallCheck(this, Items);
43 |
44 | return _super.apply(this, arguments);
45 | }
46 |
47 | _createClass(Items, [{
48 | key: "render",
49 | value: function render() {
50 | var props = this.props;
51 | return /*#__PURE__*/_react["default"].createElement("div", {
52 | className: "ple-sideItems"
53 | }, /*#__PURE__*/_react["default"].createElement("div", {
54 | className: "ple-sideItems__wrap"
55 | }, /*#__PURE__*/_react["default"].createElement("ul", null, Object.keys(props.files).map(function (o) {
56 | return /*#__PURE__*/_react["default"].createElement(_Item["default"], {
57 | key: o,
58 | id: o,
59 | image: props.files[o].image,
60 | onDragStart: props.onDragStart,
61 | onDragEnd: props.onDragEnd,
62 | onTouchStart: props.onTouchStart,
63 | onTouchMove: props.onTouchMove,
64 | onTouchEnd: props.onTouchEnd,
65 | onClick: function onClick() {
66 | return props.onSelect(parseInt(o));
67 | },
68 | active: props.files[o].active
69 | });
70 | }), props.progress !== null && /*#__PURE__*/_react["default"].createElement("li", {
71 | className: "ple-sideItems__loading"
72 | }, /*#__PURE__*/_react["default"].createElement("div", {
73 | className: "ple-sideItems__progress"
74 | }, /*#__PURE__*/_react["default"].createElement("span", {
75 | className: "ple-sideItems__bar",
76 | style: {
77 | height: "".concat(props.progress, "%")
78 | }
79 | }), /*#__PURE__*/_react["default"].createElement("span", {
80 | className: "ple-sideItems__percent"
81 | }, "".concat(props.progress, "%")))))));
82 | }
83 | }]);
84 |
85 | return Items;
86 | }(_react["default"].Component);
87 |
88 | Items.displayName = 'Items';
89 | Items.defaultProps = {
90 | files: [],
91 | // files
92 | onSelect: function onSelect(key) {},
93 | // on select event
94 | onDragStart: null,
95 | // on drag start
96 | onDragEnd: null,
97 | // on drag end
98 | onTouchStart: null,
99 | // on touch start
100 | onTouchMove: null,
101 | // on touch move
102 | onTouchEnd: null,
103 | // on touch end
104 | progress: null // on progress number
105 |
106 | };
107 | var _default = Items;
108 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/reducers/body.js:
--------------------------------------------------------------------------------
1 | import * as types from '../actions/types';
2 | import * as libs from '../lib';
3 | import * as defaults from './defaults';
4 |
5 |
6 | let lastGridId = 0;
7 | let shuffleIndex = 0;
8 |
9 |
10 | /**
11 | * setting
12 | *
13 | * @param {Object} state
14 | * @param {*} action
15 | * @return {Object}
16 | */
17 | export function setting(state=defaults.setting.body.setting, action)
18 | {
19 | switch(action.type)
20 | {
21 | case types.INIT_PLE:
22 | try {
23 | return {
24 | ...state,
25 | ...action.preference.body.setting
26 | };
27 | } catch(e) {
28 | return state;
29 | }
30 |
31 | case types.GRID_SETTING_UPDATE:
32 | return {
33 | ...state,
34 | ...action.value,
35 | };
36 | }
37 | return state;
38 | }
39 |
40 | /**
41 | * grid
42 | *
43 | * @param {Object} state
44 | * @param {*} action
45 | * @return {Object}
46 | */
47 | export function grid(state={}, action)
48 | {
49 | let newState = Object.assign({}, state);
50 |
51 | switch (action.type)
52 | {
53 | case types.INIT_PLE:
54 | let grid = {};
55 | try {
56 | if ((action.preference.body.grid instanceof Array))
57 | {
58 | action.preference.body.grid.forEach((o, k) => {
59 | grid[k] = o;
60 | });
61 | }
62 | else if (typeof action.preference.body.grid === 'object')
63 | {
64 | grid = action.preference.body.grid;
65 | }
66 | else
67 | {
68 | throw 'error';
69 | }
70 | Object.keys(grid).forEach((o) => {
71 | newState[lastGridId++] = {
72 | color: defaults.setting.body.blockColor,
73 | ...grid[o],
74 | indexPrefix: shuffleIndex,
75 | };
76 | });
77 | } catch(e) {
78 | newState = {};
79 | }
80 | return newState;
81 |
82 | case types.GRID_ADD_BLOCK:
83 | newState[lastGridId++] = {
84 | color: defaults.setting.body.blockColor,
85 | layout: { x: Infinity, y: Infinity, w: 1, h: 1 },
86 | ...action.value,
87 | indexPrefix: shuffleIndex,
88 | };
89 | return newState;
90 |
91 | case types.GRID_REMOVE_BLOCK:
92 | if (!action.keys || !action.keys.length) return state;
93 | action.keys.forEach(o => {
94 | delete newState[o];
95 | });
96 | return newState;
97 |
98 | case types.GRID_SHUFFLE_BLOCKS:
99 | return Object.keys(newState).map((k) => {
100 | return {
101 | ...newState[k],
102 | layout: {
103 | x: libs.number.randomRange(0, action.value.x - 1),
104 | y: libs.number.randomRange(0, action.value.y - 1),
105 | w: libs.number.randomRange(1, action.value.w),
106 | h: libs.number.randomRange(1, action.value.h),
107 | },
108 | indexPrefix: shuffleIndex++,
109 | };
110 | });
111 |
112 | case types.GRID_DUPLICATE_BLOCK:
113 | action.keys.forEach(k => {
114 | if (!newState[k]) return;
115 | newState[lastGridId++] = Object.assign({}, newState[k]);
116 | });
117 | return newState;
118 |
119 | case types.GRID_CHANGE_COLOR:
120 | action.keys.forEach(k => {
121 | if (!newState[k]) return;
122 | newState[k].color = action.color;
123 | });
124 | return newState;
125 |
126 | case types.GRID_ATTACH_IMAGES:
127 | if (!libs.object.isArray(action.value)) return state;
128 |
129 | if (action.activeBlocks && action.activeBlocks.length)
130 | {
131 | Object.keys(newState).forEach((k) => {
132 | if (!action.value.length) return;
133 | if (action.activeBlocks.indexOf(k) === -1) return;
134 | newState[k].image = {
135 | src: action.value.splice(0,1)[0],
136 | position: '50% 50%',
137 | size: 'cover'
138 | };
139 | });
140 | }
141 | else
142 | {
143 | Object.keys(newState).forEach((k) => {
144 | if (newState[k].image) return;
145 | if (!action.value || !action.value.length) return;
146 | newState[k].image = {
147 | src: action.value.splice(0,1)[0],
148 | position: '50% 50%',
149 | size: 'cover'
150 | };
151 | });
152 | if (action.value.length)
153 | {
154 | action.value.forEach((o, k) => {
155 | newState[lastGridId++] = {
156 | color: defaults.setting.body.blockColor,
157 | layout: {
158 | x: (Object.keys(state).length + k) % action.columns,
159 | y: Infinity,
160 | w: 1,
161 | h: 1
162 | },
163 | image: {
164 | src: o,
165 | position: '50% 50%',
166 | size: 'cover',
167 | },
168 | indexPrefix: shuffleIndex
169 | };
170 | });
171 | }
172 | }
173 |
174 | return newState;
175 |
176 | case types.GRID_ATTACH_IMAGE:
177 | if (!(action.image && typeof action.image === 'string')) return state;
178 | if (!newState[action.keys]) return state;
179 |
180 | newState[action.keys].image = {
181 | src: action.image,
182 | position: '50% 50%',
183 | size: 'cover',
184 | };
185 | return newState;
186 |
187 | case types.GRID_UPDATE_BLOCKS:
188 | return Object.assign({}, state, action.value);
189 |
190 | case types.CROPPER_CLOSE:
191 | if (!newState[action.key]) return state;
192 | if (action.position)
193 | {
194 | newState[action.key].image.position = action.position;
195 | }
196 | if (action.size)
197 | {
198 | newState[action.key].image.size = action.size;
199 | }
200 | return newState;
201 | }
202 |
203 | return state;
204 | }
205 |
206 | /**
207 | * active block
208 | *
209 | * @param {Array} state
210 | * @param {*} action
211 | * @return {Array}
212 | */
213 | export function activeBlock(state=[], action)
214 | {
215 | let newState = Object.assign([], state);
216 |
217 | switch (action.type)
218 | {
219 | case types.GRID_ACTIVE_BLOCK:
220 | return action.value;
221 |
222 | case types.GRID_REMOVE_BLOCK:
223 | if (action.keys && action.keys.length)
224 | {
225 | action.keys.forEach(o => {
226 | if (newState.indexOf(o) < 0) return;
227 | newState.splice(newState.indexOf(o), 1);
228 | });
229 | return newState;
230 | }
231 | return [];
232 | }
233 |
234 | return state;
235 | }
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Body/Toolbar/EditLayoutSetting.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import $ from 'jquery/dist/jquery.slim';
3 | import ColorPicker from 'react-simple-colorpicker';
4 | import className from 'classnames';
5 |
6 |
7 | class EditLayoutSetting extends React.Component {
8 |
9 | constructor(props)
10 | {
11 | super(props);
12 | this.state = {
13 | ...props.defaultSetting,
14 | ...props.setting,
15 | popup_bgColor: false,
16 | };
17 | }
18 |
19 | activeBgColorPopup(sw, e)
20 | {
21 | const { state } = this;
22 | const cTarget = e ? e.currentTarget : null;
23 |
24 | sw = sw || !state.popup_bgColor;
25 |
26 | if (sw)
27 | {
28 | $(document).on('click.pleEditBgColor', (e) => {
29 | if ($(e.target).closest('.ple-edit-bgColor__popup').length) return;
30 | if (!(e.target === cTarget) && !(e.target.parentNode === cTarget))
31 | {
32 | this.activeBgColorPopup(false);
33 | }
34 | });
35 | }
36 | else
37 | {
38 | $(document).off('click.pleEditBgColor');
39 | }
40 |
41 | this.setState({ popup_bgColor: sw });
42 | }
43 |
44 | _submit(e)
45 | {
46 | e.preventDefault();
47 | this.props.submit(this.state);
48 | }
49 |
50 | _reset()
51 | {
52 | this.setState({
53 | ...this.props.defaultSetting
54 | });
55 | }
56 |
57 | _change(e)
58 | {
59 | let value = e.target.value || '';
60 | switch(e.target.type) {
61 | case 'text':
62 | this.setState({ [e.target.name]: value });
63 | break;
64 | case 'number':
65 | value = value || 0;
66 | this.setState({ [e.target.name]: parseInt(value) });
67 | break;
68 | }
69 | }
70 |
71 | _openBgColorPicker(e)
72 | {
73 | e.persist();
74 | this.activeBgColorPopup(null, e);
75 | }
76 |
77 | render()
78 | {
79 | const { state, props } = this;
80 |
81 | return (
82 |
194 | );
195 | }
196 |
197 | }
198 | EditLayoutSetting.displayName = 'EditLayoutSetting';
199 | EditLayoutSetting.defaultProps = {
200 | submit: (e) => {},
201 | setting: null,
202 | defaultSetting: {
203 | width: 100,
204 | height: 100,
205 | column: 5,
206 | outerMargin: 10,
207 | innerMargin: 10,
208 | bgColor: 'rgba(255,255,255,1)',
209 | },
210 | };
211 |
212 |
213 | export default EditLayoutSetting;
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Photo Layout Editor for React
9 |
10 |
11 |
12 |
13 |
14 |
20 |
21 |
22 |
23 |
45 |
46 | Grid
47 |
48 |
49 | get keys
50 | get blocks
51 | get preference
52 | set preference
53 | update
54 | add
55 | remove
56 | shuffle
57 | assign images
58 | assign image
59 | duplicate
60 | select
61 | unselect
62 | toggle select all
63 |
64 |
65 |
66 |
67 | Util
68 |
69 |
70 | Toggle side
71 |
72 |
73 | Export(side)
74 | Export(grid)
75 | Export(preference)
76 | Export(all)
77 | Make image
78 |
79 |
80 | Import(side)
81 | Import(grid)
82 | Import(preference)
83 | Import(all)
84 |
85 |
86 |
87 |
88 | Make image area
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/API/Grid.js:
--------------------------------------------------------------------------------
1 | import * as actions from '../actions';
2 |
3 |
4 | class Grid {
5 |
6 | constructor(store)
7 | {
8 | this.store = store;
9 | }
10 |
11 | /**
12 | * get keys in block
13 | *
14 | * @param {String} mode
15 | * @param {Array} keys
16 | * @return {Array}
17 | */
18 | getKeys(mode=null, keys=[])
19 | {
20 | const state = this.store.getState();
21 | const { body } = state.tree;
22 | let result = [];
23 |
24 | switch(mode)
25 | {
26 | case 'selected':
27 | return body.activeBlock;
28 | case 'value':
29 | result = [];
30 | keys.forEach((k) => {
31 | if (body.grid[k])
32 | {
33 | result.push(k);
34 | }
35 | });
36 | return result;
37 | case 'all':
38 | default:
39 | return Object.keys(body.grid).map(k => k);
40 | }
41 | }
42 |
43 | /**
44 | * get blocks
45 | *
46 | * @param {String} mode
47 | * @param {Array} keys
48 | * @return {Object}
49 | */
50 | getBlocks(mode=null, keys=[])
51 | {
52 | const state = this.store.getState();
53 | const { body } = state.tree;
54 | let selected = [];
55 | let result = {};
56 |
57 | switch(mode)
58 | {
59 | case 'selected':
60 | selected = this.getKeys('selected');
61 | break;
62 | case 'value':
63 | selected = this.getKeys('value', keys);
64 | break;
65 | case 'all':
66 | default:
67 | return body.grid;
68 | }
69 |
70 | selected.forEach(k => {
71 | if (!body.grid[k]) return;
72 | result[k] = body.grid[k];
73 | });
74 |
75 | return result;
76 | }
77 |
78 | /**
79 | * shuffle items
80 | *
81 | * @param {Object} options
82 | */
83 | shuffle(options={})
84 | {
85 | const state = this.store.getState();
86 | const { body } = state.tree;
87 | const defaultOptions = { x: body.setting.column, y: 2, w: 2, h: 2 };
88 |
89 | // assign options
90 | options = Object.assign({}, defaultOptions, options);
91 |
92 | this.store.dispatch(actions.body.shuffleBlocks(options));
93 | }
94 |
95 | /**
96 | * assign image
97 | *
98 | * @param {Array} images
99 | */
100 | assignImages(images=[])
101 | {
102 | const state = this.store.getState();
103 | const { body } = state.tree;
104 |
105 | this.store.dispatch(actions.body.attachImages(
106 | images,
107 | body.setting.column,
108 | body.activeBlock
109 | ));
110 | }
111 |
112 | /**
113 | * assign image
114 | *
115 | * @param {Number} key
116 | * @param {String} image
117 | */
118 | assignImage(key=null, image=null)
119 | {
120 | if (!(key !== null && image !== null)) return;
121 | this.store.dispatch(actions.body.attachImage(key, image));
122 | }
123 |
124 | /**
125 | * remove images
126 | *
127 | * @param {Array} keys
128 | */
129 | removeImages(keys=[])
130 | {
131 | const state = this.store.getState();
132 | let newGrid = Object.assign({}, state.tree.body.grid);
133 | keys.forEach(k => {
134 | newGrid[k].image = null;
135 | });
136 | this.update(newGrid);
137 | }
138 |
139 | /**
140 | * add blocks
141 | *
142 | * @param {Array} blocks
143 | */
144 | add(blocks=null)
145 | {
146 | const { getState, dispatch } = this.store;
147 | const state = getState();
148 | const { body } = state.tree;
149 | const defaultOptions = {
150 | layout: {
151 | x: Object.keys(body.grid).length % body.setting.column,
152 | y: Infinity,
153 | w: 1,
154 | h: 1
155 | },
156 | color: null,
157 | image: null
158 | };
159 |
160 | /**
161 | * add block
162 | *
163 | * @param {Object} options
164 | */
165 | function block(options={})
166 | {
167 | // assign option
168 | options = Object.assign({}, defaultOptions, options);
169 | options.layout = Object.assign({}, defaultOptions.layout, options.layout);
170 | options.image = options.image ? {
171 | src: null,
172 | position: '50% 50%',
173 | size: 'cover',
174 | ...options.image
175 | } : null;
176 |
177 | dispatch(actions.body.addBlock(options));
178 | }
179 |
180 | // checking blocks
181 | blocks = (blocks && blocks.length) ? blocks : [null];
182 |
183 | // play add blocks
184 | blocks.forEach(o => block(o));
185 | }
186 |
187 | /**
188 | * update blocks
189 | *
190 | * @param {Object} blocks
191 | */
192 | update(blocks={})
193 | {
194 | this.store.dispatch(actions.body.updateBlocks(blocks));
195 | }
196 |
197 | /**
198 | * remove blocks
199 | *
200 | * @param {Array} keys
201 | */
202 | remove(keys=[])
203 | {
204 | this.store.dispatch(actions.body.removeBlock(keys));
205 | }
206 |
207 | /**
208 | * select blocks
209 | *
210 | * @param {Array} keys
211 | */
212 | select(keys=[])
213 | {
214 | keys = keys.map(k => k.toString());
215 | this.store.dispatch(actions.body.activeBlock(keys));
216 | }
217 |
218 | /**
219 | * un select blocks
220 | *
221 | * @param {Array} keys
222 | */
223 | unselect(keys=[])
224 | {
225 | const state = this.store.getState();
226 | let newActiveBlock = Object.assign([], state.tree.body.activeBlock);
227 |
228 | keys.forEach(k => {
229 | k = k.toString();
230 | if (newActiveBlock.indexOf(k) !== -1)
231 | {
232 | newActiveBlock.splice(newActiveBlock.indexOf(k), 1);
233 | }
234 | });
235 | this.store.dispatch(actions.body.activeBlock(newActiveBlock));
236 | }
237 |
238 | /**
239 | * toggle select all blocks
240 | *
241 | * @param {Boolean} isSelect
242 | */
243 | toggleSelectAll(isSelect=null)
244 | {
245 | const state = this.store.getState();
246 |
247 | if (typeof isSelect !== 'boolean')
248 | {
249 | isSelect = !state.tree.body.activeBlock.length;
250 | }
251 | if (isSelect)
252 | {
253 | this.select(this.getKeys('all'));
254 | }
255 | else
256 | {
257 | this.select([]);
258 | }
259 | }
260 |
261 | /**
262 | * duplicate blocks
263 | *
264 | * @param {Array} keys
265 | */
266 | duplicate(keys=[])
267 | {
268 | this.store.dispatch(actions.body.duplicateBlock(keys));
269 | }
270 |
271 | /**
272 | * get preference
273 | *
274 | * @return {Object}
275 | */
276 | getPreference()
277 | {
278 | const state = this.store.getState();
279 | return state.tree.body.setting;
280 | }
281 |
282 | /**
283 | * set preference
284 | *
285 | * @param {Object} value
286 | */
287 | setPreference(value={})
288 | {
289 | let newPreference = Object.assign({}, this.getPreference(), value);
290 | this.store.dispatch(actions.body.updateSetting(newPreference));
291 | }
292 |
293 | }
294 |
295 |
296 | export default Grid;
--------------------------------------------------------------------------------
/lib/Container/Side/Navigation/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
13 |
14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
15 |
16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
17 |
18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
19 |
20 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
21 |
22 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
23 |
24 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
25 |
26 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
27 |
28 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
29 |
30 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
31 |
32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
33 |
34 | var SideNavigation = /*#__PURE__*/function (_React$Component) {
35 | _inherits(SideNavigation, _React$Component);
36 |
37 | var _super = _createSuper(SideNavigation);
38 |
39 | function SideNavigation(props) {
40 | var _this;
41 |
42 | _classCallCheck(this, SideNavigation);
43 |
44 | _this = _super.call(this, props);
45 | _this.comps = {
46 | inputFile: null
47 | };
48 | _this.state = {
49 | timestamp: Date.now()
50 | };
51 | return _this;
52 | }
53 | /**
54 | * Upload images
55 | *
56 | * @param {Event} e
57 | */
58 |
59 |
60 | _createClass(SideNavigation, [{
61 | key: "upload",
62 | value: function upload(e) {
63 | this.props.onUpload(e.target.files);
64 | this.setState({
65 | timestamp: Date.now()
66 | });
67 | }
68 | }, {
69 | key: "render",
70 | value: function render() {
71 | var _this2 = this;
72 |
73 | var props = this.props,
74 | state = this.state,
75 | comps = this.comps;
76 | return /*#__PURE__*/_react["default"].createElement("nav", {
77 | className: "ple-sideNavigation ple-side__navigation"
78 | }, /*#__PURE__*/_react["default"].createElement("div", {
79 | className: "ple-sideNavigation__wrap"
80 | }, /*#__PURE__*/_react["default"].createElement("button", {
81 | type: "button",
82 | title: "attach images",
83 | onClick: props.onAttach
84 | }, /*#__PURE__*/_react["default"].createElement("i", {
85 | className: "ple-sp-ico ple-ico-reply ple-abs"
86 | }, "Moving the image to grid block")), /*#__PURE__*/_react["default"].createElement("button", {
87 | type: "button",
88 | title: "toggle select",
89 | onClick: props.onToggleSelect
90 | }, /*#__PURE__*/_react["default"].createElement("i", {
91 | className: "ple-sp-ico ple-ico-select ple-abs"
92 | }, "Toggle all select")), /*#__PURE__*/_react["default"].createElement("span", {
93 | title: "upload images",
94 | key: state.timestamp
95 | }, /*#__PURE__*/_react["default"].createElement("input", {
96 | ref: function ref(r) {
97 | comps.inputFile = r;
98 | },
99 | type: "file",
100 | onChange: function onChange(e) {
101 | return _this2.upload(e);
102 | },
103 | multiple: true
104 | }), /*#__PURE__*/_react["default"].createElement("i", {
105 | className: "ple-sp-ico ple-ico-upload ple-abs"
106 | }, "upload images")), /*#__PURE__*/_react["default"].createElement("button", {
107 | type: "button",
108 | title: "remove images",
109 | onClick: props.onRemove
110 | }, /*#__PURE__*/_react["default"].createElement("i", {
111 | className: "ple-sp-ico ple-ico-trash ple-abs"
112 | }, "remove images"))));
113 | }
114 | }]);
115 |
116 | return SideNavigation;
117 | }(_react["default"].Component);
118 |
119 | SideNavigation.displayName = 'Navigation';
120 | SideNavigation.defaultProps = {
121 | onRemove: function onRemove() {},
122 | onToggleSelect: function onToggleSelect() {},
123 | onAttach: function onAttach() {},
124 | onUpload: function onUpload() {}
125 | };
126 | var _default = SideNavigation;
127 | exports["default"] = _default;
--------------------------------------------------------------------------------
/lib/Container/Side/Items/Item.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
4 |
5 | Object.defineProperty(exports, "__esModule", {
6 | value: true
7 | });
8 | exports["default"] = void 0;
9 |
10 | var _react = _interopRequireDefault(require("react"));
11 |
12 | var _classnames = _interopRequireDefault(require("classnames"));
13 |
14 | var lib = _interopRequireWildcard(require("../../../lib"));
15 |
16 | function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
17 |
18 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
19 |
20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
21 |
22 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
23 |
24 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
25 |
26 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
27 |
28 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
29 |
30 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
31 |
32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
33 |
34 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
35 |
36 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
37 |
38 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
39 |
40 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
41 |
42 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
43 |
44 | var Item = /*#__PURE__*/function (_React$Component) {
45 | _inherits(Item, _React$Component);
46 |
47 | var _super = _createSuper(Item);
48 |
49 | function Item() {
50 | _classCallCheck(this, Item);
51 |
52 | return _super.apply(this, arguments);
53 | }
54 |
55 | _createClass(Item, [{
56 | key: "render",
57 | value: function render() {
58 | var props = this.props;
59 | var attr = Object.assign({}, lib.util.isTouchDevice() ? {
60 | onTouchStart: props.onTouchStart,
61 | onTouchMove: props.onTouchMove,
62 | onTouchEnd: props.onTouchEnd
63 | } : {
64 | onDragStart: props.onDragStart,
65 | onDragEnd: props.onDragEnd
66 | });
67 | return /*#__PURE__*/_react["default"].createElement("li", null, /*#__PURE__*/_react["default"].createElement("span", _extends({
68 | type: "button",
69 | "data-id": props.id,
70 | "data-image": props.image,
71 | draggable: true,
72 | onClick: props.onClick
73 | }, attr, {
74 | style: {
75 | backgroundImage: "url('".concat(props.image, "')")
76 | },
77 | className: (0, _classnames["default"])({
78 | 'ple-sideItems__item-active': props.active
79 | })
80 | })));
81 | }
82 | }]);
83 |
84 | return Item;
85 | }(_react["default"].Component);
86 |
87 | Item.displayName = 'Item';
88 | Item.defaultProps = {
89 | image: null,
90 | // image
91 | id: null,
92 | // id
93 | active: null,
94 | // active item
95 | onClick: null,
96 | // on click item
97 | onDragStart: null,
98 | // on drag start
99 | onDragEnd: null,
100 | // on drag end
101 | onTouchStart: null,
102 | // on touch start
103 | onTouchMove: null,
104 | // on touch move
105 | onTouchEnd: null // on touch end
106 |
107 | };
108 | var _default = Item;
109 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/PhotoLayoutEditor/Container/Side/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import classNames from 'classnames';
4 | import $ from 'jquery/dist/jquery.slim';
5 | import * as actions from '../../actions';
6 | import ToggleSideButton from './ToggleSideButton';
7 | import Navigation from './Navigation';
8 | import Items from './Items';
9 | import * as lib from '../../lib';
10 | import selectItems from './selectItems';
11 |
12 |
13 | class Side extends React.Component {
14 |
15 | constructor(props)
16 | {
17 | super(props);
18 |
19 | this.dragTarget = null;
20 | this.dragPosition = [];
21 | this.$gridItems = null;
22 | this.$dragItem = null;
23 | this.uploading = false;
24 | }
25 |
26 | /**
27 | * get gridster item
28 | * 포인트 위치에 있는 gridster블럭을 가져온다.
29 | *
30 | * @return {Object} gridster item
31 | */
32 | getGridsterItem()
33 | {
34 | const { props } = this;
35 | let target = null;
36 | this.$gridItems = $(props.element).find('.ple-grid > div');
37 |
38 | this.$gridItems.each((n, el) => {
39 | const $this = $(el);
40 | const pos = $this.offset();
41 | if (pos.left < this.dragPosition[0] &&
42 | (pos.left + $this.width()) > this.dragPosition[0] &&
43 | pos.top < this.dragPosition[1] &&
44 | (pos.top + $this.height()) > this.dragPosition[1])
45 | {
46 | target = $this.data('key');
47 | return false;
48 | }
49 | });
50 |
51 | return target;
52 | }
53 |
54 | /**
55 | * On select items
56 | *
57 | * @param {Number} key
58 | */
59 | _selectItem(key)
60 | {
61 | const { props } = this;
62 | let selected = selectItems(props, key);
63 | props.api.side.select(selected);
64 | }
65 |
66 | /**
67 | * Remove items
68 | */
69 | _removeItems()
70 | {
71 | const { props } = this;
72 | let keys = props.api.side.getKeys('selected');
73 |
74 | if (keys.length)
75 | {
76 | if (confirm('Do you really want to delete it?'))
77 | {
78 | props.api.side.remove(keys);
79 | }
80 | }
81 | else
82 | {
83 | if (!confirm('Delete all?')) return;
84 | keys = props.api.side.getKeys('all');
85 | props.api.side.remove(keys);
86 | }
87 | }
88 |
89 | /**
90 | * upload
91 | *
92 | * @param {FileList} files
93 | */
94 | _upload(files)
95 | {
96 | const { props } = this;
97 | props.api.side.upload(files);
98 | }
99 |
100 | /**
101 | * Attach images to grid
102 | */
103 | _attach()
104 | {
105 | try
106 | {
107 | let keys = this.props.api.side.getKeys('selected');
108 | let result = this.props.api.side.attachToGrid(keys);
109 | if (result) throw result;
110 | }
111 | catch(e)
112 | {
113 | alert(e.message);
114 | }
115 | }
116 |
117 | _dragStartItem(evt)
118 | {
119 | const { props } = this;
120 |
121 | // for firefox
122 | evt.dataTransfer.setData('text/plain', null);
123 |
124 | this.$gridItems = $(props.element).find('.ple-grid > div');
125 | this.$gridItems.on('dragover', (e) => {
126 | e.preventDefault();
127 | if ($(e.currentTarget).hasClass('ple-grid__item-hover')) return;
128 | $(e.currentTarget).addClass('ple-grid__item-hover');
129 | }).on('dragleave', (e) => {
130 | e.preventDefault();
131 | $(e.currentTarget).removeClass('ple-grid__item-hover');
132 | }).on('drop', (e) => {
133 | e.preventDefault();
134 | $(e.currentTarget).removeClass('ple-grid__item-hover');
135 | this.dragTarget = $(e.currentTarget).data('key');
136 | });
137 | }
138 | _dragEndItem(e)
139 | {
140 | const { props } = this;
141 |
142 | this.$gridItems.off();
143 | this.$gridItems = null;
144 |
145 | // check drag target
146 | if (this.dragTarget === null) return;
147 |
148 | // play redux
149 | props.dispatch(actions.body.attachImage(
150 | this.dragTarget,
151 | $(e.currentTarget).data('image')
152 | ));
153 |
154 | // empty dragTarget
155 | this.dragTarget = null;
156 | }
157 | _touchStartItem(e)
158 | {
159 | this.$dragItem = $(e.currentTarget)
160 | .clone()
161 | .removeAttr('draggable')
162 | .addClass('ple-side__placeholder')
163 | .width($(e.currentTarget).width())
164 | .height($(e.currentTarget).height());
165 |
166 | $('body').append(this.$dragItem);
167 | }
168 | _touchMoveItem(e)
169 | {
170 | if (!lib.util.checkSupportCss('touch-action', 'pan-y'))
171 | {
172 | e.preventDefault();
173 | }
174 |
175 | let touch = e.nativeEvent.touches[0];
176 | this.dragPosition = [touch.pageX, touch.pageY];
177 | this.$dragItem.css({
178 | left: touch.pageX - (this.$dragItem.width() * 0.5),
179 | top: touch.pageY - (this.$dragItem.height() * 0.5)
180 | });
181 | }
182 | _touchEndItem(e)
183 | {
184 | const { props } = this;
185 |
186 | this.$dragItem.remove();
187 | this.$dragItem = null;
188 |
189 | if (this.dragPosition.length > 0)
190 | {
191 | this.dragTarget = this.getGridsterItem();
192 |
193 | // check drag target
194 | if (this.dragTarget === null) return;
195 |
196 | // play redux
197 | props.dispatch(actions.body.attachImage(
198 | this.dragTarget,
199 | $(e.currentTarget).data('image')
200 | ));
201 | this.dragPosition = [];
202 | }
203 | }
204 |
205 | render()
206 | {
207 | const { props } = this;
208 |
209 | return (
210 |
211 |
215 | props.api.side.toggleSelectAll(false)}
217 | className="ple-side__background"/>
218 | props.api.util.toggleSide(undefined)}/>
221 | this._attach()}
223 | onToggleSelect={() => props.api.side.toggleSelectAll()}
224 | onUpload={(e) => this._upload(e)}
225 | onRemove={() => this._removeItems()}/>
226 | this._selectItem(e)}
229 | onDragStart={(e) => this._dragStartItem(e)}
230 | onDragEnd={(e) => this._dragEndItem(e)}
231 | onTouchStart={(e) => this._touchStartItem(e)}
232 | onTouchMove={(e) => this._touchMoveItem(e)}
233 | onTouchEnd={(e) => this._touchEndItem(e)}
234 | progress={props.tree.side.progressPercent}/>
235 |
236 |
237 | );
238 | }
239 |
240 | }
241 | Side.displayName = 'Side';
242 | Side.defaultProps = {
243 | tree: {}, // data tree in reduce
244 | setting: {}, // setting in reduce
245 | api: {}, // api
246 | dispatch: null, // redux dispatch
247 | };
248 |
249 |
250 | export default connect((state) => Object.assign({}, state))(Side);
251 |
--------------------------------------------------------------------------------