Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions nx/public/plugins/tags.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>DA App SDK Sample</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" href="data:,">
<!-- Import DA App SDK -->
<script src="https://da.live/nx/utils/sdk.js" type="module"></script>
<!-- Project App Logic -->
<script src="/nx/public/plugins/tags/tags.js" type="module"></script>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<main></main>
</body>
</html>
112 changes: 112 additions & 0 deletions nx/public/plugins/tags/tag-browser.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
:host {
font-family: 'Adobe Clean', adobe-clean, sans-serif;
}

.tag-browser {
display: block;
position: relative;
overflow: hidden;
width: 100%;
height: 100vh;
}

.tag-browser .search-details {
height: 36px;
display: flex;
align-items: center;
justify-content: space-between;
line-height: 36px;
font-size: 16px;
padding: 0 12px;
}

.tag-browser .tag-search input[type="text"] {
flex: 1;
height: 32px;
padding: 0 8px;
font-size: 16px;
font-family: 'Adobe Clean', adobe-clean, sans-serif;
border: 1px solid #d1d1d1;
border-radius: 2px;
box-sizing: border-box;
}

.tag-browser .tag-groups {
display: flex;
overflow: auto;
position: absolute;
width: 100%;
height: calc(100% - 36px);
}

.tag-browser ul {
margin: 0;
padding: 0;
list-style: none;
}

.tag-browser .tag-group-column {
flex-shrink: 0;
width: 100%;
height: 100%;
overflow: auto;
max-width: 280px;
padding: 0 12px;
box-sizing: border-box;
}

.tag-browser .tag-group-column:first-child {
padding: 0 12px;
}

.tag-browser .tag-group .tag-details {
cursor: pointer;
display: flex;
justify-content: space-between;
line-height: 36px;
}

.tag-browser .tag-group .tag-title {
flex: 1;
padding: 0 6px;
font-size: 16px;
font-family: 'Adobe Clean', adobe-clean, sans-serif;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
background: none;
border: none;
text-align: left;
cursor: pointer;
}

.tag-browser .tag-search button,
.tag-browser .tag-details button {
cursor: pointer;
height: 32px;
width: 32px;
margin: 2px;
padding: 0;
border: none;
display: block;
border-radius: 2px;
background-color: #efefef;
}

.tag-browser .tag-group .tag-title:hover {
background-color: #efefef;
}

.tag-browser .tag-search button:hover,
.tag-browser .tag-group button:hover,
.tag-browser .tag-group .tag-title.active {
background-color: #e9e9e9;
}

.tag-browser .tag-search::after,
.tag-browser .tag-group::after {
content: "";
display: block;
height: 1px;
background: #d1d1d1;
}
164 changes: 164 additions & 0 deletions nx/public/plugins/tags/tag-browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/* eslint-disable no-underscore-dangle, import/no-unresolved */
import { LitElement, html, nothing } from 'https://da.live/nx/deps/lit/lit-core.min.js';
import getStyle from 'https://da.live/nx/utils/styles.js';

const style = await getStyle(import.meta.url);

class DaTagBrowser extends LitElement {
static properties = {
rootTags: { type: Array },
actions: { type: Object },
getTags: { type: Function },
tagValue: { type: String },
_tags: { state: true },
_activeTag: { state: true },
_searchQuery: { state: true },
_secondaryTags: { state: true },
};

constructor() {
super();
this._tags = [];
this._activeTag = {};
this._searchQuery = '';
this._secondaryTags = false;
}

getTagSegments() {
return (this._activeTag.activeTag ? this._activeTag.activeTag.split('/') : []).concat(this._activeTag.name);
}

getTagValue() {
if (this.tagValue === 'title') return this._activeTag.title;
const tagSegments = this.getTagSegments();
return tagSegments.join(tagSegments.length > 2 ? '/' : ':').replace('/', ':');
}

handleBlur() {
this._secondaryTags = false;
}

connectedCallback() {
super.connectedCallback();
this.shadowRoot.adoptedStyleSheets = [style];
this.addEventListener('blur', this.handleBlur, true);
}

disconnectedCallback() {
this.removeEventListener('blur', this.handleBlur, true);
super.disconnectedCallback();
}

updated(changedProperties) {
if (changedProperties.has('rootTags')) {
this._tags = [this.rootTags];
this._activeTag = {};
}

if (changedProperties.has('_tags')) {
setTimeout(() => {
const groups = this.renderRoot.querySelector('.tag-groups');
if (!groups) return;
const firstTag = groups.lastElementChild?.querySelector('.tag-title');
firstTag?.focus();
groups.scrollTo({ left: groups.scrollWidth, behavior: 'smooth' });
}, 100);
}
}

async handleTagClick(tag, idx) {
this._activeTag = tag;
if (!this.getTags) return;
const newTags = await this.getTags(tag);
if (!newTags || newTags.length === 0) return;
this._tags = [...this._tags.toSpliced(idx + 1), newTags];
}

handleTagInsert(tag) {
this._activeTag = tag;
const tagValue = this._secondaryTags ? `, ${this.getTagValue()}` : this.getTagValue();
this.actions.sendText(tagValue);
this._secondaryTags = true;
}

handleBackClick() {
if (this._tags.length === 0) return;
this._tags = this._tags.slice(0, -1);
this._activeTag = this._tags[this._tags.length - 1]
.find((tag) => this._activeTag.activeTag.includes(tag.name)) || {};
}

handleSearchInput(event) {
this._searchQuery = event.target.value.toLowerCase();
}

filterTags(tags) {
if (!this._searchQuery) return tags;
return tags.filter((tag) => tag.title.toLowerCase().includes(this._searchQuery));
}

renderSearchBar() {
return html`
<section class="tag-search">
<div class="search-details">
<input
type="text"
placeholder="Search tags..."
@input=${this.handleSearchInput}
value=${this._searchQuery}
/>
${(this._tags.length > 1) ? html`<button @click=${this.handleBackClick}>←</button>` : nothing}
</div>
</section>
`;
}

renderTag(tag, idx) {
const active = this.getTagSegments()[idx] === tag.name;
return html`
<li class="tag-group">
<div class="tag-details">
<button
class="tag-title ${active ? 'active' : ''}"
@click=${() => this.handleTagClick(tag, idx)}
aria-pressed="${active}">
${tag.title.split('/').pop()}
</button>
<button
class="tag-insert"
@click=${() => this.handleTagInsert(tag, idx)}
aria-label="Insert tag ${tag.title}">
</button>
</div>
</li>
`;
}

renderTagGroup(group, idx) {
const filteredGroup = this.filterTags(group);
return html`
<ul class="tag-group-list">
${filteredGroup.map((tag) => this.renderTag(tag, idx))}
</ul>
`;
}

render() {
if (this._tags.length === 0) return nothing;
return html`
<div class="tag-browser">
${this.renderSearchBar()}
<ul class="tag-groups">
${this._tags.map((group, idx) => html`
<li class="tag-group-column">
${this.renderTagGroup(group, idx)}
</li>
`)}
</ul>
</div>
`;
}
}

customElements.define('da-tag-browser', DaTagBrowser);
Loading