diff --git a/karma.conf.js b/karma.conf.js index 61bff4482..00921513f 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -12,7 +12,7 @@ module.exports = config => { // npm run test -- --grep test/foo/bar.test.js // npm run test -- --grep test/bar/* { - pattern: config.grep ? config.grep : 'packages/*/test/**/*.test.js', + pattern: config.grep ? config.grep : 'packages/**/*/test/**/*.test.js', type: 'module', }, ], diff --git a/packages/helpers/sb-action-logger/custom-elements.json b/packages/helpers/sb-action-logger/custom-elements.json index 22cb809c4..fb97ea31b 100644 --- a/packages/helpers/sb-action-logger/custom-elements.json +++ b/packages/helpers/sb-action-logger/custom-elements.json @@ -10,6 +10,12 @@ "type": "String", "description": "The title of action logger", "default": "Action Logger" + }, + { + "name": "simple", + "type": "Boolean", + "description": "Simple mode, which only shows a single log", + "default": "false" } ], "events": [], diff --git a/packages/helpers/sb-action-logger/src/SbActionLogger.js b/packages/helpers/sb-action-logger/src/SbActionLogger.js index 80595a9d7..bdb5defed 100644 --- a/packages/helpers/sb-action-logger/src/SbActionLogger.js +++ b/packages/helpers/sb-action-logger/src/SbActionLogger.js @@ -6,6 +6,7 @@ export class SbActionLogger extends LitElement { static get properties() { return { title: { type: String, reflect: true }, + simple: { type: Boolean, reflect: true }, __logCounter: { type: Number }, }; } @@ -21,6 +22,8 @@ export class SbActionLogger extends LitElement { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); display: block; + font-family: 'Nunito Sans', -apple-system, '.SFNSText-Regular', 'San Francisco', + BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif; } .header__info { @@ -88,6 +91,7 @@ export class SbActionLogger extends LitElement { .logger__log { padding: 16px; + display: flex; } .logger__log:not(:last-child) { @@ -102,6 +106,16 @@ export class SbActionLogger extends LitElement { white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } + + .logger__log-count { + line-height: 8px; + font-size: 12px; + padding: 4px; + border-radius: 4px; + margin-right: 8px; + color: white; + background-color: #777; + } `; } @@ -111,6 +125,10 @@ export class SbActionLogger extends LitElement { this.__logCounter = 0; } + get loggerEl() { + return this.shadowRoot.querySelector('.logger'); + } + /** * Renders the passed content as a node, and appends it to the logger * Only supports simple values, will be interpreted to a String @@ -119,14 +137,20 @@ export class SbActionLogger extends LitElement { * @param {} content Content to be logged to the action logger */ log(content) { - const loggerEl = this.shadowRoot.querySelector('.logger'); - const offlineRenderContainer = document.createElement('div'); - render(this._logTemplate(content), offlineRenderContainer); - // TODO: Feature, combine duplicate consecutive logs as 1 dom element and add a counter for dupes - loggerEl.appendChild(offlineRenderContainer.firstElementChild); - this.__logCounter += 1; this.__animateCue(); - loggerEl.scrollTo({ top: loggerEl.scrollHeight, behavior: 'smooth' }); + + if (this.simple) { + this.__clearLogs(); + } + + if (this.__isConsecutiveDuplicateLog(content)) { + this.__handleConsecutiveDuplicateLog(); + } else { + this.__appendLog(content); + this.loggerEl.scrollTo({ top: this.loggerEl.scrollHeight, behavior: 'smooth' }); + } + + this.__logCounter += 1; // increment total log counter } /** @@ -143,6 +167,60 @@ export class SbActionLogger extends LitElement { `; } + render() { + return html` +
+
+

${this.title}

+
${this.__logCounter}
+ +
+
+
+
+
+
+ `; + } + + __appendLog(content) { + const offlineRenderContainer = document.createElement('div'); + render(this._logTemplate(content), offlineRenderContainer); + this.loggerEl.appendChild(offlineRenderContainer.firstElementChild); + } + + __isConsecutiveDuplicateLog(content) { + if ( + this.loggerEl.lastElementChild && + this.loggerEl.lastElementChild.querySelector('code').textContent.trim() === content + ) { + return true; + } + return false; + } + + __handleConsecutiveDuplicateLog() { + if (!this.loggerEl.lastElementChild.querySelector('.logger__log-count')) { + this.__prependLogCounterElement(); + } + + // Increment log counter for these duplicate logs + const logCounter = this.loggerEl.lastElementChild.querySelector('.logger__log-count'); + let incrementedLogCount = logCounter.textContent; + incrementedLogCount = parseInt(incrementedLogCount, 10) + 1; + logCounter.innerText = incrementedLogCount; + } + + __prependLogCounterElement() { + const countEl = document.createElement('div'); + countEl.classList.add('logger__log-count'); + countEl.innerText = 1; + this.loggerEl.lastElementChild.insertBefore( + countEl, + this.loggerEl.lastElementChild.firstElementChild, + ); + } + __animateCue() { const cueEl = this.shadowRoot.querySelector('.header__log-cue-overlay'); cueEl.classList.remove('header__log-cue-overlay--slide'); @@ -159,20 +237,4 @@ export class SbActionLogger extends LitElement { loggerEl.innerHTML = ''; this.__logCounter = 0; } - - render() { - return html` -
-
-

${this.title}

-
${this.__logCounter}
- -
-
-
-
-
-
- `; - } } diff --git a/packages/helpers/sb-action-logger/stories/index.stories.mdx b/packages/helpers/sb-action-logger/stories/index.stories.mdx index d1beeddfb..e72421293 100644 --- a/packages/helpers/sb-action-logger/stories/index.stories.mdx +++ b/packages/helpers/sb-action-logger/stories/index.stories.mdx @@ -21,37 +21,50 @@ import '../../sb-action-logger.js'; A visual element to show action logs in Storybook demos `sb-action-logger` - {html` - -
To log: Hello, World!
- - - `} + {() => { + const uid = Math.random().toString(36).substr(2, 10); + return html` +
To log: Hello, World!
+ +
Or to log: What's up, Planet!
+ + + `; + }}
+You need some reference to your logger. Above example shows this by using a unique ID. + +```js +const uid = Math.random().toString(36).substr(2, 10); +``` + +This connects the logger element to the trigger. + ```html
To log: Hello, World!
- + ``` -Note that you need some reference to your logger. Above example shows this by using a unique ID. - ## Features: - A public method `log` to log things to the action logger. - Overridable `title` property. - Clear button to clear logs - A counter to count the total amount of logs +- Stacks consecutive duplicate logs and shows a counter +- `simple` property/attribute to only show a single log ## How to use @@ -67,17 +80,55 @@ npm i sb-action-logger ## Variations -### Custom Title +### Simple mode - - {html` - - - `} +Simple mode essentially means there is only ever 1 log. +Duplicates are not counted or stacked, but you will still see the visual cue. + + + {() => { + const uid = Math.random().toString(36).substr(2, 10); + return html` +
To log: Hello, World!
+ +
Or to log: What's up, Planet!
+ + + `; + }}
+```html + +``` + +### Custom Title + +You can customize the action logger title with the `.title` property. + + + {() => { + const uid = Math.random().toString(36).substr(2, 10); + return html` + + + `; + }} + + +```html + +``` ## Rationale @@ -101,16 +152,16 @@ Maybe in the future I will abstract this component to a more generic (ugly) one If you use an action logger inside your Story in Storybook, you will also see it in your canvas, and this may not be your intention. -One idea I have is that we can simplify the usage further by making this a Storybook (docs-)plugin or decorator or whatever. -I am not too familiar with them at the moment, but it would be cool if someone can simply enable an action logger option on a particular Story inside their .mdx, -and then actions are automatically logged to the visual logger below it. Would need to figure out how to catch the action and pass it to the visual logger element. +One idea is to simplify the usage further by making this a Storybook (docs-)plugin or decorator or whatever. +It would be cool if someone can simply enable an action logger option on a particular Story inside their .mdx, +and then actions are automatically logged to the visual logger below it. +Would need to figure out how to catch the action and pass it to the visual logger element. -I have not investigated yet on the how, but that is the rough idea. Feel free to help out here :) +Isn't investigated yet on the how, but that is the rough idea. ## Future -I plan on adding more features. -They can always be found in the test folder where I specify new features as tests first, and then I skip them until I implement them. Easy to find them that way. -If the feature you'd like is not in the tests, I probably did not think about it yet or did not plan to do it yet, so in that case feel free to make an issue so we can add it. +New planned features can be found in the test folder where they are specified as skipped tests. +If the feature you'd like is not in the tests, feel free to make an issue so we can add it. -I'm happy to accept pull requests for skipped tests (features to be added), see the CONTRIBUTING.md on GitHub for more details on how to contribute to this codebase. +See our CONTRIBUTING.md for more guidelines on how to contribute. diff --git a/packages/helpers/sb-action-logger/test/sb-action-logger.test.js b/packages/helpers/sb-action-logger/test/sb-action-logger.test.js index 80a7a45e8..3f7681ea4 100644 --- a/packages/helpers/sb-action-logger/test/sb-action-logger.test.js +++ b/packages/helpers/sb-action-logger/test/sb-action-logger.test.js @@ -91,30 +91,52 @@ describe('sb-action-logger', () => { expect(el.shadowRoot.querySelector('.logger').children.length).to.equal(0); }); - it('duplicate consecutive logs are kept as one', async () => { + it('duplicate consecutive logs are kept as one, adds a visual counter', async () => { const el = await fixture(html` `); - expect(el).to.be.true; + + el.log('Hello, World!'); + el.log('Hello, World!'); + el.log('Hello, World!'); // 3 consecutive dupes + el.log('Hello, Earth!'); + el.log('Hello, World!'); + el.log('Hello, Planet!'); + el.log('Hello, Planet!'); // 2 consecutive dupes + + const loggerEl = el.shadowRoot.querySelector('.logger'); + + const firstLog = loggerEl.firstElementChild; + const lastLog = loggerEl.lastElementChild; + + expect(loggerEl.children.length).to.equal(4); + expect(firstLog.querySelector('.logger__log-count').innerText).to.equal('3'); + expect(lastLog.querySelector('.logger__log-count').innerText).to.equal('2'); + }); + + it('can be set to simple mode for only showing a single log statement', async () => { + const el = await fixture(html` + + `); + el.log('Hello, World!'); + const loggerEl = el.shadowRoot.querySelector('.logger'); + + expect(loggerEl.children.length).to.equal(1); + expect(loggerEl.firstElementChild.querySelector('code').innerText).to.equal('Hello, World!'); + + el.log('Hello, Earth!'); + expect(loggerEl.children.length).to.equal(1); + expect(loggerEl.firstElementChild.querySelector('code').innerText).to.equal('Hello, Earth!'); + + el.log('Hello, Planet!'); + el.log('Hello, Planet!'); + expect(loggerEl.children.length).to.equal(1); + expect(loggerEl.firstElementChild.querySelector('code').innerText).to.equal('Hello, Planet!'); + expect(loggerEl.firstElementChild.querySelector('.logger__log-count')).to.be.null; }); }); describe('Potential Additional Features', () => { - it.skip('duplicate consecutive adds a visual counter to count per duplicate', async () => { - const el = await fixture(html` - - `); - expect(el).to.be.true; - }); - - // This is handy if you don't want to keep track of updates - it.skip('can be set to mode=simple for only showing a single log statement', async () => { - const el = await fixture(html` - - `); - expect(el).to.be.true; - }); - it.skip('fires a sb-action-logged event when something is logged to the logger', async () => { const el = await fixture(html`