/**
* @overview HTML templates of <i>ccmjs</i>-based web component for ER-REL Trainer.
* @author André Kless <andre.kless@h-brs.de> 2022
* @copyright EILD.nrw 2022
* @license The MIT License (MIT)
*/
import { html, render } from './../libs/lit/lit.js';
export { render };
/**
* HTML templates of <i>ccmjs</i>-based web component for ER-REL Trainer.
* @module HTMLTemplates
*/
/**
* Returns the main HTML template.
* @function
* @param {object} app - App instance.
* @param {boolean} [show_solution] - Reveal main solution.
* @returns {*}
*/
export function main( app, show_solution ) {
/**
* App state data
* @type {app_state}
*/
const data = app.getValue();
/**
* Data of the notation in the ER diagram
* @type {notation_data}
*/
const notation = app.notations[ data.notation ];
/**
* Current phrase number
* @type {number}
*/
const phrase_nr = data.results.length;
/**
* Data of the current phrase
* @type {phrase_data}
*/
const phrase = data.phrases[ phrase_nr - 1 ];
/**
* Result data of the current phrase
* @type {result_data}
*/
const result = data.results[ phrase_nr - 1 ];
/**
* ER diagram shows a binary relation
* @type {boolean}
*/
const is_binary = phrase.entities.length === 2;
/**
* ER diagram shows a recursive binary relation
* @type {boolean}
*/
const is_recursive = is_binary && phrase.entities[ 0 ] === phrase.entities[ 1 ] && !notation.mirrored;
/**
* ER diagram shows an one-to-one (1:1, 1:c, c:1 or c:c) relation
* @type {boolean}
*/
const is_single = !phrase.solution.find( value => value !== '1' && value !== 'c' );
/**
* ER diagram shows an many-to-many (n:n, cn:n, n:cn or cn:cn) relation
* @type {boolean}
*/
const is_multi = !phrase.solution.find( value => value !== 'cn' && value !== 'n' );
/**
* ER diagram shows an one-to-many (1:n, c:n, 1:cn or c:cn) relation
* @type {boolean}
*/
const is_many = is_binary && !is_single && !is_multi;
/**
* ER diagram shows a generalisation/specialisation relation
* @type {boolean}
*/
const is_hierarchy = !phrase.relation;
/**
* Bit mask for arrows
* @type {number}
*/
const arrows_mask = 15872;
/**
* Bit mask for foreign keys
* @type {number}
*/
const fk_mask = 496;
/**
* Translation index for the heading ('heading', 'correct' or 'failed')
* @type {string}
*/
const heading = result.correct === undefined ? 'main_heading' : 'feedback_' + ( result.correct ? 'correct' : 'failed' );
return html`
<div class="d-flex justify-content-between align-items-center">
<h1 class="mx-3" data-lang="main_title">${ app.text.main_title }</h1>
<aside></aside>
</div>
<header class="bg-light border rounded-top d-flex flex-wrap justify-content-between align-items-center p-2">
<!-- Heading -->
<div id="heading" class="p-2 pe-3" data-lang="${ heading }">${ app.text[ heading ] }</div>
<div class="d-flex align-items-center text-nowrap px-2" ?data-hidden=${ app.fixed_notation }>
<!-- Notation Selection -->
<section>
<div class="d-flex align-items-center">
<label for="notation-input" class="m-0"><b data-lang="main_notation">${ app.text.main_notation }</b></label>
<select id="notation-input" class="form-select ms-2" @change=${ event => app.events.onNotation( event.target.value ) }>
${ Object.values( app.notations ).sort( ( a, b ) =>
a.title.localeCompare( b.title ) ).map( ( { key, title } ) =>
html`<option value="${ key }" ?disabled=${ !is_binary && app.notations[ key ].swap } ?selected=${ data.notation === key }>${ title }</option>`
) }
</select>
</div>
</section>
<!-- Legend -->
<section class="ms-2" ?data-hidden=${ !app.legend }>
<button class="btn btn-link" @click=${ app.events.onLegend } data-lang="legend">${ app.text.legend }</button>
</section>
</div>
</header>
<main class="border rounded-bottom border-top-0 px-4 py-2 text-nowrap">
<div>
<!-- Phrase -->
<section class="lead px-2 py-3" ?data-hidden=${ !phrase.text }>
<b>
<span data-lang="main_phrase">${ app.text.main_phrase }</span><span ?data-hidden=${ app.phrases.length === 1 }> ${ phrase_nr }/${ app.number }</span>:
</b>
<span class="text-wrap">${ phrase.text }</span>
</section>
<!-- Diagram and Tables -->
${ diagram() }
<hr>
${ scheme() }
<!-- Correct Solution -->
<div ?data-hidden=${ !app.feedback || !app.show_solution || !show_solution || result.correct === undefined }>
<hr>
<div class="lead text-center pt-2" data-lang="feedback_solution">${ app.text.feedback_solution }</div>
${ app.feedback && app.show_solution && show_solution && result.correct !== undefined ? scheme( true ) : '' }
</div>
<hr>
<!-- Comments During Input -->
<section ?data-hidden=${ !( app.comments && app.comments.input && result.correct === undefined ) }>
${ comment( 'create_tables', !result.input.find( ( table, i ) => table !== null && !( i === 2 && is_recursive ) ) ) }
${ comment( 'decide_null', result.input.find( table => table && table.find( attr => attr & 508 && !( attr & 3 ) ) ) ) }
${ comment( 'connect_tables', result.input.find( ( table, i ) => table !== null && !table.find( attr => attr & fk_mask ) && !result.input.find( ( table, j ) => i !== j && isConnected( i, j ) ) ) ) }
${ comment( 'set_arrows', result.input.find( ( table, i ) => table !== null && table.find( ( attr, j ) => i !== j && attr & fk_mask && result.input[ j ] && !( attr & 1 << ( 4 + j + 5 ) ) && !( result.input[ j ][ i ] & 1 << ( 4 + i + 5 ) ) ) ) ) }
</section>
<!-- Comments on Wrong Solution -->
<section ?data-hidden=${ !( app.comments && app.comments.wrong && result.correct === false ) }>
<!-- Wrong Tables -->
${ comment( 'missing_entity_table', result.input.find( ( table, i ) => i && !table && !( i === 2 && is_recursive ) ) !== undefined ) }
${ comment( 'missing_relation_table', is_multi && !result.input[ 0 ] ) }
${ comment( 'not_needed_relation_table', !is_multi && result.input[ 0 ] ) }
<!-- Wrong Primary Keys -->
${ comment( 'missing_pk', result.input.find( table => table && !table.find( attr => attr & 1 << 2 ) ) ) }
${ comment( 'pk_not_null', result.input.find( table => table && table.find( attr => attr & 1 << 2 && !( attr & 1 << 1 ) ) ) ) }
${ comment( 'wrong_pk', result.input.find( ( table, i ) => table && i && table.find( ( attr, j ) => attr & 1 << 2 && i !== j ) ) ) }
${ comment( 'nm_pk', is_multi && result.input[ 0 ] && ( result.input[ 0 ][ 0 ] & 1 << 2 || result.input[ 0 ].find( ( attr, i ) => i && !( attr & 1 << 2 ) ) ) ) }
<!-- Wrong Foreign Keys -->
${ comment( '1c_fk', phrase.solution.toString() === '1,c' && result.input[ 1 ] && !( result.input[ 1 ][ 2 ] & 64 ) || phrase.solution.toString() === 'c,1' && result.input[ 2 ] && !( result.input[ 2 ][ 1 ] & 32 ) ) }
${ comment( '1n_fk', is_many && result.input.find( ( table, i ) => i && table && !phrase.solution[ i - 1 ].includes( 'n' ) && !( table[ i === 1 ? 2 : 1 ] & 1 << ( 4 + ( i === 1 ? 2 : 1 ) ) ) ) ) }
${ comment( 'nm_fk', is_multi && result.input[ 0 ] && result.input[ 0 ].find( ( attr, i ) => i ? !( attr & 1 << ( 4 + i ) ) : attr & 1 << ( 4 + i ) ) ) }
${ comment( 'hierarchy_fk', is_hierarchy && result.input.find( ( table, i ) => i > 1 && table && !( table[ 1 ] & ( 1 << 5 ) ) ) ) }
${ comment( 'not_null_fk', !( is_many && ( phrase.solution.includes( 'c' ) || phrase.solution.toString() === 'c,c' ) ) && !is_multi && result.input.find( ( table, i ) => table && table.find( ( attr, j ) => attr & fk_mask && ( attr & fk_mask ) === ( ( result.solution && result.solution[ i ] && result.solution[ i ][ j ] ) & fk_mask ) && attr & 1 ) ) ) }
${ comment( 'null_fk', is_many && ( phrase.solution.includes( 'c' ) || phrase.solution.toString() === 'c,c' ) && ( () => {
const left = phrase.solution[ 0 ] === 'c';
const table = result.input[ left ? 1 : 2 ];
const attr = table && table[ left ? 2 : 1 ];
const fk = left ? 64 : 32;
return attr & fk && attr & 2;
} )() ) }
<!-- Wrong Alternate Key -->
${ comment( 'ak', is_hierarchy && result.input.find( ( table, i ) => i > 1 && table && table[ 1 ] & ( 1 << 5 ) && !( table[ 1 ] & 1 << 3 ) ) || is_single && ( () => {
const c1 = phrase.solution.toString() === 'c,1';
const table = result.input[ c1 ? 2 : 1 ];
const attr = table && table[ c1 ? 1 : 2 ];
const fk = c1 ? 32 : 64;
return attr & fk && !( attr & 8 );
} )() ) }
</section>
<!-- Comments on Correct Solution -->
<section ?data-hidden=${ !( app.comments && app.comments.correct && result.correct === true ) }>
${ comment( 'alternate_solution', !!result.alternate_solution ) }
${ comment( 'mandatory', ( () => {
switch ( phrase.solution.toString() ) {
case '1,1':
case '1,n':
case 'n,1':
case 'c,n':
case 'n,c':
return true;
}
return false;
} )() ) }
${ comment( 'total', is_hierarchy && phrase.solution[ 0 ] === 't' ) }
${ comment( 'disjoint', is_hierarchy && phrase.solution[ 1 ] === 'd' ) }
</section>
<!-- Buttons -->
<section id="buttons" class="d-flex justify-content-center flex-wrap px-2 py-3">
<button id="submit" type="submit" form="scheme" class="btn btn-primary m-1" ?disabled=${ result.correct !== undefined } data-lang="btn_submit">${ app.text.btn_submit }</input>
<button id="correction" class="btn btn-primary m-1" @click=${ app.events.onCorrection } ?data-hidden=${ !app.correction } ?disabled=${ result.correct !== false || show_solution } data-lang="btn_correction">${ app.text.btn_correction }</button>
<button id="solution" class="btn btn-primary m-1" @click=${ app.events.onSolution } ?data-hidden=${ !app.show_solution } ?disabled=${ result.correct === undefined || show_solution } data-lang="btn_solution">${ app.text.btn_solution }</button>
<button id="next" class="btn btn-primary m-1" @click=${ app.events.onNext } ?disabled=${ !app.skip && result.correct === undefined || phrase_nr === app.number } data-lang="btn_${ app.skip && result.correct === undefined ? 'skip' : 'next' }">${ app.text[ 'btn_' + ( app.skip && result.correct === undefined ? 'skip' : 'next' ) ] }</button>
<button id="finish" class="btn btn-primary m-1" @click=${ app.events.onFinish } ?disabled=${ !app.onfinish || !app.skip && result.correct === undefined || !app.anytime_finish && phrase_nr < app.number } data-lang="btn_finish">${ app.text.btn_finish }</button>
</section>
</div>
</main>
<!-- Lizenzen -->
${ app.license ? html`
<aside class="bg-light rounded text-center form-text mt-4 mx-3">
Der <a href="https://github.com/EILD-nrw/er_rel_trainer" target="_blank">ER-REL-Trainer</a> wurde
von <a href="https://h-brs.de/de/inf/andre-kless" target="_blank">André Kless</a> im Rahmen
des <a href="https://github.com/EILD-nrw" target="_blank">EILD-Projekts</a> an
der <a href="https://h-brs.de" target="_blank">Hochschule Bonn-Rhein-Sieg</a> entwickelt.
Dieser interaktive Trainer enthält Software unter <a href="https://opensource.org/licenses/MIT" target="_blank">MIT-Lizenz</a> und Content
unter der <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.de" target="_blank">CC0-Lizenz</a>.
</aside>
` : '' }
<!-- Logos -->
${ app.logos ? html`
<aside class="mx-3 mt-3 text-center">
<img src="${ app.logos }">
</aside>
` : '' }
`;
/**
* Returns the HTML template for the ER diagram.
* @returns {*}
*/
function diagram() {
return html`
<section class="diagram px-2 py-4">
<div class="text-center lead">
<div></div>
<div></div>
${ entity( phrase.relation ? 3 : 1 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ phrase.relation ? connection( 3 ) : line( 2 ) }
<div></div>
<div></div>
${ phrase.relation ? entity( 1 ) : line( 0 ) }
${ phrase.relation ? connection( 1 ) : line( 0 ) }
<div id="relation">
<img src="${ notation.images[ phrase.relation ? 5 : 6 ] }">
<div ?data-centered=${ notation.centered } ?data-triangle=${ !phrase.relation }>
${ phrase.relation || html`
<div data-lang="hierarchy_${ phrase.solution[ 0 ] }">${ app.text[ 'hierarchy_' + phrase.solution[ 0 ] ] }</div>
<div data-lang="hierarchy_${ phrase.solution[ 1 ] }">${ app.text[ 'hierarchy_' + phrase.solution[ 1 ] ] }</div>
` || '' }
</div>
</div>
${ phrase.relation ? connection( 2 ) : line( 0 ) }
${ phrase.relation ? ( is_recursive ? line( 1 ) : entity( 2 ) ) : line( 0 ) }
${ line( is_recursive ? 4 : ( phrase.relation ? 0 : 6 ) ) }
${ line( is_recursive ? 3 : ( phrase.relation ? 0 : 5 ) ) }
${ is_recursive ? line( 3 ) : ( phrase.relation ? connection( 4 ) : line( 8 ) ) }
${ line( is_recursive ? 3 : ( phrase.relation ? 0 : 5 ) ) }
${ line( is_recursive ? 2 : ( phrase.relation ? 0 : 7 ) ) }
${ line( phrase.relation ? 0 : 1 ) }
<div></div>
${ line( phrase.relation || phrase.entities.length < 4 ? 0 : 1 ) }
<div></div>
${ line( phrase.relation ? 0 : 1 ) }
${ entity( phrase.relation ? 0 : 2 ) }
<div></div>
${ entity( phrase.relation ? 4 : ( phrase.entities.length > 3 ? 3 : 4 ) ) }
<div></div>
${ entity( phrase.relation ? 0 : ( phrase.entities.length > 3 ? 4 : 3 ) ) }
</div>
</section>
`;
/**
* Returns the HTML template for an entity connection.
* @param {entity_nr} [entity] - Entity number
* @returns {*}
*/
function connection( entity ) {
return entity ? html`
<div class="${ entity > 2 ? 'vertical' : '' }">
<img class="${ entity === 1 && notation.mirrored ? 'mirrored' : '' }" src="${ notation.images[ [ '1', 'c', 'n', 'cn' ].indexOf( phrase.solution[ notation.swap ? ( entity > 1 ? 0 : 1 ) : entity - 1 ] ) + 1 ] }" ?data-hidden=${ !phrase.entities[ entity - 1 ] }>
</div>
` : html`<div></div>`;
}
/**
* Returns the HTML template for an entity.
* @param {entity_nr} [nr] - Entity number
* @returns {*}
*/
function entity( nr ) {
return phrase.entities[ nr - 1 ] ? html`
<div class="entity p-3">
${ phrase.entities[ nr - 1 ] }
</div>
` : html`<div></div>`;
}
/**
* Returns the HTML template for a connection line.
* @param {number} [nr] - Line number
* @returns {*}
*/
function line( nr ) {
if ( !nr ) return html`<div></div>`;
if ( nr === 8 ) return html`
<div class="line8">
<svg viewBox="0 0 240 60">
<line x1="0" y1="59" x2="90" y2="59" stroke="black" stroke-width="2"/>
<line x1="90" y1="0" x2="90" y2="60" stroke="black" stroke-width="2"/>
<line x1="120" y1="0" x2="120" y2="60" stroke="${ phrase.entities.length < 4 ? 'transparent' : 'black' }" stroke-width="2"/>
<line x1="150" y1="0" x2="150" y2="60" stroke="black" stroke-width="2"/>
<line x1="150" y1="59" x2="240" y2="59" stroke="black" stroke-width="2"/>
</svg>
</div>
`;
return html`<div class="line${ nr }"></div>`;
}
}
/**
* Returns the HTML template for the relation scheme tables.
* @param {boolean} [is_solution] - Scheme shows the solution
* @returns {*}
*/
function scheme( is_solution ) {
/**
* State in which the tables are displayed.
* @type {table_data[]}
*/
const input = is_solution ? result.solution : result.input;
/**
* Main solution with which the tables are compared for the automated feedback.
* @type {table_data[]}
*/
const solution = !is_solution && ( result.alternate_solution || result.solution );
return html`
<form id="scheme" @submit=${ event => { event.preventDefault(); !is_solution && app.events.onSubmit(); } }>
<section class="tables px-2 py-4">
${ is_recursive && recursive()
|| is_binary && binary()
|| is_hierarchy && ( phrase.entities.length === 3 ? hierarchy_3() : hierarchy_4() )
|| ( phrase.entities.length === 3 ? multi_3() : multi_4() ) }
</section>
</form>
`;
/**
* Returns the HTML template for the scheme tables of a binary relation.
* @returns {*}
*/
function binary() {
return html`
<div class="binary">
${ table( 1 ) }
${ arrow( 'l', 2, 1 ) }
${ connection( 'h', 1, 2 ) }
${ arrow( 'r', 1, 2 ) }
${ table( 2 ) }
${ arrow( 'u', 0, 1 ) }
<div></div>
<div></div>
<div></div>
${ arrow( 'u', 0, 2 ) }
${ connection( 'ur', 1, 0 ) }
${ arrow( 'r', 1, 0 ) }
${ table( 0 ) }
${ arrow( 'l', 2, 0 ) }
${ connection( 'ul', 2, 0 ) }
</div>
`;
}
/**
* Returns the HTML template for the scheme tables of a recursive binary relation.
* @returns {*}
*/
function recursive() {
return html`
<div class="recursive">
${ connection( 'rd', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'dl', 1, 2 ) }
<div></div>
${ arrow( 'd', 1, 2 ) }
<div></div>
${ connection( 'v', 1, 2 ) }
<div></div>
${ table( 1 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'ul', 1, 2 ) }
<div></div>
${ arrow( 'u', 0, 1 ) }
<div></div>
<div></div>
<div></div>
${ connection( 'ur', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ arrow( 'r', 1, 0 ) }
${ table( 0 ) }
</div>
`;
}
/**
* Returns the HTML template for the scheme tables of a many-to-many relation with 3 entities.
* @returns {*}
*/
function multi_3() {
return html`
<div class="multi-3">
${ connection( 'rd', 1, 3 ) }
${ connection( 'h', 1, 3 ) }
${ arrow( 'r', 1, 3 ) }
${ table( 3 ) }
${ arrow( 'l', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'dl', 2, 3 ) }
${ connection( 'v', 1, 3 ) }
<div></div>
<div></div>
${ arrow( 'u', 0, 3 ) }
<div></div>
<div></div>
${ connection( 'v', 2, 3 ) }
${ arrow( 'd', 3, 1 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 0 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 2 ) }
${ table( 1 ) }
${ arrow( 'l', 0, 1 ) }
${ arrow( 'r', 1, 0 ) }
${ table( 0 ) }
${ arrow( 'l', 2, 0 ) }
${ arrow( 'r', 0, 2 ) }
${ table( 2 ) }
${ arrow( 'u', 2, 1 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'u', 1, 2 ) }
${ connection( 'ur', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'ul', 1, 2 ) }
</div>
`;
}
/**
* Returns the HTML template for the scheme tables of a many-to-many relation with 4 entities.
* @returns {*}
*/
function multi_4() {
return html`
<div class="multi-4">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'rd', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'dl', 3, 4 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'd', 4, 3 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'v', 3, 4 ) }
<div></div>
<div></div>
${ connection( 'rd', 1, 3 ) }
${ connection( 'h', 1, 3 ) }
${ arrow( 'r', 1, 3 ) }
${ table( 3 ) }
${ arrow( 'l', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'dl', 2, 3 ) }
<div></div>
<div></div>
${ connection( 'v', 3, 4 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 3 ) }
<div></div>
<div></div>
${ arrow( 'u', 0, 3 ) }
<div></div>
<div></div>
${ connection( 'v', 2, 3 ) }
<div></div>
<div></div>
${ connection( 'v', 3, 4 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 1 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 0 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 2 ) }
<div></div>
<div></div>
${ connection( 'v', 3, 4 ) }
${ connection( 'rd', 1, 2 ) }
${ arrow( 'r', 2, 1 ) }
${ table( 1 ) }
${ arrow( 'l', 0, 1 ) }
${ arrow( 'r', 1, 0 ) }
${ table( 0 ) }
${ arrow( 'l', 2, 0 ) }
${ arrow( 'r', 0, 2 ) }
${ table( 2 ) }
${ arrow( 'l', 1, 2 ) }
${ connection( 'dl', 1, 2 ) }
${ connection( 'v', 3, 4 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
${ arrow( 'u', 4, 1 ) }
<div></div>
<div></div>
${ arrow( 'u', 4, 0 ) }
<div></div>
<div></div>
${ arrow( 'u', 4, 2 ) }
<div></div>
${ connection( 'v', 1, 2 ) }
${ connection( 'v', 3, 4 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
${ connection( 'v', 1, 4 ) }
<div></div>
<div></div>
${ arrow( 'd', 0, 4 ) }
<div></div>
<div></div>
${ connection( 'v', 2, 4 ) }
<div></div>
${ connection( 'v', 1, 2 ) }
${ connection( 'v', 3, 4 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
${ connection( 'ur', 1, 4 ) }
${ connection( 'h', 1, 4 ) }
${ arrow( 'r', 1, 4 ) }
${ table( 4 ) }
${ arrow( 'l', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'ul', 2, 4 ) }
<div></div>
${ connection( 'v', 1, 2 ) }
${ connection( 'v', 3, 4 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'u', 3, 4 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'v', 1, 2 ) }
${ connection( 'v', 3, 4 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'ur', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'h', 3, 4 ) }
${ connection( 'x', 1, 2, 3, 4 ) }
${ connection( 'ul', 3, 4 ) }
${ connection( 'ur', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ connection( 'ul', 1, 2 ) }
<div></div>
</div>
`;
}
/**
* Returns the HTML template for the scheme tables of a generalisation/specialisation relation with 3 entities.
* @returns {*}
*/
function hierarchy_3() {
return html`
<div class="multi-3">
${ connection( 'rd', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ arrow( 'r', 2, 1 ) }
${ table( 1 ) }
${ arrow( 'l', 3, 1 ) }
${ connection( 'h', 1, 3 ) }
${ connection( 'dl', 1, 3 ) }
${ connection( 'v', 1, 2 ) }
<div></div>
<div></div>
${ arrow( 'u', 0, 1 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 3 ) }
${ arrow( 'd', 1, 2 ) }
<div></div>
<div></div>
${ arrow( 'd', 1, 0 ) }
<div></div>
<div></div>
${ arrow( 'd', 1, 3 ) }
${ table( 2 ) }
${ arrow( 'l', 0, 2 ) }
${ arrow( 'r', 2, 0 ) }
${ table( 0 ) }
${ arrow( 'l', 3, 0 ) }
${ arrow( 'r', 0, 3 ) }
${ table( 3 ) }
${ arrow( 'u', 3, 2 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'u', 2, 3 ) }
${ connection( 'ur', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'h', 2, 3 ) }
${ connection( 'ul', 2, 3 ) }
</div>
`;
}
/**
* Returns the HTML template for the scheme tables of a generalisation/specialisation relation with 4 entities.
* @returns {*}
*/
function hierarchy_4() {
return html`
<div class="multi-4">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'rd', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'dl', 1, 0 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'd', 0, 1 ) }
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'v', 1, 0 ) }
<div></div>
<div></div>
${ connection( 'rd', 1, 2 ) }
${ connection( 'h', 1, 2 ) }
${ arrow( 'r', 2, 1 ) }
${ table( 1 ) }
${ arrow( 'l', 4, 1 ) }
${ connection( 'h', 1, 4 ) }
${ connection( 'dl', 1, 4 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 0 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 2 ) }
<div></div>
<div></div>
${ arrow( 'u', 3, 1 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 4 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 0 ) }
<div></div>
<div></div>
${ arrow( 'd', 1, 2 ) }
<div></div>
<div></div>
${ arrow( 'd', 1, 3 ) }
<div></div>
<div></div>
${ arrow( 'd', 1, 4 ) }
<div></div>
<div></div>
${ connection( 'v', 1, 0 ) }
${ connection( 'rd', 2, 4 ) }
${ arrow( 'r', 4, 2 ) }
${ table( 2 ) }
${ arrow( 'l', 3, 2 ) }
${ arrow( 'r', 2, 3 ) }
${ table( 3 ) }
${ arrow( 'l', 4, 3 ) }
${ arrow( 'r', 3, 4 ) }
${ table( 4 ) }
${ arrow( 'l', 2, 4 ) }
${ connection( 'dl', 2, 4 ) }
${ connection( 'v', 1, 0 ) }
${ connection( 'v', 2, 4 ) }
<div></div>
${ arrow( 'u', 0, 2 ) }
<div></div>
<div></div>
${ arrow( 'u', 0, 3 ) }
<div></div>
<div></div>
${ arrow( 'u', 0, 4 ) }
<div></div>
${ connection( 'v', 2, 4 ) }
${ connection( 'v', 1, 0 ) }
${ connection( 'v', 2, 4 ) }
<div></div>
${ connection( 'v', 2, 0 ) }
<div></div>
<div></div>
${ arrow( 'd', 3, 0 ) }
<div></div>
<div></div>
${ connection( 'v', 4, 0 ) }
<div></div>
${ connection( 'v', 2, 4 ) }
${ connection( 'v', 1, 0 ) }
${ connection( 'v', 2, 4 ) }
<div></div>
${ connection( 'ur', 2, 0 ) }
${ connection( 'h', 2, 0 ) }
${ arrow( 'r', 2, 0 ) }
${ table( 0 ) }
${ arrow( 'l', 4, 0 ) }
${ connection( 'h', 4, 0 ) }
${ connection( 'ul', 4, 0 ) }
<div></div>
${ connection( 'v', 2, 4 ) }
${ connection( 'v', 1, 0 ) }
${ connection( 'v', 2, 4 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ arrow( 'u', 1, 0 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'v', 2, 4 ) }
${ connection( 'v', 1, 0 ) }
${ connection( 'v', 2, 4 ) }
<div></div>
<div></div>
<div></div>
<div></div>
${ connection( 'ur', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'h', 1, 0 ) }
${ connection( 'x', 2, 4, 1, 0 ) }
${ connection( 'ul', 1, 0 ) }
${ connection( 'ur', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'h', 2, 4 ) }
${ connection( 'ul', 2, 4 ) }
<div></div>
</div>
`;
}
/**
* Returns the HTML template for a scheme table.
* @param {table_nr} table - Table number
* @returns {*}
*/
function table( table ) {
/**
* Input data for table attributes
* @type {number[]}
*/
const input_table = input[ table ] && input[ table ].map( attr => attr & ~arrows_mask );
/**
* Solution data for table attributes
* @type {number[]}
*/
const solution_table = solution && solution[ table ] && solution[ table ].map( attr => attr & ~arrows_mask );
// Table not created? => Render 'add table' button
if ( !input_table )
return html`
<div>
<button type="button" class="btn btn-${ solution ? ( solution_table === input_table ? 'success' : 'danger' ) : 'primary' } btn-sm text-nowrap" ?data-hidden=${ is_solution } ?disabled=${ solution } @click=${ () => app.events.onAddTable( table ) }>+ <span data-lang="main_table">${ app.text.main_table }</span>: "<span data-lang="${ table || phrase.relation ? '' : 'hierarchy_is' }">${ table ? phrase.entities[ table - 1 ] : phrase.relation || app.text.hierarchy_is }</span>"</button>
</div>
`;
// Table created? => Render table
return html`
<div class="box border text-nowrap bg-${ solution ? ( solution_table && solution_table.toString() === input_table.toString() ? 'success' : 'danger' ) : 'white' }">
<!-- Table Header -->
<header class="bg-${ solution ? ( !!solution_table === !!input_table ? 'success' : 'danger' ) : 'light' } border-bottom p-2 d-flex justify-content-center align-items-center">
<!-- Edit Attributes Icon -->
<span class="icon d-flex align-items-center" ?data-invisible=${ solution } @click=${ () => app.events.onEditTable( table ) }>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="text-primary" viewBox="0 0 16 16">
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
</svg>
</span>
<!-- Table Name -->
<span class="mx-2">${ getTableName( app, phrase, table ) }</span>
<!-- Remove Table Icon -->
<span class="icon d-flex align-items-center" ?data-invisible=${ solution || is_solution } @click=${ () => app.events.onRemoveTable( table ) }>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="text-danger" viewBox="0 0 16 16">
<path d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"/>
</svg>
</span>
</header>
<!-- Table Body -->
<main>
<!-- Table Attributes -->
<div class="p-1">
${ input_table.map( ( value, i ) => attr( i ) ) }
</div>
<!-- Composed Primary Key -->
${ key( 'pk' ) }
<!-- Composed Alternate Key -->
${ key( 'ak' ) }
</main>
</div>
`;
/**
* Returns the HTML template for a table attribute.
* @param {number} i - Attribute index in the table
* @returns {*}
*/
function attr( i ) {
/**
* Current bit mask of the table attribute.
* @type {number}
*/
const input_attr = input_table[ i ];
/**
* Bit mask of the solution for the table attribute.
* @type {number}
*/
const solution_attr = solution && solution_table && solution_table[ i ];
return html`
<div class="${ solution ? 'bg-' + ( solution_attr === input_attr ? 'success' : 'danger' ) + ' ' : '' }rounded d-flex align-items-center px-1" ?data-hidden=${ !input_attr }>
<span class="me-1">${ getAttributeName( app, phrase, i ) }</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 2 ) === ( input_attr & 1 << 2 ) ? 'success' : 'danger' ) : 'primary' } ms-1" ?data-hidden=${ !( input_attr & 1 << 2 ) || input_table.filter( value => value & 1 << 2 ).length > 1 }>${ app.text.badge_pk }</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 3 ) === ( input_attr & 1 << 3 ) ? 'success' : 'danger' ) : 'success' } ms-1" ?data-hidden=${ !( input_attr & 1 << 3 ) || input_table.filter( value => value & 1 << 3 ).length > 1 }>${ app.text.badge_ak }</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 4 ) === ( input_attr & 1 << 4 ) ? 'success' : 'danger' ) : 'warning' } ms-1" ?data-hidden=${ !( input_attr & 1 << 4 ) }>${ app.text.badge_fk }0</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 5 ) === ( input_attr & 1 << 5 ) ? 'success' : 'danger' ) : 'warning' } ms-1" ?data-hidden=${ !( input_attr & 1 << 5 ) }>${ app.text.badge_fk }1</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 6 ) === ( input_attr & 1 << 6 ) ? 'success' : 'danger' ) : 'warning' } ms-1" ?data-hidden=${ !( input_attr & 1 << 6 ) }>${ app.text.badge_fk }2</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 7 ) === ( input_attr & 1 << 7 ) ? 'success' : 'danger' ) : 'warning' } ms-1" ?data-hidden=${ !( input_attr & 1 << 7 ) }>${ app.text.badge_fk }3</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 8 ) === ( input_attr & 1 << 8 ) ? 'success' : 'danger' ) : 'warning' } ms-1" ?data-hidden=${ !( input_attr & 1 << 8 ) }>${ app.text.badge_fk }4</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 0 ) === ( input_attr & 1 << 0 ) ? 'success' : 'danger' ) : 'light' } ${ solution ? '' : 'text-dark' } ms-1" ?data-hidden=${ !( input_attr & 1 << 0 ) }>${ app.text.badge_opt }</span>
<span class="badge bg-${ solution ? ( ( solution_attr & 1 << 1 ) === ( input_attr & 1 << 1 ) ? 'success' : 'danger' ) : 'light' } ${ solution ? '' : 'text-dark' } ms-1" ?data-hidden=${ !( input_attr & 1 << 1 ) }>${ app.text.badge_man }</span>
</div>
`;
}
/**
* Returns the HTML template for a composite key.
* @param {string} id - Key ID ('pk' or 'ak')
* @returns {*}
*/
function key( id ) {
let bit, color;
switch ( id ) {
case 'pk': bit = 4; color = 'primary'; break;
case 'ak': bit = 8; color = 'success'; break;
}
return html`
<div class="border-top px-2 py-1 d-flex" ?data-hidden=${ input_table.filter( value => value & bit ).length < 2 }>
<div>
<span class="badge bg-${ color }">${ app.text[ 'badge_' + id ] }</span>
</div>
<div class="ps-2">
${ input_table.map( ( value, i ) => html`<div>${ value & bit ? getAttributeName( app, phrase, i ) : '' }</div>` ) }
</div>
</div>
`;
}
}
/**
* Returns the HTML template for a part of a connection between two scheme tables.
* @param {string} line_id - 'v': vertical, 'h': horizontal, 'l': left, 'r': right, 'u': up, 'd': down, 'ur': up right, 'ul': up left, 'rd': right down, 'dl': down left, 'x': cross
* @param {table_nr} a - Table number of the first table of the first connection.
* @param {table_nr} b - Table number of the second table of the first connection.
* @param {table_nr} [c] - Table number of the first table of the second connection (only when the connecting lines of two table connections cross each other).
* @param {table_nr} [d] - Table number of the second table of the second connection.
* @returns {*}
*/
function connection( line_id, a, b, c, d ) {
if ( line_id === 'x' ) {
const v = isConnected( a, b );
const h = isConnected( c, d );
if ( !v && !h ) return html`<div></div>`;
if ( v && !h ) return line( 'v' );
if ( !v && h ) return line( 'h' );
if ( v && h ) return line( 'x' );
}
return isConnected( a, b ) ? line( line_id ) : html`<div></div>`;
}
/**
* Returns the HTML template for a line as part of a connection between two scheme tables.
* @param {string} line_id - 'v': vertical, 'h': horizontal, 'l': left, 'r': right, 'u': up, 'd': down, 'ur': up right, 'ul': up left, 'rd': right down, 'dl': down left, 'x': cross
* @returns {*}
*/
function line( line_id ) {
if ( line_id === 'x' ) return html`
<div class="line-x">
<svg viewBox="0 0 18 26">
<line x1="9" y1="0" x2="9" y2="26" stroke="black" stroke-width="2"/>
<line x1="0" y1="13" x2="18" y2="13" stroke="black" stroke-width="2"/>
</svg>
</div>
`;
return html`<div class="line-${ line_id }"></div>`;
}
/**
* Returns the HTML template for an arrow as endpoint of a connection between two scheme tables.
* @param {string} arrow_id - 'l': left, 'r': right, 'u': up, 'd': down
* @param {table_nr} from - Number of the table from which the connection starts.
* @param {table_nr} to - Number of the table to which the connection goes.
* @returns {*}
*/
function arrow( arrow_id, from, to ) {
if ( isConnected( from, to ) ) {
const $arrow = () => {
switch ( arrow_id ) {
case 'l':
case 'r':
return html`
<div class="arrow-${ arrow_id }">
<svg viewBox="0 0 18 26">
<line x1="0" y1="12" x2="10" y2="12" stroke="black" stroke-width="2"></line>
<polygon points="10,8 18,12 10,17" style="fill:black"></polygon>
</svg>
</div>
`;
case 'u':
case 'd':
return html`
<div class="arrow-${ arrow_id }">
<svg viewBox="0 0 18 26">
<polygon points="5,8 9,0 13,8" style="fill:black"></polygon>
<line x1="9" y1="8" x2="9" y2="26" stroke="black" stroke-width="2"></line>
</svg>
</div>
`;
}
};
const $line = () => line( arrow_id === 'r' || arrow_id === 'l' ? 'h' : 'v' );
if ( app.auto_arrows )
return hasRef( from, to ) ? $arrow() : $line();
const input_attr = input[ from ][ to ];
const input_bit = input_attr & 1 << ( 9 + to );
const solution_attr = solution && solution[ from ] && solution[ from ][ to ];
const solution_bit = solution_attr & 1 << ( 9 + to );
return html`
<div class="endpoint d-flex justify-content-center align-items-center${ solution ? ' bg-' + ( input_bit === solution_bit ? 'success' : 'danger' ) : '' }">
<select required ?disabled=${ solution || is_solution } @change=${ event => app.events.onArrow( event.target.value, from, to ) }>
<option value=""></option>
<option value="line" ?selected=${ !input_bit && is_solution }>—</option>
<option value="arrow" ?selected=${ input_bit }>→</option>
</select>
<div ?data-hidden=${ input_attr & input_bit }>
${ $line() }
</div>
<div ?data-hidden=${ !( input_attr & input_bit ) }>
${ $arrow() }
</div>
</div>
`;
}
return html`<div class="arrow-${ arrow_id }"></div>`;
}
}
/**
* Checks if two tables are connected.
* @param {table_nr} a - table
* @param {table_nr} b - another table
* @returns {boolean}
*/
function isConnected( a, b ) {
return hasRef( a, b ) || hasRef( b, a );
}
/**
* Checks if a table has a reference (foreign key) to another table.
* @param {table_nr} from - Table potentially referencing another table.
* @param {table_nr} to - Table that is potentially referenced.
* @returns {boolean}
*/
function hasRef( from, to ) {
const value = result.input[ from ] && ( result.input[ to ] || is_recursive && to === 2 ) && result.input[ from ][ to ];
return !!( value & 1 << 4 || value & 1 << 5 || value & 1 << 6 || value & 1 << 7 || value & 1 << 8 );
}
/**
* Returns the HTML template for a comment.
* @param {string} index - Comment index in config
* @param {boolean} condition - Condition under which the comment is displayed.
* @returns {*}
*/
function comment( index, condition ) {
return condition ? html`<div class="alert alert-info my-2 text-wrap" role="alert" data-lang="comment_${ index }">${ app.text[ 'comment_' + index ] }</div>` : html`<div></div>`;
}
}
/**
* Returns the HTML template of the legend table that shows the different notations in the ER diagram.
* @function
* @param {object} app - App instance.
* @returns {*}
*/
export function legend( app ) {
const ids = [ '1', 'c', 'n', 'cn' ];
return html`
<table class="table table-bordered">
<thead>
<tr>
<th scope="col"></th>
${ ids.map( id => html`<th scope="col" data-lang="legend_${ id }">${ app.text[ 'legend_' + id ] }</th>`) }
</tr>
</thead>
<tbody>
${ Object.values( app.notations ).sort( ( a, b ) => a.title.localeCompare( b.title ) ).map( notation => html`
<tr>
<th scope="row" style="vertical-align: middle">${ notation.title }</th>
${ ids.map( ( id, i ) => html`<td><img src="${ notation.images[ i + 1 ] }"></td>` ) }
</tr>
` ) }
</tbody>
</table>
`;
}
/**
* Returns the HTML template for the title of the table dialog.
* @function
* @param {object} app - App instance.
* @param {table_nr} table - Table number.
* @returns {*}
*/
export function tableDialogTitle( app, table ) {
const data = app.getValue();
const phrase = data.phrases[ data.results.length - 1 ];
return html`
<span data-lang='main_table'>${ app.text.main_table }</span>: ${ table && phrase.entities[ table - 1 ] || phrase.relation || app.text.hierarchy_is }
`;
}
/**
* Returns the HTML template for the body of the table dialog.
* @function
* @param {object} app - App instance.
* @param {table_nr} table - Table number.
* @returns {*}
*/
export function tableDialogBody( app, table ) {
const data = app.getValue();
const phrase = data.phrases[ data.results.length - 1 ];
const result = data.results[ data.results.length - 1 ];
return html`
<table class="table table-bordered table-striped text-nowrap">
<tbody>
${ result.input[ table ].map( ( _, i, arr ) => attr( i ) ) }
</tbody>
</table>
`;
/**
* Returns the HTML template for a row of a table attribute.
* @param {table_nr} attr - Table that references the attribute as a foreign key.
* @returns {*}
*/
function attr( attr ) {
return html`
<tr>
<td>
${ getAttributeName( app, phrase, attr ) }
</td>
<td>
<span class="badge ${ result.input[ table ][ attr ] & 1 << 2 ? 'on' : 'off' } bg-primary" @click=${ () => app.events.onToggleBadge( table, attr, 2 ) }>${ app.text.badge_pk }</span>
<span class="badge ${ result.input[ table ][ attr ] & 1 << 3 ? 'on' : 'off' } bg-success" @click=${ () => app.events.onToggleBadge( table, attr, 3 ) }>${ app.text.badge_ak }</span>
<span class="badge ${ result.input[ table ][ attr ] & 1 << attr + 4 ? 'on' : 'off' } bg-warning" ?data-invisible=${ app.hide_own_fk && table === attr } @click=${ () => app.events.onToggleBadge( table, attr, attr + 4 ) }>${ app.text.badge_fk }${ attr }</span>
<span class="d-inline-flex">
<span class="badge left ${ result.input[ table ][ attr ] & 1 ? 'on' : 'off' } bg-secondary" @click=${ () => app.events.onToggleBadge( table, attr, 0 ) }>${ app.text.badge_opt }</span>
<span class="badge right ${ result.input[ table ][ attr ] & 1 << 1 ? 'on' : 'off' } bg-secondary" @click=${ () => app.events.onToggleBadge( table, attr, 1 ) }>${ app.text.badge_man }</span>
</span>
</td>
</tr>
`;
}
}
/**
* Returns the HTML template for the footer of the table dialog.
* @function
* @param {object} app - App instance.
* @returns {*}
*/
export function tableDialogFooter( app ) {
return html`
<div data-lang="table_dialog_info">${ app.text.table_dialog_info }</div>
<div class="d-flex flex-wrap mt-2">
<div class="me-2 mb-1 d-flex align-items-center">
<span class="badge bg-primary" data-lang="badge_pk">${ app.text.badge_pk }</span>:
<span data-lang="badge_pk_title">${ app.text.badge_pk_title }</span>
</div>
<div class="me-2 mb-1 d-flex align-items-center">
<span class="badge bg-success" data-lang="badge_ak">${ app.text.badge_ak }</span>:
<span data-lang="badge_ak_title">${ app.text.badge_ak_title }</span>
</div>
<div class="me-2 mb-1 d-flex align-items-center">
<span class="badge bg-warning" data-lang="badge_fk">${ app.text.fk }</span>:
<span data-lang="badge_fk_title">${ app.text.badge_fk_title }</span>
</div>
</div>
`;
}
/**
* Returns the name of a table.
* @private
* @function
* @param {object} app - App instance.
* @param {phrase_data} phrase - Phrase to which the table belongs.
* @param {table_nr} table - Table number.
* @returns {string}
*/
function getTableName( app, phrase, table ) {
return table && phrase.entities[ table - 1 ] || phrase.relation || app.text.hierarchy_is;
}
/**
* Returns the name of a table attribute.
* @private
* @function
* @param {object} app - App instance.
* @param {phrase_data} phrase - Phrase to which the attribute belongs.
* @param {table_nr} attr - Table that references the attribute as a foreign key.
* @returns {string}
*/
function getAttributeName( app, phrase, attr ) {
const getRole = entity_nr => attr && phrase.roles && phrase.roles[ entity_nr - 1 ];
const is_recursive = phrase.entities.length === 2 && phrase.entities[ 0 ] === phrase.entities[ 1 ];
const name = getTableName( app, phrase, attr );
return toID( getRole( attr ) || is_recursive && attr === 2 && !getRole( 1 ) && name + '2' || name );
}
/**
* Converts a string to an identifier in snake case.
* @private
* @function
* @param {string} str
* @returns {string}
* @example
* toID('Person') // => person_id
*/
function toID( str ) {
return str.toLowerCase().trim().replace( /ä/g, 'ae' ).replace( /ö/g, 'oe' ).replace( /ü/g, 'ue' ).replace( /ß/g, 'ss' ).replace( /\W/g, '_' ) + '_id';
}