UNPKG

@print-one/grapesjs

Version:

Free and Open Source Web Builder Framework

486 lines (439 loc) 12.9 kB
/** * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/GrapesJS/grapesjs/blob/master/src/asset_manager/config/config.ts) * ```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) * * [getContainer](#getcontainer) * * [Asset]: asset.html * * @module Assets */ import { debounce, isFunction } from 'underscore'; import { ItemManagerModule } from '../abstract/Module'; import EditorModel from '../editor/model/Editor'; import defaults, { AssetManagerConfig } from './config/config'; import Asset from './model/Asset'; import Assets from './model/Assets'; import AssetsView from './view/AssetsView'; import FileUploaderView from './view/FileUploader'; export type AssetEvent = | 'asset' | 'asset:open' | 'asset:close' | 'asset:add' | 'asset:remove' | 'asset:update' | 'asset:custom' | 'asset:upload:start' | 'asset:upload:end' | 'asset:upload:error' | 'asset:upload:response'; 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`; const assetCmd = 'open-assets'; const assetEvents = { 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, }; // TODO type AssetProps = Record<string, any>; type OpenOptions = { select?: (asset: Asset, complete: boolean) => void; types?: string[]; accept?: string; target?: any; }; export default class AssetManager extends ItemManagerModule<AssetManagerConfig, Assets> { storageKey = 'assets'; Asset = Asset; Assets = Assets; assetsVis: Assets; am?: AssetsView; fu?: FileUploaderView; _bhv?: any; events!: typeof assetEvents; /** * Initialize module * @param {Object} config Configurations * @private */ constructor(em: EditorModel) { // @ts-ignore super(em, 'AssetManager', new Assets([], em), assetEvents, defaults); const { all, config } = this; // @ts-ignore this.assetsVis = new Assets([]); // @ts-ignore const ppfx = config.pStylePrefix; if (ppfx) { config.stylePrefix = `${ppfx}${config.stylePrefix}`; } // Setup the sync between the global and public collections all.on('add', (model: Asset) => this.getAllVisible().add(model)); all.on('remove', (model: Asset) => this.getAllVisible().remove(model)); this.__onAllEvent = debounce(() => this.__trgCustom(), 0); return this; } __propEv(ev: string, ...data: any[]) { this.em.trigger(ev, ...data); this.getAll().trigger(ev, ...data); } __trgCustom() { const bhv = this.__getBehaviour(); const custom = this.getConfig().custom; if (!bhv.container && !(custom as any).open) { return; } this.em.trigger(this.events.custom, this.__customData()); } __customData() { const bhv = this.__getBehaviour(); return { am: this as AssetManager, open: this.isOpen(), assets: this.getAll().models, types: bhv.types || [], container: bhv.container, close: () => this.close(), remove: (asset: string | Asset, opts?: Record<string, any>) => this.remove(asset, opts), select: (asset: Asset, complete: boolean) => { 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: OpenOptions = {}) { const cmd = this.em.Commands; cmd.run(assetCmd, { types: ['image'], select: () => {}, ...options, }); } /** * Close the asset manager. * @example * assetManager.close(); */ close() { const cmd = this.em.Commands; cmd.stop(assetCmd); } /** * Checks if the asset manager is open * @returns {Boolean} * @example * assetManager.isOpen(); // true | false */ isOpen() { const cmd = this.em.Commands; return !!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: string | AssetProps | (string | AssetProps)[], opts: Record<string, any> = {}) { // Put the model at the beginning if (typeof opts.at == 'undefined') { opts.at = 0; } return this.all.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: string): Asset | null { return this.all.where({ src })[0] || null; } /** * Return the global collection, containing all the assets * @returns {Collection<[Asset]>} */ getAll() { return this.all; } /** * Return the visible collection, which contains assets actually rendered * @returns {Collection<[Asset]>} */ getAllVisible() { return this.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: string | Asset, opts?: Record<string, any>) { return this.__remove(asset, opts); } store() { return this.getProjectData(); } load(data: Record<string, any>) { return this.loadProjectData(data); } /** * Return the Asset Manager Container * @returns {HTMLElement} */ getContainer() { const bhv = this.__getBehaviour(); return bhv.container || this.am?.el; } /** * Get assets element container * @returns {HTMLElement} * @private */ getAssetsEl() { return this.am?.el.querySelector('[data-el=assets]'); } /** * Render assets * @param {array} assets Assets to render, without the argument will render all global assets * @returns {HTMLElement} * @private * @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?: Asset[]) { if (this.getConfig().custom) return; const toRender = assts || this.getAll().models; if (!this.am) { const obj = this.__viewParams(); obj.fu = this.FileUploader(); this.am = new AssetsView({ ...obj }); this.am.render(); } this.assetsVis.reset(toRender); return this.getContainer(); } __viewParams() { return { collection: this.assetsVis, // Collection visible in asset manager globalCollection: this.all, config: this.config, module: this as AssetManager, fu: undefined as any, }; } /** * 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: string, definition: any) { this.getAll().addType(id, definition); } /** * Get type * @param {string} id Type ID * @returns {Object} Type definition * @private */ getType(id: string) { return this.getAll().getType(id); } /** * Get types * @returns {Array} * @private */ getTypes() { return this.getAll().getTypes(); } //------- AssetsView() { return this.am; } FileUploader() { if (!this.fu) { this.fu = new FileUploaderView(this.__viewParams()); } return this.fu; } onLoad() { this.getAll().reset(this.config.assets); const { em, events } = this; em.on(`run:${assetCmd}`, () => this.__propEv(events.open)); em.on(`stop:${assetCmd}`, () => this.__propEv(events.close)); } postRender(editorView: any) { this.config.dropzone && this.fu?.initDropzone(editorView); } /** * Set new target * @param {Object} m Model * @private * */ setTarget(m: any) { this.assetsVis.target = m; } /** * Set callback after asset was selected * @param {Object} f Callback function * @private * */ onSelect(f: any) { this.assetsVis.onSelect = f; } /** * Set callback to fire when the asset is clicked * @param {function} func * @private */ onClick(func: any) { // @ts-ignore this.config.onClick = func; } /** * Set callback to fire when the asset is double clicked * @param {function} func * @private */ onDblClick(func: any) { // @ts-ignore this.config.onDblClick = func; } __behaviour(opts = {}) { return (this._bhv = { ...(this._bhv || {}), ...opts, }); } __getBehaviour(opts = {}) { return this._bhv || {}; } destroy() { this.all.stopListening(); this.all.reset(); this.assetsVis.stopListening(); this.assetsVis.reset(); this.fu?.remove(); this.am?.remove(); this._bhv = {}; } }