@teipublisher/pb-components
Version:
Collection of webcomponents underlying TEI Publisher
380 lines (348 loc) • 13.6 kB
JavaScript
import { LitElement, html, css } from 'lit-element';
import './pb-dialog.js';
import '@polymer/iron-ajax';
import { pbMixin, waitOnce } from './pb-mixin.js';
import { translate } from './pb-i18n.js';
import './pb-restricted.js';
import './pb-ajax.js';
import './pb-edit-xml.js';
import { cmpVersion } from './utils.js';
import { themableMixin } from './theming.js';
/**
* High-level component implementing the ODD management panel
* on the start page.
* @fires pb-start-update - Fired before the element updates its content
* @fires pb-end-update - Fired after the element has finished updating its content
* @fires pb-load - Sending the ODD to be used
* @fires pb-refresh-odds When received, refresh the list of ODDs
*/
export class PbManageOdds extends themableMixin(pbMixin(LitElement)) {
static get properties() {
return {
...super.properties,
/**
* array of ODD-files to be listed
*/
odds: {
type: Array,
},
target: {
type: String,
},
_valid: {
type: Boolean,
},
_current: {
type: String,
},
};
}
constructor() {
super();
this.odds = [];
}
connectedCallback() {
super.connectedCallback();
this.subscribeTo('pb-refresh-odds', ev => {
this._refresh();
// regenerate newly uploaded ODDs
const regenAjax = this.shadowRoot.getElementById('regenerate');
const params = ev.detail.odds.map(odd => `odd=${odd}`).join('&');
if (this.minApiVersion('1.0.0')) {
regenAjax.url = `api/odd?${params}`;
} else {
regenAjax.url = `modules/lib/regenerate.xql?${params}`;
}
regenAjax.trigger();
});
}
firstUpdated() {
super.firstUpdated();
this._loader = this.shadowRoot.getElementById('load');
waitOnce('pb-page-ready', options => {
if (cmpVersion(options.apiVersion, '1.0.0') < 0) {
this._loader.url = `${options.endpoint}/modules/lib/components-odd.xql`;
} else {
this._loader.url = `${options.endpoint}/api/odd`;
}
this._refresh();
});
}
_refresh(params) {
this.emitTo('pb-start-update');
this._loader.params = params;
this._loader.generateRequest();
}
_update() {
this.emitTo('pb-end-update');
this.odds = this._loader.lastResponse;
this.requestUpdate();
}
_selectODD(ev) {
const selected = ev.model.itemsIndex;
this.odds.forEach((odd, index) => {
if (index !== selected && odd.current) {
this.set(`odds.${index}.current`, false);
this.set(`odds.${selected}.current`, true);
}
});
const params = { odd: `${ev.model.item.name}.odd` };
console.log('<pb-manage-odds> selected ODD: %o', params);
this.emitTo('pb-load', {
params,
});
}
_createODD(ev) {
ev.preventDefault();
const name = this.shadowRoot.querySelector('input[name="new_odd"]').value;
const title = this.shadowRoot.querySelector('input[name="title"]').value;
console.log('<pb-manage-odds> create ODD: %s, %s', name, title);
if (this.lessThanApiVersion('1.0.0')) {
this._refresh({ new_odd: name, title });
} else {
const createRequest = this.shadowRoot.getElementById('create');
createRequest.url = `${this.getEndpoint()}/api/odd/${name}`;
createRequest.params = {
title,
};
this.emitTo('pb-start-update');
createRequest.generateRequest();
}
}
_created(ev) {
this.emitTo('pb-end-update');
if (ev.detail.status === 201) {
this._refresh();
} else {
console.log('<pb-manage-odds> unexpected response for create odd: %o', ev.detail);
}
}
_createByExample() {
const name = this.shadowRoot.querySelector('input[name="new_odd"]').value;
const title = this.shadowRoot.querySelector('input[name="title"]').value;
const params = { new_odd: name, title };
const fileBrowser = document.getElementById(this.target);
if (!(fileBrowser || fileBrowser.getSelected)) {
console.error('<pb-manage-odds> target %s not found', this.target);
}
const selected = fileBrowser.getSelected();
document.querySelectorAll('.document-select paper-checkbox[checked]').forEach(checkbox => {
selected.push(checkbox.value);
});
console.log('<pb-manage-odds> create ODD by example: %o', selected);
params.byExample = selected;
this._refresh(params);
}
_delete(odd) {
this._current = odd;
this.shadowRoot.getElementById('deleteDialog').openDialog();
}
_confirmDelete() {
if (this._current) {
console.log('<pb-manage-odds> deleting ODD: %s', this._current);
if (this.lessThanApiVersion('1.0.0')) {
this._refresh({ delete: this._current });
} else {
this.emitTo('pb-start-update');
const deleteRequest = this.shadowRoot.getElementById('delete');
deleteRequest.url = `${this.getEndpoint()}/api/odd/${this._current}`;
deleteRequest.generateRequest();
}
this._current = null;
} else {
console.error('<pb-manage-odds> no file marked for deletion');
}
}
_deleted() {
const deleteRequest = this.shadowRoot.getElementById('delete');
const error = deleteRequest.lastError;
if (error.status === 410) {
this._refresh();
} else {
console.error('<pb-manage-odds> failed to delete odd: %d %o', error.status, error.response);
this.emitTo('pb-end-update');
}
}
_validate() {
// Validate the entire form to see if we should enable the `Submit` button.
const valid = this.shadowRoot.getElementById('ironform').validate();
this.shadowRoot.getElementById('createBtn').disabled = !valid;
this.shadowRoot.getElementById('createByEx').disabled = !valid;
}
render() {
if (!this.odds) {
return null;
}
const regenUrl = this.lessThanApiVersion('1.0.0') ? 'modules/lib/regenerate.xql' : 'api/odd';
return html`
${this.odds.map(
odd =>
html`
<div class="odd-container">
<div class="odd">
<a
href="odd-editor.html?odd=${odd.name}.odd"
target="_blank"
title="edit ODD in graphical editor"
>${odd.label}</a
>
<!-- TODO this toolbar should only appear once per ODD files papercard -->
<header role="group">
${odd.canWrite
? html`
<pb-restricted login="login">
<pb-ajax
url="${regenUrl}?odd=${odd.name}.odd"
method="post"
class="editor-link"
emit="${this.emit ? this.emit : ''}"
.emitConfig="${this.emitConfig}"
>
<h2 slot="title">${translate('menu.admin.recompile')}</h2>
<button title="Regenerate ODD">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2z"/>
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466"/>
</svg>
</button
</pb-ajax>
</pb-restricted>
<pb-restricted login="login">
<button title="Delete ODD" @click="${() => this._delete(`${odd.name}.odd`)}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3" viewBox="0 0 16 16">
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5"/>
</svg>
</button>
</pb-restricted>
`
: null}
<a
href="odd-editor.html?odd=${odd.name}.odd"
target="_blank"
class="editor-link"
title="edit ODD in graphical editor"
>
<button>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-card-heading" viewBox="0 0 16 16">
<path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2z"/>
<path d="M3 8.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0 2a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 0 1h-6a.5.5 0 0 1-.5-.5m0-5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5z"/>
</svg>
</button>
</a>
<pb-edit-xml path="${odd.path}" class="editor-link">
<button title="Edit XML">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10l.293.293 6.5-6.5zm-9.761 5.175-.106.106-1.528 3.881 3.881-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>
</button>
</pb-edit-xml>
</header>
</div>
<div class="odd-description">${odd.description}</div>
</div>
`,
)}
<pb-restricted login="login">
<form action="" method="GET">
<input
id="new_odd"
name="new_odd"
type="text"
required
pattern="[a-zA-Z0-9-_]+"
placeholder="${translate('odd.manage.filename')}"
/>
<input
id="title"
name="title"
type="text"
required
minlength="1"
placeholder="${translate('odd.manage.title')}"
/>
<button id="createBtn" @click="${this._createODD}">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-plus" viewBox="0 0 16 16">
<path d="M8 6.5a.5.5 0 0 1 .5.5v1.5H10a.5.5 0 0 1 0 1H8.5V11a.5.5 0 0 1-1 0V9.5H6a.5.5 0 0 1 0-1h1.5V7a.5.5 0 0 1 .5-.5"/>
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
</svg>
<span>${translate('odd.manage.create')}</span>
</button>
</form>
</pb-restricted>
<pb-ajax id="regenerate" url="${regenUrl}" method="post"></pb-ajax>
<iron-ajax
id="load"
verbose
handle-as="json"
method="get"
with-credentials
@response="${this._update}"
>
</iron-ajax>
<iron-ajax id="delete" method="delete" with-credentials @error="${this._deleted}"></iron-ajax>
<iron-ajax
id="create"
method="post"
with-credentials
@response="${this._created}"
@error="${this._created}"
></iron-ajax>
<pb-dialog id="deleteDialog" title="${translate('browse.delete')}">
<p>${translate('odd.manage.delete', { file: this.file })}</p>
<div slot="footer">
<button autofocus @click="${this._confirmDelete}" rel="prev">
${translate('dialogs.yes')}
</button>
<button rel="prev">
${translate('dialogs.no')}
</button>
</div>
</pb-dialog>
`;
}
static get styles() {
return css`
:host {
display: block;
}
.odd-container {
margin-bottom: .5rem;
border-bottom: 1px solid var(--pb-manage-odds-border-color, #e0e0e0);
padding-bottom: .5rem;
}
.odd {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: .5rem;
}
.odd a,
.odd a:link,
.odd a:visited {
display: block;
color: var(--pb-manage-odds-link-color);
}
.odd > header {
display: inline-flex;
column-gap: .25rem;
justify-content: flex-end;
align-items: center;
}
pb-restricted {
display: inline-block;
}
.odd-description {
color: #888888;
font-size: 0.8em;
margin-top: 0.5rem;
}
form {
margin-top: 1rem;
}
`;
}
}
if (!customElements.get('pb-manage-odds')) {
customElements.define('pb-manage-odds', PbManageOdds);
}