Skip to content

Commit 5540739

Browse files
Snapshots: Deep linking (#175)
* Snapshots: Deep linking Allow user to copy deep link to a snapshot. Uses `snapshot=snapshot_name` query param. * Fix test
1 parent 783a3a3 commit 5540739

File tree

9 files changed

+825
-12
lines changed

9 files changed

+825
-12
lines changed

eslint.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ export default defineConfig([
7878
// Allow console in test files
7979
files: ['test/**/*.js'],
8080
rules: {
81+
'max-classes-per-file': 0,
8182
'no-console': 'off',
83+
'no-underscore-dangle': 0,
8284
'no-unused-expressions': 0,
8385
},
8486
}

nx/blocks/snapshot-admin/snapshot-admin.js

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,20 @@ class NxSnapshotAdmin extends LitElement {
6868
this._snapshots = [{ open: true }, ...this._snapshots];
6969
}
7070

71-
handleDelete(idx) {
72-
this._snapshots.splice(idx, 1);
73-
this._snapshots = [...this._snapshots];
71+
handleDelete(snapshot) {
72+
const idx = this._snapshots.findIndex((s) => s.name === snapshot.name);
73+
if (idx > -1) {
74+
this._snapshots.splice(idx, 1);
75+
this._snapshots = [...this._snapshots];
76+
}
77+
}
78+
79+
handleClearFilter() {
80+
const url = new URL(window.location);
81+
url.searchParams.delete('snapshot');
82+
window.history.replaceState({}, '', url);
83+
this._snapshots.forEach((s) => { s.open = false; });
84+
this.requestUpdate();
7485
}
7586

7687
handleDialog() {
@@ -82,25 +93,33 @@ class NxSnapshotAdmin extends LitElement {
8293
}
8394

8495
renderSnapshots() {
85-
const count = this._snapshots.filter((snapshot) => snapshot.name).length;
96+
const filterName = new URLSearchParams(window.location.search).get('snapshot');
97+
const snapshots = filterName
98+
? this._snapshots.filter((s) => s.name?.toLowerCase() === filterName.toLowerCase())
99+
: this._snapshots;
100+
const count = snapshots.filter((snapshot) => snapshot.name).length;
86101
const s = count === 1 ? '' : 's';
87102

88103
return html`
89104
<hr/>
90105
<div class="nx-snapshot-list-header">
91106
<h2>${count} snapshot${s}</h2>
92-
<sl-button @click=${this.handleNew}>Add new</sl-button>
107+
${filterName ? html`<sl-button size="small" @click=${this.handleClearFilter}>See All</sl-button>` : nothing}
108+
${filterName ? nothing : html`<sl-button @click=${this.handleNew}>Add new</sl-button>`}
93109
</div>
94110
<div class="nx-snapshot-list-labels">
95111
<p>Name</p>
96112
<p>Review</p>
97113
</div>
98-
${this._snapshots ? html`
114+
${snapshots ? html`
99115
<div class="nx-snapshot-list">
100116
<ul>
101-
${repeat(this._snapshots, (snapshot) => snapshot.name, (snapshot, idx) => html`
102-
<li><nx-snapshot @delete=${() => this.handleDelete(idx)} .basics=${snapshot} .isRegistered=${this._isRegistered} .userPermissions=${this._userPermissions}></nx-snapshot></li>
103-
`)}
117+
${repeat(
118+
snapshots,
119+
(snap) => snap.name,
120+
(snap) => html`
121+
<li><nx-snapshot @delete=${() => this.handleDelete(snap)} .basics=${snap} .isRegistered=${this._isRegistered} .userPermissions=${this._userPermissions} .startOpen=${!!filterName}></nx-snapshot></li>`,
122+
)}
104123
</ul>
105124
</div>
106125
` : nothing}

nx/blocks/snapshot-admin/views/snapshot.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ svg.icon {
5555
display: grid;
5656
grid-template-columns: 1fr 64px;
5757
gap: var(--spacing-400);
58+
59+
.nx-snapshot-link {
60+
display: inline-flex;
61+
align-items: center;
62+
gap: 6px;
63+
border: none;
64+
border-radius: 4px;
65+
background: transparent;
66+
cursor: pointer;
67+
vertical-align: middle;
68+
69+
&:hover {
70+
background: #0000001a;
71+
}
72+
73+
.copied {
74+
font-size: 12px;
75+
font-weight: 400;
76+
color: var(--s2-green-900);
77+
animation: fade-in-out 1.5s ease-in-out;
78+
}
79+
80+
svg.icon {
81+
width: 14px;
82+
height: 14px;
83+
}
84+
}
5885
}
5986

6087
.name-missing {
@@ -378,3 +405,21 @@ svg.icon {
378405
opacity: 1;
379406
}
380407
}
408+
409+
@keyframes fade-in-out {
410+
0% {
411+
opacity: 0;
412+
}
413+
414+
20% {
415+
opacity: 1;
416+
}
417+
418+
80% {
419+
opacity: 1;
420+
}
421+
422+
100% {
423+
opacity: 0;
424+
}
425+
}

nx/blocks/snapshot-admin/views/snapshot.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,21 @@ const ICONS = [
2727
`${nx}/public/icons/S2_Icon_Publish_20_N.svg`,
2828
`${nx}/public/icons/S2_Icon_ArrowDown_20_N.svg`,
2929
`${nx}/public/icons/S2_Icon_ArrowUp_20_N.svg`,
30+
`${nx}/public/icons/S2_Icon_Link_20_N.svg`,
3031
];
3132

3233
class NxSnapshot extends LitElement {
3334
static properties = {
3435
basics: { attribute: false },
3536
isRegistered: { attribute: false },
3637
userPermissions: { attribute: false },
38+
startOpen: { attribute: false },
3739
_manifest: { state: true },
3840
_editUrls: { state: true },
3941
_message: { state: true },
40-
_isOpen: { state: true },
4142
_action: { state: true },
4243
_launchesCollapsed: { state: true },
44+
_linkCopied: { state: true },
4345
};
4446

4547
constructor() {
@@ -57,6 +59,9 @@ class NxSnapshot extends LitElement {
5759
if (props.has('basics') && this.basics.name && !this._manifest) {
5860
this.loadManifest();
5961
}
62+
if (props.has('startOpen') && this.startOpen && this.basics) {
63+
this.basics.open = true;
64+
}
6065
super.update();
6166
}
6267

@@ -168,6 +173,15 @@ class NxSnapshot extends LitElement {
168173
this._message = { heading: 'Copied', message: 'URLs copied to clipboard.', open: true };
169174
}
170175

176+
handleCopyLink(e) {
177+
e.stopPropagation();
178+
const url = new URL(window.location);
179+
url.searchParams.set('snapshot', this.basics.name);
180+
navigator.clipboard.writeText(url.toString());
181+
this._linkCopied = true;
182+
setTimeout(() => { this._linkCopied = false; }, 1500);
183+
}
184+
171185
async handleDialog(e) {
172186
if (e.detail === 'delete') {
173187
const result = await deleteSnapshot(this.basics.name);
@@ -358,7 +372,7 @@ class NxSnapshot extends LitElement {
358372

359373
renderDetails() {
360374
const showEdit = !this._manifest?.resources || this._editUrls;
361-
const count = this._manifest?.resources.length || 0;
375+
const count = this._manifest?.resources?.length || 0;
362376
const s = count === 1 ? '' : 's';
363377

364378
return html`
@@ -434,7 +448,19 @@ class NxSnapshot extends LitElement {
434448
}
435449

436450
renderName() {
437-
return html`<div class="nx-snapshot-header-title"><p>${this.basics.name}</p> <p>${this._reviewStatus}</p></div>`;
451+
return html`
452+
<div class="nx-snapshot-header-title">
453+
<p>
454+
${this.basics.name}
455+
${this.basics.open ? html`
456+
<button class="nx-snapshot-link" @click=${this.handleCopyLink}>
457+
<svg class="icon" viewBox="0 0 20 20"><use href="#S2_Icon_Link_20_N"/></svg>
458+
${this._linkCopied ? html`<span class="copied">copied</span>` : nothing}
459+
</button>
460+
` : nothing}
461+
</p>
462+
<p>${this._reviewStatus}</p>
463+
</div>`;
438464
}
439465

440466
render() {
Lines changed: 6 additions & 0 deletions
Loading

test/mocks/sl-components.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { LitElement, html, nothing } from 'da-lit';
2+
3+
class SlInput extends LitElement {
4+
static properties = {
5+
value: { type: String },
6+
name: { type: String },
7+
type: { type: String },
8+
error: { type: String },
9+
placeholder: { type: String },
10+
label: { type: String },
11+
};
12+
13+
render() {
14+
return html`<input .value=${this.value || ''} name=${this.name || nothing} type=${this.type || 'text'} />`;
15+
}
16+
}
17+
18+
class SlTextarea extends LitElement {
19+
static properties = {
20+
value: { type: String },
21+
name: { type: String },
22+
resize: { type: String },
23+
};
24+
25+
render() {
26+
return html`<textarea .value=${this.value || ''} name=${this.name || nothing}></textarea>`;
27+
}
28+
}
29+
30+
class SlButton extends LitElement {
31+
static properties = {
32+
disabled: { type: Boolean },
33+
size: { type: String },
34+
};
35+
36+
render() {
37+
return html`<button ?disabled=${this.disabled}><slot></slot></button>`;
38+
}
39+
}
40+
41+
class SlDialog extends LitElement {
42+
showModal() {}
43+
44+
show() {}
45+
46+
close() {}
47+
48+
render() {
49+
return html`<dialog><slot></slot></dialog>`;
50+
}
51+
}
52+
53+
customElements.define('sl-input', SlInput);
54+
customElements.define('sl-textarea', SlTextarea);
55+
customElements.define('sl-button', SlButton);
56+
customElements.define('sl-dialog', SlDialog);

0 commit comments

Comments
 (0)