diff --git a/gulpfile.js b/gulpfile.js index 105c245..ba9da19 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -85,7 +85,7 @@ gulp.task(`build-scripts`, () => { json(), babel({ babelrc: false, - exclude: [`node_modules/**`, `js/tests/**`], + exclude: [`node_modules/**`, `src/tests/**`], presets: [ [`@babel/preset-env`, {modules: false, useBuiltIns: `entry`}] ] @@ -115,7 +115,7 @@ gulp.task(`build-prompt`, () => { json(), babel({ babelrc: false, - exclude: [`node_modules/**`, `js/tests/**`], + exclude: [`node_modules/**`, `src/tests/**`], presets: [ [`@babel/preset-env`, {modules: false}] ], diff --git a/sass/console-blocks/c-html.scss b/sass/console-blocks/c-html.scss new file mode 100644 index 0000000..3ee2b8a --- /dev/null +++ b/sass/console-blocks/c-html.scss @@ -0,0 +1,31 @@ +.c-html { + &__tag { + color: #a894a6; + } + + &__tag-name { + color: #881280; + } + + &__attr-name { + color: #994500; + } + + &__attr-value, + &__id { + color: #1a1aa6; + } + + &__comment, + &__doctype { + color: slategray; + } + + // &__punctuation { + // color: #999999; + // } + + &__link { + color: #4855cc; + } +} diff --git a/sass/console-blocks/console.scss b/sass/console-blocks/console.scss index a84e149..ed056ba 100644 --- a/sass/console-blocks/console.scss +++ b/sass/console-blocks/console.scss @@ -1,5 +1,5 @@ .console { - font: 0.8em/normal "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo", "Courier", "Courier New", monospace; + font: 0.8em/normal "Menlo", "Monaco", "Consolas", "Courier New", monospace; line-height: 1.4; cursor: default; diff --git a/sass/style.scss b/sass/style.scss index 4ab696e..802b5cd 100644 --- a/sass/style.scss +++ b/sass/style.scss @@ -31,6 +31,7 @@ __elem — element @import "console-blocks/grey"; @import "console-blocks/error"; @import "console-blocks/nowrap"; +@import "console-blocks/c-html"; // Prompt blocks @import "prompt-blocks/prompt"; diff --git a/src/array/array-view.js b/src/array/array-view.js index 6c48ee1..da50c81 100644 --- a/src/array/array-view.js +++ b/src/array/array-view.js @@ -1,11 +1,14 @@ import TypeView from '../type-view'; +import EntryView from '../entry-view'; import MapEntryView from '../object/map-entry-view'; import {getElement} from '../utils'; -import {Mode, ViewType} from '../enums'; +import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums'; const EMPTY = `empty`; const MULTIPLY_SIGN = `×`; +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); + export default class ArrayView extends TypeView { constructor(params, cons) { super(params, cons); @@ -14,6 +17,8 @@ export default class ArrayView extends TypeView { if (!params.parentView) { this.rootView = this; } + + this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); } get template() { @@ -42,7 +47,7 @@ export default class ArrayView extends TypeView { this._state.isOpened = this.isOpeningAllowed; } - _getStateDescriptors() { + [getStateDescriptorsKey]() { const self = this; return { set isHeadContentShowed(bool) { @@ -139,7 +144,7 @@ export default class ArrayView extends TypeView { } if (inHead && emptyCount !== 0 && (hasKey || i === l - 1)) { TypeView.appendEntryElIntoFragment( - this._createEntryEl({key, el: getElement(`${EMPTY}${emptyCount > 1 ? ` ${MULTIPLY_SIGN} ${emptyCount}` : ``}`), withoutKey: true}), + new EntryView({key, entryEl: getElement(`${EMPTY}${emptyCount > 1 ? ` ${MULTIPLY_SIGN} ${emptyCount}` : ``}`), withoutKey: true}).el, fragment ); if (inHead && countEntriesWithoutKeys) { @@ -150,9 +155,9 @@ export default class ArrayView extends TypeView { if (hasKey) { if (isMapEntriesSpecialValue) { const pair = arr[i]; - const el = new MapEntryView({val: pair, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el; + const entryEl = new MapEntryView({val: pair, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el; TypeView.appendEntryElIntoFragment( - this._createEntryEl({key, el, withoutKey: inHead}), + new EntryView({key, entryEl, withoutKey: inHead}).el, fragment ); } else { diff --git a/src/base-view.js b/src/base-view.js new file mode 100644 index 0000000..429d5b6 --- /dev/null +++ b/src/base-view.js @@ -0,0 +1,285 @@ +import AbstractView from './abstract-view'; +import {Mode, GET_STATE_DESCRIPTORS_KEY_NAME} from './enums'; + +const DEFAULT_DEPTH = 1; + +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); + +export default class BaseView extends AbstractView { + constructor(params, cons) { + super(); + if (params.parentView) { + this._parentView = params.parentView; + this.rootView = params.parentView.rootView; + } + this._viewTypeParams = void 0; + this._console = cons; + this._value = params.val; + this._mode = params.mode; + this._type = params.type; + this._currentDepth = params.depth ? params.depth : DEFAULT_DEPTH; + + this._cache = {}; + + this._stateDescriptorsQueue = []; + this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); + } + + /** + * @abstract + * @protected + */ + _afterRender() {} + + _bind() { + if (!this.viewType) { + throw new Error(`this.viewType must be specified`); + } + if (!this.rootView) { + throw new Error(`this.rootView must be specified`); + } + + this._headEl = this.el.querySelector(`.head`); + this._headContentEl = this.el.querySelector(`.head__content`); + this._infoEl = this.el.querySelector(`.info`); + this._contentEl = this.el.querySelector(`.item__content`); + + this._afterRender(); + } + + get protoConstructorName() { + if (!this._cache.protoConstructorName) { + const proto = Object.getPrototypeOf(this._value); + this._cache.protoConstructorName = proto && proto.hasOwnProperty(`constructor`) ? + proto.constructor.name : `Object`; + } + return this._cache.protoConstructorName; + } + + get stringTagName() { + if (!this._cache.stringTagName) { + const stringTag = Object.prototype.toString.call(this._value); + this._cache.stringTagName = stringTag.substring(8, stringTag.length - 1); + } + return this._cache.stringTagName; + } + + set stringTagName(val) { + if (!this._cache.stringTagName) { + this._cache.stringTagName = val; + } + } + + get value() { + return this._value; + } + + get parentView() { + return this._parentView; + } + + /** + * Current state + * @type {{}} + * @param {{}} params — object with values which will be assigned throught setters + */ + get _state() { + if (!this._viewState) { + this._viewState = {}; + this._stateDescriptorsQueue.forEach((descriptorsObj) => { + + Object.defineProperties( + this._viewState, + Object.getOwnPropertyDescriptors(descriptorsObj) + ); + }); + Object.seal(this._viewState); + } + return this._viewState; + } + + /** + * @return {{}} — object that contains descriptors only + */ + [getStateDescriptorsKey]() { + const self = this; + return { + set isShowInfo(bool) { + if (!self._infoEl) { + return; + } + if (bool && !self._infoEl.textContent) { + self._infoEl.textContent = self.info; + } + self._isShowInfo = self.toggleInfoShowed(bool); + }, + get isShowInfo() { + return self._isShowInfo; + }, + set isHeadContentShowed(bool) { + self.toggleHeadContentShowed(bool); + }, + set isOpeningDisabled(bool) { + if (!bool && self._mode === Mode.PREVIEW) { + throw new Error(`Enabling opening object in preview mode is forbidden`); + } + if (self._isOpeningDisabled === bool) { + return; + } + + self.toggleArrowPointer(!bool); + self._addOrRemoveHeadClickHandler(!bool); + self._isOpeningDisabled = bool; + }, + get isOpeningDisabled() { + return self._isOpeningDisabled; + }, + set isBraced(bool) { + self.toggleHeadContentBraced(bool); + }, + set isOpened(bool) { + if (bool === self._isOpened) { + return; + } + + self._isOpened = bool; + self.toggleArrowBottom(bool); + self._state.isContentShowed = bool; + }, + get isOpened() { + return self._isOpened; + }, + set isContentShowed(bool) { + if (bool === self._isContentShowed) { + return; + } + self._isContentShowed = self.toggleContentShowed(bool); + if (self._isContentShowed && self._contentEl.childElementCount === 0) { + self._contentEl.appendChild(self.createContent(self._value, false).fragment); + } + }, + get isContentShowed() { + return self._isContentShowed; + }, + set isOversized(bool) { + self.toggleHeadContentOversized(bool); + }, + set isItalicEnabled(bool) { + self._isItalicEnabled = self.toggleItalic(bool); + }, + get isItalicEnabled() { + return self._isItalicEnabled; + } + }; + } + + toggleHeadContentBraced(isEnable) { + return this._headContentEl.classList.toggle(`entry-container--braced`, isEnable); + } + + toggleHeadContentOversized(isEnable) { + return this._headContentEl.classList.toggle(`entry-container--oversize`, isEnable); + } + + toggleInfoShowed(isEnable) { + return !this._infoEl.classList.toggle(`hidden`, !isEnable); + } + + toggleHeadContentShowed(isEnable) { + return !this._headContentEl.classList.toggle(`hidden`, !isEnable); + } + + toggleContentShowed(isEnable) { + return !this._contentEl.classList.toggle(`hidden`, !isEnable); + } + + toggleItalic(isEnable) { + return this._headEl.classList.toggle(`italic`, isEnable); + } + + toggleArrowPointer(isEnable) { + return this._headEl.classList.toggle(`item__head--arrow-pointer`, isEnable); + } + + toggleArrowBottom(isEnable) { + return this._headEl.classList.toggle(`item__head--arrow-bottom`, isEnable); + } + + get isOpeningAllowed() { + return this._mode !== Mode.PREVIEW && + !this._state.isOpeningDisabled && + this.isAutoExpandNeeded; + } + + get depth() { + if (!this._cache.depth) { + this._cache.depth = this._parentView ? this._parentView.depth + 1 : 1; + } + return this._cache.depth; + } + + get nextNestingLevel() { + return this._currentDepth + 1; + } + + /** + * Check if autoexpand needed + * Setter for force + * @type {boolean} + */ + get isAutoExpandNeeded() { + if (!this._cache.isAutoExpandNeeded) { + this._cache.isAutoExpandNeeded = false; + + const rootViewTypeParams = this._console.params[this.rootView.viewType]; + + if (this._currentDepth > rootViewTypeParams.expandDepth) { + return this._cache.isAutoExpandNeeded; + } + + if (this._parentView) { + if (!(this._parentView.isAutoExpandNeeded && + !rootViewTypeParams.excludeViewTypesFromAutoexpand.includes(this.viewType))) { + return this._cache.isAutoExpandNeeded; + } + } + } + return this._cache.isAutoExpandNeeded; + } + + set isAutoExpandNeeded(bool) { + this._cache.isAutoExpandNeeded = bool; + } + + _headClickHandler(evt) { + evt.preventDefault(); + this._state.isOpened = !this._state.isOpened; + } + + _addOrRemoveHeadClickHandler(bool) { + if (bool) { + if (!this._bindedHeadClickHandler) { + this._bindedHeadClickHandler = this._headClickHandler.bind(this); + } + this._headEl.addEventListener(`click`, this._bindedHeadClickHandler); + } else if (this._bindedHeadClickHandler) { + this._headEl.removeEventListener(`click`, this._bindedHeadClickHandler); + } + } + + /** + * @param {HTMLElement|null} entryEl + * @param {DocumentFragment} fragment + */ + static appendEntryElIntoFragment(entryEl, fragment) { + if (entryEl !== null) { + fragment.appendChild(entryEl); + } + } + + static prependEntryElIntoFragment(entryEl, fragment) { + if (entryEl !== null) { + fragment.insertBefore(entryEl, fragment.firstElementChild); + } + } +} diff --git a/src/console.js b/src/console.js index 15b30a5..4f68c77 100644 --- a/src/console.js +++ b/src/console.js @@ -3,9 +3,12 @@ import ObjectView from './object/object-view'; import MapSetView from './object/map-set-view'; import PromiseView from './object/promise-view'; import StringNumberView from './object/string-number-view'; +import ObjectNodeView from './object/object-node-view'; import ArrayView from './array/array-view'; import FunctionView from './function/function-view'; import PrimitiveView from './primitive/primitive-view'; +import NodeView from './node/node-view'; +// import NodeView from './object/node-view'; import mergeParams from './utils/merge-params'; import {getElement, checkObjectisPrototype, checkEnumContainsValue} from './utils'; import {Mode, ViewType, Env} from './enums'; @@ -52,7 +55,8 @@ export default class Console { this.params = { object: this._parseViewParams(ViewType.OBJECT, mergeParams([{}, mergedParams.common, mergedParams.object])), array: this._parseViewParams(ViewType.ARRAY, mergeParams([{}, mergedParams.common, mergedParams.array])), - function: this._parseViewParams(ViewType.FUNCTION, mergeParams([{}, mergedParams.common, mergedParams.function])) + function: this._parseViewParams(ViewType.FUNCTION, mergeParams([{}, mergedParams.common, mergedParams.function])), + node: this._parseViewParams(ViewType.NODE, mergeParams([{}, mergedParams.common, mergedParams.node])) }; Object.assign(this.params, this._parseConsoleParams(mergedParams)); } @@ -331,6 +335,7 @@ export default class Console { const stringTagName = stringTag.substring(8, stringTag.length - 1); const objectIsPrototype = checkObjectisPrototype(val); + if (stringTagName !== `Object` && ( Array.isArray(val) || ( !objectIsPrototype && ( @@ -345,10 +350,16 @@ export default class Console { view = new ArrayView(params, this); } else if (!objectIsPrototype && (this.checkInstanceOf(val, `Map`) || this.checkInstanceOf(val, `Set`))) { view = new MapSetView(params, this); - } else if (!objectIsPrototype && val instanceof Promise) { + } else if (!objectIsPrototype && this.checkInstanceOf(val, `Promise`)) { view = new PromiseView(params, this); } else if (!objectIsPrototype && (this.checkInstanceOf(val, `String`) || this.checkInstanceOf(val, `Number`))) { view = new StringNumberView(params, this); + } else if (!objectIsPrototype && this.checkInstanceOf(val, `Node`)) { + if (mode === Mode.LOG || mode === Mode.LOG_HTML || mode === Mode.ERROR) { + view = new NodeView(params, this); + } else { + view = new ObjectNodeView(params, this); + } } else { view = new ObjectView(params, this); } diff --git a/src/entry-view.js b/src/entry-view.js new file mode 100644 index 0000000..2570038 --- /dev/null +++ b/src/entry-view.js @@ -0,0 +1,53 @@ +import AbstractView from './abstract-view'; +import {escapeHTML} from './utils'; + +export default class EntryView extends AbstractView { + constructor({key, entryEl, mode, withoutKey, withoutValue, getViewEl, isGrey}) { + super(); + this._key = key; + this._entryEl = entryEl; // TODO не забыть поменять + this._mode = mode; + this._withoutKey = withoutKey; + this._withoutValue = withoutValue; + this._getViewEl = getViewEl; + this._isGrey = isGrey; + } + + get template() { + return `
${this._withoutKey ? `` : + `${escapeHTML(this._key.toString())}` + }${this._withoutValue ? `` : + `
${this._entryEl ? `` : `(...)`}
` + }
`; + } + + _bind() { + if (this._withoutValue) { + return; + } + this._valueContEl = this._el.querySelector(`.entry-container__value-container`); + + if (this._entryEl) { + this._valueContEl.appendChild(this._entryEl); + return; + } + + this._valueContEl.addEventListener(`click`, this._onClickInsertEl.bind(this), { + once: true + }); + } + + _onClickInsertEl(evt) { + evt.preventDefault(); + + this._valueContEl.textContent = ``; + this._valueContEl.classList.remove(`entry-container__value-container--underscore`); + + try { + const viewEl = this._getViewEl(); + this._valueContEl.appendChild(viewEl); + } catch (err) { + this._valueContEl.textContent = `[Exception: ${err.stack}]`; + } + } +} diff --git a/src/enums.js b/src/enums.js index adfe64a..e0c8239 100644 --- a/src/enums.js +++ b/src/enums.js @@ -19,7 +19,8 @@ export const ViewType = { FUNCTION: `function`, OBJECT: `object`, ARRAY: `array`, - PRIMITIVE: `primitive` + PRIMITIVE: `primitive`, + NODE: `node` }; /** @@ -47,3 +48,5 @@ export const WhereChangeHeaderOnExpand = { ANY_DEPTH: `any-depth`, ANY_DEPTH_EXCEPT_ROOT: `any-depth-except-root` }; + +export const GET_STATE_DESCRIPTORS_KEY_NAME = `get-state-descriptors`; diff --git a/src/node/node-view.js b/src/node/node-view.js new file mode 100644 index 0000000..c81da79 --- /dev/null +++ b/src/node/node-view.js @@ -0,0 +1,230 @@ +import {ViewType, GET_STATE_DESCRIPTORS_KEY_NAME, Mode} from '../enums'; +import BaseView from '../base-view'; +import EntryView from '../entry-view'; +import {getElement, escapeHTML} from '../utils'; + +const ELS_WITHOUT_ENDING_TAG = [`input`, `br`, `hr`, `img`, `base`, `link`, `meta`, `wbr`, `source`, `embed`, `param`, `track`, `area`, `col`]; + +const MAX_PREVIEW_CONTENT_LENGTH = 43; + +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); + +const getElementAttributesStr = (el) => ( + Array.from(el.attributes) + .map(({name, value}) => ( + `${name}=\ +"${value}"` + )) + .join(` `) +); + +const checkElementIsEmpty = (el) => !( + el.textContent.trim() || + el.childNodes.length || + el.content +); + +export default class NodeView extends BaseView { + constructor(params, cons) { + super(params, cons); + this.viewType = ViewType.NODE; + this._viewTypeParams = this._console.params[this.viewType]; + if (!params.parentView) { + this.rootView = this; + } + + this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); + this._closingTag = this._getClosingTag(); + } + + get template() { + return `\ +
+
+ \ +
+ +
`; + } + + _afterRender() { + this._tagContentEl = this._headContentEl.querySelector(`.tag-pair__content`); + this._tagRestContentEl = this._headContentEl.querySelector(`.tag-pair__rest`); + + this._state.isHeadContentShowed = true; + this._state.isRestHeadContentShowed = this.isShowRestHeadContent; + this._state.isOpeningDisabled = this.isDisableOpening; + + this._state.isOpened = this.isOpeningAllowed; + } + + [getStateDescriptorsKey]() { + const self = this; + return { + set isRestHeadContentShowed(bool) { + self._isRestHeadContentShowed = !self._tagRestContentEl.classList.toggle(`hidden`, !bool); + }, + get isRestHeadContentShowed() { + return self._isRestHeadContentShowed; + }, + set isErrorEnabled(bool) { + self._isErrorEnabled = self.toggleError(bool); + }, + get isErrorEnabled() { + return self._isErrorEnabled; + }, + set isOpened(bool) { + if (bool === self._isOpened) { + return; + } + + self._isOpened = bool; + self.toggleArrowBottom(bool); + self._state.isContentShowed = bool; + self._state.isRestHeadContentShowed = self.isShowRestHeadContent; + }, + get isOpened() { + return self._isOpened; + } + }; + } + + get isShowRestHeadContent() { + return this._mode !== Mode.PREVIEW && !this._state.isOpened; + } + + get isDisableOpening() { + if (this._mode === Mode.PREVIEW) { + return true; + } + + return !( + (this._value.nodeType === Node.ELEMENT_NODE && this._closingTag) || + this._value.nodeType === Node.DOCUMENT_FRAGMENT_NODE || + this._value.nodeType === Node.DOCUMENT_NODE + ); + } + + /** + * Возвращает строку контента ноды. Если это Элемент — первый, открывающий, тег. + * @return {string} + */ + _getMainHeadContent() { + let val; + if (this._mode === Mode.PREVIEW) { + val = this._getHeadPlainContent(this._value); + } else { + val = this._getHeadNodesContent(this._value); + } + return val; + } + + _getHeadNodesContent(v) { + let val; + if (v.nodeType === Node.ELEMENT_NODE) { + const attrs = getElementAttributesStr(v); + val = `<${v.tagName.toLowerCase()}${attrs ? ` ${attrs}` : ``}>`; + } else if (v.nodeType === Node.TEXT_NODE) { + val = v.data; + } else if (v.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { + val = `#document-fragment`; + } else if (v.nodeType === Node.DOCUMENT_NODE) { + val = `#document`; + } else if (v.nodeType === Node.COMMENT_NODE) { + val = `<!--${v.data}-->`; + } else if (v.nodeType === Node.ATTRIBUTE_NODE) { + val = `${v.name}`; + } else if (v.nodeType === Node.DOCUMENT_TYPE_NODE) { + val = `<!DOCTYPE html>`; + } else { + val = `not implemented`; + } + return val; + } + + _getHeadPlainContent(v) { + // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName#Value + let val; + if (v.nodeType === Node.ELEMENT_NODE) { + val = `${v.tagName.toLowerCase()}`; + if (v.id) { + val += `#${v.id}`; + } + if (v.classList.length) { + val += `.${Array.prototype.join.call(v.classList, `.`)}`; + } + } else { + val = `${v.nodeName}`; + } + return val; + } + + /** + * Возвращает остальную строку контента ноды. + * Если это элемент — текст после открывающего тега (если там он есть) и закрывающий тег + * Если не элемент — пустую строку + * @param {ContentType} type + * @return {string} + */ + _getRestHeadContent() { + if (this._value.nodeType === Node.ELEMENT_NODE) { + let content = ``; + if (!checkElementIsEmpty(this._value) && !this._console.checkInstanceOf(this._value, `HTMLTemplateElement`)) { + if (this._value.children.length || this._value.innerHTML.length > MAX_PREVIEW_CONTENT_LENGTH) { + content = `…`; + } else { + content = escapeHTML(this._value.innerHTML); + } + } + + let closingTag = this._closingTag; + return `${content}${closingTag}`; + } else if (this._value.nodeType === Node.ATTRIBUTE_NODE) { + const attrValue = this._value.value; + return attrValue ? `="${attrValue}"` : ``; + } + return ``; + } + + _getClosingTag() { + if (this._value.nodeType === Node.ELEMENT_NODE) { + const tagNameLower = this._value.tagName.toLowerCase(); + if (!ELS_WITHOUT_ENDING_TAG.includes(tagNameLower)) { + return `</${tagNameLower}>`; + } + } + return ``; + } + + createContent(node) { + const fragment = document.createDocumentFragment(); + + const mode = Mode.PROP; + + if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE) { + if (this._console.checkInstanceOf(node, `HTMLTemplateElement`)) { + const val = node.content; + const nodeView = new NodeView({val, mode, depth: this.nextNestingLevel, parentView: this}, this._console); + const entryEl = new EntryView({entryEl: nodeView.el, withoutKey: true}).el; + fragment.appendChild(entryEl); + } + for (let childNode of [...node.childNodes]) { + if (childNode.nodeType === Node.TEXT_NODE && !childNode.textContent.trim()) { + continue; + } + const nodeView = new NodeView({val: childNode, mode, depth: this.nextNestingLevel, parentView: this}, this._console); + const entryEl = new EntryView({entryEl: nodeView.el, withoutKey: true}).el; + fragment.appendChild(entryEl); + } + } + + const closingTag = this._closingTag; + if (closingTag) { + const entryEl = getElement(closingTag); + const el = new EntryView({entryEl, withoutKey: true}).el; + fragment.appendChild(el); + } + + return {fragment}; + } +} diff --git a/src/object/map-entry-view.js b/src/object/map-entry-view.js index 87794b4..56645c3 100644 --- a/src/object/map-entry-view.js +++ b/src/object/map-entry-view.js @@ -1,5 +1,8 @@ import TypeView from '../type-view'; -import {Mode, ViewType} from '../enums'; +import EntryView from '../entry-view'; +import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums'; + +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); export default class MapEntryView extends TypeView { constructor(params, cons) { @@ -11,6 +14,8 @@ export default class MapEntryView extends TypeView { this._pairKey = this._value[0]; this._pairValue = this._value[1]; + + this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); } get template() { return `\ @@ -33,7 +38,7 @@ export default class MapEntryView extends TypeView { this._state.isOpened = this.isOpeningAllowed; } - _getStateDescriptors() { + [getStateDescriptorsKey]() { const self = this; return { set isHeadContentShowed(bool) { @@ -65,11 +70,11 @@ export default class MapEntryView extends TypeView { const valueEl = this._console.createTypedView(this._pairValue, this._mode, this.nextNestingLevel, this, this._propKey).el; TypeView.appendEntryElIntoFragment( - this._createEntryEl({key: `key`, el: keyEl, withoutKey: false}), + new EntryView({key: `key`, entryEl: keyEl, withoutKey: false}).el, fragment ); TypeView.appendEntryElIntoFragment( - this._createEntryEl({key: `value`, el: valueEl, withoutKey: false}), + new EntryView({key: `value`, entryEl: valueEl, withoutKey: false}).el, fragment ); diff --git a/src/object/map-set-view.js b/src/object/map-set-view.js index 731d94d..6007da1 100644 --- a/src/object/map-set-view.js +++ b/src/object/map-set-view.js @@ -1,4 +1,5 @@ import TypeView from '../type-view'; +import EntryView from '../entry-view'; import ObjectView from './object-view'; import MapEntryView from '../object/map-entry-view'; import {Mode} from '../enums'; @@ -28,9 +29,10 @@ export default class MapSetView extends ObjectView { } const entry = entriesArr[i]; let entryEl; + if (this._console.checkInstanceOf(this._value, `Map`)) { const el = new MapEntryView({val: entry, mode, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console).el; - entryEl = this._createEntryEl({key: i, el, withoutKey: true}); + entryEl = new EntryView({key: i, entryEl: el, withoutKey: true}).el; } if (this._console.checkInstanceOf(this.value, `Set`)) { entryEl = this._createTypedEntryEl({obj: entriesArr, key: i, mode, withoutKey: true, notCheckDescriptors: true}); @@ -46,7 +48,7 @@ export default class MapSetView extends ObjectView { const entriesArrView = this._console.createTypedView(entriesArr, Mode.PROP, this.nextNestingLevel, this, `[[Entries]]`); entriesArrView.isAutoExpandNeeded = true; TypeView.appendEntryElIntoFragment( - this._createEntryEl({key: `[[Entries]]`, el: entriesArrView.el, withoutKey: false}), + new EntryView({key: `[[Entries]]`, entryEl: entriesArrView.el, withoutKey: false}).el, fragment ); } diff --git a/src/object/object-node-view.js b/src/object/object-node-view.js new file mode 100644 index 0000000..046a99e --- /dev/null +++ b/src/object/object-node-view.js @@ -0,0 +1,22 @@ +import ObjectView from './object-view'; +import {Mode} from '../enums'; +import NodeView from '../node/node-view'; + +export default class ObjectNodeView extends ObjectView { + constructor(params, cons) { + super(params, cons); + } + + createContent(obj, inHead) { + if (inHead) { + const nodeView = new NodeView({val: this._value, mode: Mode.PREVIEW, depth: this.nextNestingLevel, parentView: this, propKey: this._propKey}, this._console); + + const fragment = document.createDocumentFragment(); + fragment.appendChild(nodeView.el); + return {fragment}; + } else { + const objContent = ObjectView.prototype.createContent.apply(this, [obj, inHead]); + return objContent; + } + } +} diff --git a/src/object/object-view.js b/src/object/object-view.js index 4cace08..a746030 100644 --- a/src/object/object-view.js +++ b/src/object/object-view.js @@ -1,9 +1,11 @@ /* eslint guard-for-in: "off"*/ /* eslint no-empty: "off"*/ import TypeView from '../type-view'; -import {Mode, ViewType} from '../enums'; +import {Mode, ViewType, GET_STATE_DESCRIPTORS_KEY_NAME} from '../enums'; import {checkObjectisPrototype} from '../utils'; +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); + export default class ObjectView extends TypeView { constructor(params, cons) { super(params, cons); @@ -12,6 +14,7 @@ export default class ObjectView extends TypeView { if (!params.parentView) { this.rootView = this; } + this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); } get template() { @@ -41,7 +44,7 @@ export default class ObjectView extends TypeView { this._state.isOpened = this.isOpeningAllowed; } - _getStateDescriptors() { + [getStateDescriptorsKey]() { const self = this; return { set isHeadContentShowed(bool) { @@ -212,7 +215,6 @@ export default class ObjectView extends TypeView { return this.el.classList.toggle(`error`, isEnable); } - get headContentClassName() { if (this._console.checkInstanceOf(this._value, `RegExp`) && this._mode !== Mode.DIR) { return `c-regexp`; @@ -226,22 +228,24 @@ export default class ObjectView extends TypeView { this.protoConstructorName === `Object`) { return `…`; } - - if (!Object.prototype.hasOwnProperty.call(this._value, `constructor`)) { - if (this._console.checkInstanceOf(this._value, `Node`)) { - if (this._console.checkInstanceOf(this._value, `HTMLElement`)) { - let str = this._value.tagName.toLowerCase(); - if (this._value.id) { - str += `#${this._value.id}`; - } - if (this._value.classList.length) { - str += `.` + Array.prototype.join.call(this._value.classList, `.`); - } - return str; - } else { - return this._value.nodeName; - } - } else if (this._console.checkInstanceOf(this._value, `Error`)) { + if (!checkObjectisPrototype(this._value)) { + // Что пишем, если у нас элемент, отнаследованный от Node + // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName#Value + // if (this._console.checkInstanceOf(this._value, `Node`)) { + // if (this._console.checkInstanceOf(this._value, `HTMLElement`)) { + // let str = this._value.tagName.toLowerCase(); + // if (this._value.id) { + // str += `#${this._value.id}`; + // } + // if (this._value.classList.length) { + // str += `.` + Array.prototype.join.call(this._value.classList, `.`); + // } + // return str; + // } else { + // return this._value.nodeName; + // } + // } else + if (this._console.checkInstanceOf(this._value, `Error`)) { let str = this._value.name; if (this._value.message) { str += `: ${this._value.message}`; diff --git a/src/object/promise-view.js b/src/object/promise-view.js index ce1a73c..b35072d 100644 --- a/src/object/promise-view.js +++ b/src/object/promise-view.js @@ -1,4 +1,5 @@ import TypeView from '../type-view'; +import EntryView from '../entry-view'; import ObjectView from './object-view'; import {Mode, PromiseStatus} from '../enums'; @@ -37,27 +38,27 @@ export default class PromiseView extends ObjectView { const mode = inHead ? Mode.PREVIEW : Mode.PROP; if (inHead) { TypeView.prependEntryElIntoFragment( - this._createEntryEl({ + new EntryView({ key: `<${this._promiseStatus}>`, - el: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el, + entryEl: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el, withoutValue: this._promiseStatus === PromiseStatus.pending, isGrey: true - }), + }).el, fragment ); } else { TypeView.appendEntryElIntoFragment( - this._createEntryEl({ + new EntryView({ key: `[[PromiseStatus]]`, - el: this._console.createTypedView(this._promiseStatus, mode, this.nextNestingLevel, this).el - }), + entryEl: this._console.createTypedView(this._promiseStatus, mode, this.nextNestingLevel, this).el + }).el, fragment ); TypeView.appendEntryElIntoFragment( - this._createEntryEl({ + new EntryView({ key: `[[PromiseValue]]`, - el: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el - }), + entryEl: this._console.createTypedView(this._promiseValue, mode, this.nextNestingLevel, this).el + }).el, fragment ); } diff --git a/src/object/string-number-view.js b/src/object/string-number-view.js index ef1ca14..3c32f35 100644 --- a/src/object/string-number-view.js +++ b/src/object/string-number-view.js @@ -1,4 +1,5 @@ import TypeView from '../type-view'; +import EntryView from '../entry-view'; import ObjectView from './object-view'; import {Mode} from '../enums'; @@ -30,15 +31,15 @@ export default class StringNumberView extends ObjectView { const insertFn = inHead ? TypeView.prependEntryElIntoFragment : TypeView.appendEntryElIntoFragment; const mode = inHead ? Mode.PREVIEW : Mode.PROP; if (this._console.checkInstanceOf(obj, `String`)) { - const el = this._console.createTypedView(this._value.toString(), mode, this.nextNestingLevel, this).el; + const entryEl = this._console.createTypedView(this._value.toString(), mode, this.nextNestingLevel, this).el; insertFn( - this._createEntryEl({key: `[[PrimitiveValue]]`, el, withoutKey: inHead}), + new EntryView({key: `[[PrimitiveValue]]`, entryEl, withoutKey: inHead}).el, fragment ); } else if (this._console.checkInstanceOf(obj, `Number`)) { - const el = this._console.createTypedView(this._value * 1, mode, this.nextNestingLevel, this).el; + const entryEl = this._console.createTypedView(this._value * 1, mode, this.nextNestingLevel, this).el; insertFn( - this._createEntryEl({key: `[[PrimitiveValue]]`, el, withoutKey: inHead}), + new EntryView({key: `[[PrimitiveValue]]`, entryEl, withoutKey: inHead}).el, fragment ); } diff --git a/src/type-view.js b/src/type-view.js index c166306..00607d8 100644 --- a/src/type-view.js +++ b/src/type-view.js @@ -1,8 +1,9 @@ /* eslint no-empty: "off"*/ /* eslint no-unused-vars: "off"*/ -import AbstractView from './abstract-view'; +import BaseView from './base-view'; +import EntryView from './entry-view'; import {getElement, escapeHTML} from './utils'; -import {Mode, Env} from './enums'; +import {Mode, Env, GET_STATE_DESCRIPTORS_KEY_NAME} from './enums'; const isNativeFunction = (fn) => { return /{\s*\[native code\]\s*}/g.test(fn); @@ -28,45 +29,13 @@ const getFirstProtoContainingObject = (typeView) => { return typeView.value; }; -export default class TypeView extends AbstractView { +const getStateDescriptorsKey = Symbol(GET_STATE_DESCRIPTORS_KEY_NAME); + +export default class TypeView extends BaseView { constructor(params, cons) { - super(); - if (params.parentView) { - this._parentView = params.parentView; - this.rootView = params.parentView.rootView; - } - /** @abstract must be overriden */ - this._viewTypeParams = void 0; - this._console = cons; - this._value = params.val; - this._mode = params.mode; - this._type = params.type; + super(params, cons); this._propKey = params.propKey; - this._currentDepth = typeof params.depth === `number` ? params.depth : 1; - - this._cache = {}; - } - - /** - * @abstract - * @protected - */ - _afterRender() {} - - _bind() { - if (!this.viewType) { - throw new Error(`this.viewType must be specified`); - } - if (!this.rootView) { - throw new Error(`this.rootView must be specified`); - } - - this._headEl = this.el.querySelector(`.head`); - this._headContentEl = this.el.querySelector(`.head__content`); - this._infoEl = this.el.querySelector(`.info`); - this._contentEl = this.el.querySelector(`.item__content`); - - this._afterRender(); + // this._stateDescriptorsQueue.push(this[getStateDescriptorsKey]()); } get protoConstructorName() { @@ -104,109 +73,6 @@ export default class TypeView extends AbstractView { return this._parentView; } - /** - * Current state - * @type {{}} - * @param {{}} params — object with values which will be assigned throught setters - */ - get _state() { - if (!this._viewState) { - this._viewState = {}; - Object.defineProperties( - this._viewState, - Object.getOwnPropertyDescriptors(this._getStateCommonDescriptors()) - ); - Object.defineProperties( - this._viewState, - Object.getOwnPropertyDescriptors(this._getStateDescriptors()) - ); - Object.seal(this._viewState); - } - return this._viewState; - } - - /** - * @abstract - * @return {{}} if not overriden return object without descriptors - */ - _getStateDescriptors() { - return {}; - } - - /** - * @return {{}} — object that contains descriptors only - */ - _getStateCommonDescriptors() { - const self = this; - return { - set isShowInfo(bool) { - if (!self._infoEl) { - return; - } - if (bool && !self._infoEl.textContent) { - self._infoEl.textContent = self.info; - } - self._isShowInfo = self.toggleInfoShowed(bool); - }, - get isShowInfo() { - return self._isShowInfo; - }, - set isHeadContentShowed(bool) { - self.toggleHeadContentShowed(bool); - }, - set isOpeningDisabled(bool) { - if (!bool && self._mode === Mode.PREVIEW) { - throw new Error(`Enabling opening object in preview mode is forbidden`); - } - if (self._isOpeningDisabled === bool) { - return; - } - self.toggleArrowPointer(!bool); - self._addOrRemoveHeadClickHandler(!bool); - self._isOpeningDisabled = bool; - }, - get isOpeningDisabled() { - return self._isOpeningDisabled; - }, - set isBraced(bool) { - self.toggleHeadContentBraced(bool); - }, - set isOpened(bool) { - if (bool === self._isOpened) { - return; - } - - self._isOpened = bool; - self.toggleArrowBottom(bool); - self._state.isContentShowed = bool; - }, - get isOpened() { - return self._isOpened; - }, - set isContentShowed(bool) { - if (bool === self._isContentShowed) { - return; - } - self._isContentShowed = self.toggleContentShowed(bool); - if (self._isContentShowed && self._contentEl.childElementCount === 0) { - self._contentEl.appendChild(self.createContent(self._value, false).fragment); - } - }, - get isContentShowed() { - return self._isContentShowed; - }, - set isOversized(bool) { - self.toggleHeadContentOversized(bool); - }, - set isItalicEnabled(bool) { - self._isItalicEnabled = self.toggleItalic(bool); - }, - get isItalicEnabled() { - return self._isItalicEnabled; - } - }; - } - toggleHeadContentBraced(isEnable) { return this._headContentEl.classList.toggle(`entry-container--braced`, isEnable); } @@ -475,7 +341,6 @@ export default class TypeView extends AbstractView { get isChangeHeaderOnExpandNeeded() { const typeParams = this._viewTypeParams; - } get info() { @@ -488,22 +353,6 @@ export default class TypeView extends AbstractView { } } - _headClickHandler(evt) { - evt.preventDefault(); - this._state.isOpened = !this._state.isOpened; - } - - _addOrRemoveHeadClickHandler(bool) { - if (bool) { - if (!this._bindedHeadClickHandler) { - this._bindedHeadClickHandler = this._headClickHandler.bind(this); - } - this._headEl.addEventListener(`click`, this._bindedHeadClickHandler); - } else if (this._bindedHeadClickHandler) { - this._headEl.removeEventListener(`click`, this._bindedHeadClickHandler); - } - } - _createGettersEntriesFragment() { const fragment = document.createDocumentFragment(); const mode = Mode.PROP; @@ -517,14 +366,14 @@ export default class TypeView extends AbstractView { if (descriptor.get !== void 0) { const getterEl = this._console.createTypedView(descriptor.get, mode, this.nextNestingLevel, this, key).el; TypeView.appendEntryElIntoFragment( - this._createEntryEl({key: `get ${key}`, el: getterEl, mode, isGrey: true}), + new EntryView({key: `get ${key}`, entryEl: getterEl, mode, isGrey: true}).el, fragment ); } if (descriptor.set !== void 0) { const setterEl = this._console.createTypedView(descriptor.set, mode, this.nextNestingLevel, this, key).el; TypeView.appendEntryElIntoFragment( - this._createEntryEl({key: `set ${key}`, el: setterEl, mode, isGrey: true}), + new EntryView({key: `set ${key}`, entryEl: setterEl, mode, isGrey: true}).el, fragment ); } @@ -532,50 +381,6 @@ export default class TypeView extends AbstractView { return fragment; } - /** - * Create entry element - * @protected - * @param {{}} params - * @param {string} params.key — key, index of array or field name - * @param {HTMLSpanElement|undefined} params.el — HTML span element to append into container - * @param {Mode} params.mode — log mode - * @param {boolean} [params.withoutKey] — create entry without key element - * @param {function} [params.getViewEl] — function to get element if so wasn't present while calling this method - * @return {HTMLSpanElement} - */ - _createEntryEl({key, el, mode, withoutKey, withoutValue, getViewEl, isGrey}) { - const entryEl = getElement(`\ -
\ -${withoutKey ? `` : `${escapeHTML(key.toString())}`}${withoutValue ? `` : `
`}\ -
`); - if (withoutValue) { - return entryEl; - } - const valueContEl = entryEl.querySelector(`.entry-container__value-container`); - - if (el) { - valueContEl.appendChild(el); - } else { - valueContEl.textContent = `(...)`; - valueContEl.classList.add(`entry-container__value-container--underscore`); - const insertEl = () => { - valueContEl.textContent = ``; - valueContEl.classList.remove(`entry-container__value-container--underscore`); - let viewEl; - try { - viewEl = getViewEl(); - valueContEl.appendChild(viewEl); - } catch (err) { - valueContEl.textContent = `[Exception: ${err.stack}]`; - } - valueContEl.removeEventListener(`click`, insertEl); - }; - valueContEl.addEventListener(`click`, insertEl); - } - - return entryEl; - } - /** * Create typed entry element * @protected @@ -607,10 +412,10 @@ ${withoutKey ? `` : `