commit
0c027baf4d
5 changed files with 215 additions and 74 deletions
|
|
@ -12,7 +12,7 @@ module.exports = config => {
|
||||||
// npm run test -- --grep test/foo/bar.test.js
|
// npm run test -- --grep test/foo/bar.test.js
|
||||||
// npm run test -- --grep test/bar/*
|
// 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',
|
type: 'module',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@
|
||||||
"type": "String",
|
"type": "String",
|
||||||
"description": "The title of action logger",
|
"description": "The title of action logger",
|
||||||
"default": "Action Logger"
|
"default": "Action Logger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "simple",
|
||||||
|
"type": "Boolean",
|
||||||
|
"description": "Simple mode, which only shows a single log",
|
||||||
|
"default": "false"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"events": [],
|
"events": [],
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ export class SbActionLogger extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
title: { type: String, reflect: true },
|
title: { type: String, reflect: true },
|
||||||
|
simple: { type: Boolean, reflect: true },
|
||||||
__logCounter: { type: Number },
|
__logCounter: { type: Number },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -21,6 +22,8 @@ export class SbActionLogger extends LitElement {
|
||||||
|
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
display: block;
|
display: block;
|
||||||
|
font-family: 'Nunito Sans', -apple-system, '.SFNSText-Regular', 'San Francisco',
|
||||||
|
BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__info {
|
.header__info {
|
||||||
|
|
@ -88,6 +91,7 @@ export class SbActionLogger extends LitElement {
|
||||||
|
|
||||||
.logger__log {
|
.logger__log {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logger__log:not(:last-child) {
|
.logger__log:not(:last-child) {
|
||||||
|
|
@ -102,6 +106,16 @@ export class SbActionLogger extends LitElement {
|
||||||
white-space: -o-pre-wrap; /* Opera 7 */
|
white-space: -o-pre-wrap; /* Opera 7 */
|
||||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
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;
|
this.__logCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get loggerEl() {
|
||||||
|
return this.shadowRoot.querySelector('.logger');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the passed content as a node, and appends it to the logger
|
* Renders the passed content as a node, and appends it to the logger
|
||||||
* Only supports simple values, will be interpreted to a String
|
* 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
|
* @param {} content Content to be logged to the action logger
|
||||||
*/
|
*/
|
||||||
log(content) {
|
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();
|
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`
|
||||||
|
<div class="header">
|
||||||
|
<div class="header__info">
|
||||||
|
<p class="header__title">${this.title}</p>
|
||||||
|
<div class="header__counter">${this.__logCounter}</div>
|
||||||
|
<button class="header__clear" @click=${this.__clearLogs}>Clear</button>
|
||||||
|
</div>
|
||||||
|
<div class="header__log-cue">
|
||||||
|
<div class="header__log-cue-overlay"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="logger"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
__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() {
|
__animateCue() {
|
||||||
const cueEl = this.shadowRoot.querySelector('.header__log-cue-overlay');
|
const cueEl = this.shadowRoot.querySelector('.header__log-cue-overlay');
|
||||||
cueEl.classList.remove('header__log-cue-overlay--slide');
|
cueEl.classList.remove('header__log-cue-overlay--slide');
|
||||||
|
|
@ -159,20 +237,4 @@ export class SbActionLogger extends LitElement {
|
||||||
loggerEl.innerHTML = '';
|
loggerEl.innerHTML = '';
|
||||||
this.__logCounter = 0;
|
this.__logCounter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
return html`
|
|
||||||
<div class="header">
|
|
||||||
<div class="header__info">
|
|
||||||
<p class="header__title">${this.title}</p>
|
|
||||||
<div class="header__counter">${this.__logCounter}</div>
|
|
||||||
<button class="header__clear" @click=${this.__clearLogs}>Clear</button>
|
|
||||||
</div>
|
|
||||||
<div class="header__log-cue">
|
|
||||||
<div class="header__log-cue-overlay"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="logger"></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,37 +21,50 @@ import '../../sb-action-logger.js';
|
||||||
A visual element to show action logs in Storybook demos `sb-action-logger`
|
A visual element to show action logs in Storybook demos `sb-action-logger`
|
||||||
|
|
||||||
<Story name="Default">
|
<Story name="Default">
|
||||||
{html`
|
{() => {
|
||||||
<style>
|
const uid = Math.random().toString(36).substr(2, 10);
|
||||||
sb-action-logger {
|
return html`
|
||||||
font-family: 'Nunito Sans', -apple-system, '.SFNSText-Regular', 'San Francisco',
|
|
||||||
BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div>To log: <code>Hello, World!</code></div>
|
<div>To log: <code>Hello, World!</code></div>
|
||||||
<button
|
<button
|
||||||
@click=${() => document.getElementById('logger-a53209ghj').log('Hello, World!')}
|
@click=${() => {
|
||||||
|
document.getElementById(`logger-${uid}`).log('Hello, World!');
|
||||||
|
}}
|
||||||
>Click this button</button>
|
>Click this button</button>
|
||||||
<sb-action-logger id="logger-a53209ghj"></sb-action-logger>
|
<div>Or to log: <code>What's up, Planet!</code></div>
|
||||||
`}
|
<button
|
||||||
|
@click=${() => {
|
||||||
|
document.getElementById(`logger-${uid}`).log(`What's up, Planet!`);
|
||||||
|
}}
|
||||||
|
>Click this button</button>
|
||||||
|
<sb-action-logger id="logger-${uid}"></sb-action-logger>
|
||||||
|
`;
|
||||||
|
}}
|
||||||
</Story>
|
</Story>
|
||||||
|
|
||||||
|
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
|
```html
|
||||||
<div>To log: <code>Hello, World!</code></div>
|
<div>To log: <code>Hello, World!</code></div>
|
||||||
<button
|
<button
|
||||||
@click=${() => document.getElementById('logger-a53209ghj').log('Hello, World!')}
|
@click=${() => document.getElementById('logger-${uid}').log('Hello, World!')}
|
||||||
>Click this button</button>
|
>Click this button</button>
|
||||||
<sb-action-logger id="logger-a53209ghj"></sb-action-logger>
|
<sb-action-logger id="logger-${uid}"></sb-action-logger>
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that you need some reference to your logger. Above example shows this by using a unique ID.
|
|
||||||
|
|
||||||
## Features:
|
## Features:
|
||||||
|
|
||||||
- A public method `log` to log things to the action logger.
|
- A public method `log` to log things to the action logger.
|
||||||
- Overridable `title` property.
|
- Overridable `title` property.
|
||||||
- Clear button to clear logs
|
- Clear button to clear logs
|
||||||
- A counter to count the total amount of 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
|
## How to use
|
||||||
|
|
||||||
|
|
@ -67,17 +80,55 @@ npm i sb-action-logger
|
||||||
|
|
||||||
## Variations
|
## Variations
|
||||||
|
|
||||||
### Custom Title
|
### Simple mode
|
||||||
|
|
||||||
<Story name="Custom Title">
|
Simple mode essentially means there is only ever 1 log.
|
||||||
{html`
|
Duplicates are not counted or stacked, but you will still see the visual cue.
|
||||||
|
|
||||||
|
<Story name="Simple mode">
|
||||||
|
{() => {
|
||||||
|
const uid = Math.random().toString(36).substr(2, 10);
|
||||||
|
return html`
|
||||||
|
<div>To log: <code>Hello, World!</code></div>
|
||||||
<button
|
<button
|
||||||
@click=${() => document.getElementById('logger-vnfoiu3478').log('Hello, World!')}
|
@click=${() => {
|
||||||
>Log</button>
|
document.getElementById(`logger-${uid}`).log('Hello, World!');
|
||||||
<sb-action-logger id="logger-vnfoiu3478" .title=${'Hello World'}></sb-action-logger>
|
}}
|
||||||
`}
|
>Click this button</button>
|
||||||
|
<div>Or to log: <code>What's up, Planet!</code></div>
|
||||||
|
<button
|
||||||
|
@click=${() => {
|
||||||
|
document.getElementById(`logger-${uid}`).log(`What's up, Planet!`);
|
||||||
|
}}
|
||||||
|
>Click this button</button>
|
||||||
|
<sb-action-logger simple id="logger-${uid}"></sb-action-logger>
|
||||||
|
`;
|
||||||
|
}}
|
||||||
</Story>
|
</Story>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sb-action-logger simple></sb-action-logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Title
|
||||||
|
|
||||||
|
You can customize the action logger title with the `.title` property.
|
||||||
|
|
||||||
|
<Story name="Custom Title">
|
||||||
|
{() => {
|
||||||
|
const uid = Math.random().toString(36).substr(2, 10);
|
||||||
|
return html`
|
||||||
|
<button
|
||||||
|
@click=${() => document.getElementById(`logger-${uid}`).log('Hello, World!')}
|
||||||
|
>Log</button>
|
||||||
|
<sb-action-logger id="logger-${uid}" .title=${'Hello World'}></sb-action-logger>
|
||||||
|
`;
|
||||||
|
}}
|
||||||
|
</Story>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<sb-action-logger .title=${'Hello World'}></sb-action-logger>
|
||||||
|
```
|
||||||
|
|
||||||
## Rationale
|
## 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.
|
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.
|
One idea is to 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,
|
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.
|
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
|
## Future
|
||||||
|
|
||||||
I plan on adding more features.
|
New planned features can be found in the test folder where they are specified as skipped tests.
|
||||||
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, feel free to make an issue so we can add it.
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
|
||||||
|
|
@ -91,30 +91,52 @@ describe('sb-action-logger', () => {
|
||||||
expect(el.shadowRoot.querySelector('.logger').children.length).to.equal(0);
|
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`
|
const el = await fixture(html`
|
||||||
<sb-action-logger></sb-action-logger>
|
<sb-action-logger></sb-action-logger>
|
||||||
`);
|
`);
|
||||||
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`
|
||||||
|
<sb-action-logger simple></sb-action-logger>
|
||||||
|
`);
|
||||||
|
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', () => {
|
describe('Potential Additional Features', () => {
|
||||||
it.skip('duplicate consecutive adds a visual counter to count per duplicate', async () => {
|
|
||||||
const el = await fixture(html`
|
|
||||||
<sb-action-logger></sb-action-logger>
|
|
||||||
`);
|
|
||||||
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`
|
|
||||||
<sb-action-logger simple></sb-action-logger>
|
|
||||||
`);
|
|
||||||
expect(el).to.be.true;
|
|
||||||
});
|
|
||||||
|
|
||||||
it.skip('fires a sb-action-logged event when something is logged to the logger', async () => {
|
it.skip('fires a sb-action-logged event when something is logged to the logger', async () => {
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
<sb-action-logger></sb-action-logger>
|
<sb-action-logger></sb-action-logger>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue