UNPKG

grapesjs-clot

Version:

Free and Open Source Web Builder Framework

511 lines (456 loc) 14.1 kB
/** * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/asset_manager/config/config.js) * ```js * const editor = grapesjs.init({ * assetManager: { * // options * } * }) * ``` * * Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance * * ```js * const assetManager = editor.AssetManager; * ``` * * ## Available Events * * `asset:open` - Asset Manager opened. * * `asset:close` - Asset Manager closed. * * `asset:add` - Asset added. The [Asset] is passed as an argument to the callback. * * `asset:remove` - Asset removed. The [Asset] is passed as an argument to the callback. * * `asset:update` - Asset updated. The updated [Asset] and the object containing changes are passed as arguments to the callback. * * `asset:upload:start` - Before the upload is started. * * `asset:upload:end` - After the upload is ended. * * `asset:upload:error` - On any error in upload, passes the error as an argument. * * `asset:upload:response` - On upload response, passes the result as an argument. * * `asset` - Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback. * * `asset:custom` - Event for handling custom Asset Manager UI. * * ## Methods * * [open](#open) * * [close](#close) * * [isOpen](#isopen) * * [add](#add) * * [get](#get) * * [getAll](#getall) * * [getAllVisible](#getallvisible) * * [remove](#remove) * * [store](#store) * * [load](#load) * * [getContainer](#getcontainer) * * [Asset]: asset.html * * @module AssetManager */ import { debounce, isFunction } from 'underscore'; import Module from 'common/module'; import defaults from './config/config'; import Asset from './model/Assets'; import Assets from './model/Assets'; import AssetsView from './view/AssetsView'; import FileUpload from './view/FileUploader'; export const evAll = 'asset'; export const evPfx = `${evAll}:`; export const evSelect = `${evPfx}select`; export const evUpdate = `${evPfx}update`; export const evAdd = `${evPfx}add`; export const evRemove = `${evPfx}remove`; export const evRemoveBefore = `${evRemove}:before`; export const evCustom = `${evPfx}custom`; export const evOpen = `${evPfx}open`; export const evClose = `${evPfx}close`; export const evUpload = `${evPfx}upload`; export const evUploadStart = `${evUpload}:start`; export const evUploadEnd = `${evUpload}:end`; export const evUploadError = `${evUpload}:error`; export const evUploadRes = `${evUpload}:response`; export default () => { let c = {}; let assets, assetsVis, am, fu; const assetCmd = 'open-assets'; return { ...Module, name: 'AssetManager', storageKey: 'assets', Asset, Assets, am, events: { all: evAll, select: evSelect, update: evUpdate, add: evAdd, remove: evRemove, removeBefore: evRemoveBefore, custom: evCustom, open: evOpen, close: evClose, uploadStart: evUploadStart, uploadEnd: evUploadEnd, uploadError: evUploadError, uploadResponse: evUploadRes, }, init(config = {}) { c = { ...defaults, ...config }; const ppfx = c.pStylePrefix; const { em } = c; this.config = c; this.em = em; if (ppfx) { c.stylePrefix = ppfx + c.stylePrefix; } // Global assets collection assets = new Assets([]); assetsVis = new Assets([]); this.all = assets; this.__initListen(); // Setup the sync between the global and public collections assets.on('add', model => this.getAllVisible().add(model)); assets.on('remove', model => this.getAllVisible().remove(model)); return this; }, __propEv(ev, ...data) { this.em.trigger(ev, ...data); this.getAll().trigger(ev, ...data); }, __onAllEvent: debounce(function () { this.__trgCustom(); }), __trgCustom() { const bhv = this.__getBehaviour(); if (!bhv.container && !this.getConfig('custom').open) { return; } this.em.trigger(this.events.custom, this.__customData()); }, __customData() { const bhv = this.__getBehaviour(); return { am: this, open: this.isOpen(), assets: this.getAll().models, types: bhv.types || [], container: bhv.container, close: () => this.close(), remove: (...args) => this.remove(...args), select: (asset, complete) => { const res = this.add(asset); isFunction(bhv.select) && bhv.select(res, complete); }, // extra options: bhv.options || {}, }; }, /** * Open the asset manager. * @param {Object} [options] Options for the asset manager. * @param {Array<String>} [options.types=['image']] Types of assets to show. * @param {Function} [options.select] Type of operation to perform on asset selection. If not specified, nothing will happen. * @example * assetManager.open({ * select(asset, complete) { * const selected = editor.getSelected(); * if (selected && selected.is('image')) { * selected.addAttributes({ src: asset.getSrc() }); * // The default AssetManager UI will trigger `select(asset, false)` on asset click * // and `select(asset, true)` on double-click * complete && assetManager.close(); * } * } * }); * // with your custom types (you should have assets with those types declared) * assetManager.open({ types: ['doc'], ... }); */ open(options = {}) { const cmd = this.em.get('Commands'); cmd.run(assetCmd, { types: ['image'], select: () => {}, ...options, }); }, /** * Close the asset manager. * @example * assetManager.close(); */ close() { const cmd = this.em.get('Commands'); cmd.stop(assetCmd); }, /** * Checks if the asset manager is open * @returns {Boolean} * @example * assetManager.isOpen(); // true | false */ isOpen() { const cmd = this.em.get('Commands'); return !!(cmd && cmd.isActive(assetCmd)); }, /** * Add new asset/s to the collection. URLs are supposed to be unique * @param {String|Object|Array<String>|Array<Object>} asset URL strings or an objects representing the resource. * @param {Object} [opts] Options * @returns {[Asset]} * @example * // As strings * assetManager.add('http://img.jpg'); * assetManager.add(['http://img.jpg', './path/to/img.png']); * * // Using objects you can indicate the type and other meta informations * assetManager.add({ * // type: 'image', // image is default * src: 'http://img.jpg', * height: 300, * width: 200, * }); * assetManager.add([{ src: 'img2.jpg' }, { src: 'img2.png' }]); */ add(asset, opts = {}) { // Put the model at the beginning if (typeof opts.at == 'undefined') { opts.at = 0; } return assets.add(asset, opts); }, /** * Return asset by URL * @param {String} src URL of the asset * @returns {[Asset]|null} * @example * const asset = assetManager.get('http://img.jpg'); */ get(src) { return assets.where({ src })[0] || null; }, /** * Return the global collection, containing all the assets * @returns {Collection<[Asset]>} */ getAll() { return assets; }, /** * Return the visible collection, which contains assets actually rendered * @returns {Collection<[Asset]>} */ getAllVisible() { return assetsVis; }, /** * Remove asset * @param {String|[Asset]} asset Asset or asset URL * @returns {[Asset]} Removed asset * @example * const removed = assetManager.remove('http://img.jpg'); * // or by passing the Asset * const asset = assetManager.get('http://img.jpg'); * assetManager.remove(asset); */ remove(asset, opts) { return this.__remove(asset, opts); }, /** * Store assets data to the selected storage * @param {Boolean} noStore If true, won't store * @returns {Object} Data to store * @example * var assets = assetManager.store(); */ store(noStore) { const obj = {}; const assets = JSON.stringify(this.getAll().toJSON()); obj[this.storageKey] = assets; if (!noStore && c.stm) c.stm.store(obj); return obj; }, /** * Load data from the passed object. * The fetched data will be added to the collection. * @param {Object} data Object of data to load * @returns {Object} Loaded assets * @example * var assets = assetManager.load({ * assets: [...] * }) * */ load(data = {}) { const name = this.storageKey; let assets = data[name] || []; if (typeof assets == 'string') { try { assets = JSON.parse(data[name]); } catch (err) {} } if (assets && assets.length) { this.getAll().reset(assets); } return assets; }, /** * Return the Asset Manager Container * @returns {HTMLElement} */ getContainer() { const bhv = this.__getBehaviour(); return bhv.container || (am && am.el); }, /** * Get assets element container * @returns {HTMLElement} * @private */ getAssetsEl() { return am.el.querySelector('[data-el=assets]'); }, /** * Render assets * @param {array} assets Assets to render, without the argument will render all global assets * @returns {HTMLElement} * @example * // Render all assets * assetManager.render(); * * // Render some of the assets * const assets = assetManager.getAll(); * assetManager.render(assets.filter( * asset => asset.get('category') == 'cats' * )); */ render(assts) { if (this.getConfig('custom')) return; const toRender = assts || this.getAll().models; if (!am) { const obj = this.__viewParams(); obj.fu = this.FileUploader(); const el = am && am.el; am = new AssetsView({ el, ...obj, }); am.render(); } assetsVis.reset(toRender); return this.getContainer(); }, __viewParams() { return { collection: assetsVis, // Collection visible in asset manager globalCollection: assets, config: c, module: this, }; }, /** * Add new type. If you want to get more about type definition we suggest to read the [module's page](/modules/Assets.html) * @param {string} id Type ID * @param {Object} definition Definition of the type. Each definition contains * `model` (business logic), `view` (presentation logic) * and `isType` function which recognize the type of the * passed entity * @private * @example * assetManager.addType('my-type', { * model: {}, * view: {}, * isType: (value) => {}, * }) */ addType(id, definition) { this.getAll().addType(id, definition); }, /** * Get type * @param {string} id Type ID * @returns {Object} Type definition * @private */ getType(id) { return this.getAll().getType(id); }, /** * Get types * @returns {Array} * @private */ getTypes() { return this.getAll().getTypes(); }, //------- AssetsView() { return am; }, FileUploader() { if (!fu) { fu = new FileUpload(this.__viewParams()); } return fu; }, onLoad() { this.getAll().reset(c.assets); const { em, events } = this; em.on(`run:${assetCmd}`, () => this.__propEv(events.open)); em.on(`stop:${assetCmd}`, () => this.__propEv(events.close)); }, postRender(editorView) { c.dropzone && fu && fu.initDropzone(editorView); }, /** * Set new target * @param {Object} m Model * @private * */ setTarget(m) { assetsVis.target = m; }, /** * Set callback after asset was selected * @param {Object} f Callback function * @private * */ onSelect(f) { assetsVis.onSelect = f; }, /** * Set callback to fire when the asset is clicked * @param {function} func * @private */ onClick(func) { c.onClick = func; }, /** * Set callback to fire when the asset is double clicked * @param {function} func * @private */ onDblClick(func) { c.onDblClick = func; }, __behaviour(opts = {}) { return (this._bhv = { ...(this._bhv || {}), ...opts, }); }, __getBehaviour(opts = {}) { return this._bhv || {}; }, destroy() { assets.stopListening(); assetsVis.stopListening(); assets.reset(); assetsVis.reset(); fu && fu.remove(); am && am.remove(); [assets, assetsVis, am, fu].forEach(i => (i = null)); this._bhv = {}; this.all = {}; c = {}; }, }; };