[skip ci]

PHRAS-3381_tx-as-classification-plan_MASTER
dlgbox
This commit is contained in:
jygaulier
2021-03-09 21:36:56 +01:00
parent 90ed397619
commit a9fb061a57
8 changed files with 468 additions and 14 deletions

View File

@@ -16,6 +16,12 @@ const thesaurusService = services => {
let dragging = false; // true when an object is dragged over the th zone
let dragTarget = null; // the target where the mouse is over
let dragUniqueSbid = null; // will end-up as : null (nothing dragged) ; false (many sbids) ; sbid (same sbid for all)
let dragLstRecords = '' // list or records, format as expected for RecordsRequest::fromRequest
const url = configService.get('baseUrl');
let searchSelection = {asArray: [], serialized: ''};
const initialize = params => {
let { $container } = params;
@@ -105,6 +111,18 @@ const thesaurusService = services => {
$('#THPD_T_tree')
.droppable({
accept: function(elem) {
let lstbr = searchSelection.asArray;
console.log("lstbr", lstbr);
dragUniqueSbid = null;
lstbr.forEach(sbid_rid => {
sbid_rid = sbid_rid.split('_');
let sbid = sbid_rid[0];
let rid = sbid_rid[1];
dragUniqueSbid = (dragUniqueSbid===null) ? sbid : (sbid===dragUniqueSbid ? sbid : false);
});
dragLstRecords = lstbr.join(';'); // a list as expected for RecordsRequest::fromRequest
$(this).removeClass('draggingOver');
console.log("accept", elem);
// if ($(elem).hasClass('grouping') && !$(elem).hasClass('SSTT')) {
@@ -115,15 +133,37 @@ const thesaurusService = services => {
// the th zone can accet drags only when in front (activated tab)
// 'hash' is set by the 'workzone' js code.
return $('#idFrameC .tabs').data('hash') === '#thesaurus_tab';
// return $('#idFrameC .tabs').data('hash') === '#thesaurus_tab';
if($('#idFrameC .tabs').data('hash') !== '#thesaurus_tab') {
return false; // can't drop on th if the th tab is not front
}
// by using classes on both main container AND the (unique) acceptable thesaurus zone
// we can have custom drag/drop css for both ok / reject
$('#THPD_T_tree', $container).removeClass('draggingOver'); // the container
$('#THPD_T_tree>LI', $container).removeClass('draggingOver'); // all thesaurus
if(dragUniqueSbid === null || dragUniqueSbid === false) {
// many sbids
// return false; // don't return false, as it will prevent "over" and will not apply css (no "not-allowed" cursor)
}
return true;
},
scope: 'objects',
hoverClass: 'groupDrop',
tolerance: 'pointer',
over: function(event, ui) {
$(this).addClass('draggingOver');
console.log("over", event, ui, event.toElement);
$('#THPD_T_tree', $container).addClass('draggingOver');
if(dragUniqueSbid !== null && dragUniqueSbid !== false) {
$('#TX_P\\.'+dragUniqueSbid+'\\.T', $container).addClass('draggingOver');
}
/*
$(this).addClass('draggingOver');
if(dragTarget) {
// something was already hilighted (should no happen)
dragTarget.removeClass('dragOver');
@@ -139,20 +179,40 @@ const thesaurusService = services => {
dragTarget.addClass('dragOver');
console.log("IN : " + dragTarget.attr('id'));
}
*/
},
out: function(event, ui) {
$(this).removeClass('draggingOver');
console.log("out", event, ui, event.toElement);
$('#THPD_T_tree', $container).removeClass('draggingOver');
$('#THPD_T_tree>LI', $container).removeClass('draggingOver');
/*
$(this).removeClass('draggingOver');
if(dragTarget) {
// something was hilighted
dragTarget.removeClass('dragOver');
}
dragging = false; // == no more dragging something over th
dragTarget = null;
*/
},
drop: (event, ui) => {
$(this).removeClass('draggingOver');
console.log("drop", event, ui);
$('#THPD_T_tree', $container).removeClass('draggingOver');
$('#THPD_T_tree>LI', $container).removeClass('draggingOver');
const target = $(event.toElement);
const sbas_id = target.data('sbas_id').toString(); // set on html by ThesaurusXmlHttpController.php
const tx_term_id = target.data('tx_term_id').toString(); // set on html by ThesaurusXmlHttpController.php
if(sbas_id === dragUniqueSbid) {
dropRecordsOnTerm(sbas_id, tx_term_id, dragLstRecords);
}
/*
$(this).removeClass('draggingOver');
if(dragTarget) {
// const tid = $(event.toElement).data('tx_term_id');
console.log("DROP ON id=" + dragTarget.attr('id'));
@@ -161,6 +221,8 @@ const thesaurusService = services => {
}
dragging = false; // == no more dragging something over th
dragTarget = null;
*/
}
})
// track the mouse
@@ -194,6 +256,57 @@ const thesaurusService = services => {
searchValue = _.debounce(searchValue, 300);
};
function dropRecordsOnTerm(sbas_id, tx_term_id, lstRecords) {
let dlg = dialog.create(
services,
{
size: 'Custom',
customWidth: 770,
customHeight: 400,
title: localeService.t('add data'),
loading: true
},
0
);
// dlg.setContent("");
// let parms = {
// url: '/thesaurus/drop_records.php',
// data: {
// 'sbas_id': sbas_id,
// 'tx_term_id': tx_term_id,
// 'lst': lstRecords
// },
// async: true,
// cache: false,
// dataType: 'json',
// // timeout: 10 * 60 * 1000, // 10 minutes !
// success: function (result, textStatus) {
// dlg.setContent(result);
// }
// };
$.get(
`${url}prod/thesaurus/droprecords`,
{
'dlg_level': 0,
'sbas_id': sbas_id,
'tx_term_id': tx_term_id,
'lst': lstRecords
},
function (data, textStatus) {
dlg.setContent(data);
}
);
}
function show() {
// first show of thesaurus
if (options.currentWizard === '???') {
@@ -1462,6 +1575,13 @@ const thesaurusService = services => {
});
}
appEvents.listenAll({
'broadcast.searchResultSelection': (selection) => {
searchSelection = selection;
}
});
return { initialize, show };
};

View File

@@ -189,10 +189,25 @@ dans l'onglet thesaurus : arbres, menus contextuels
}
#THPD_T_tree.draggingOver {
//background-color: #FF0000;
/* something is currently dragged over, we change some hover */
&:hover {
cursor: not-allowed;
}
span.tx_term:hover {
color: $mediumTextColor; // no more highlight
cursor: not-allowed;
}
LI.draggingOver {
//background-color: #00FF00;
&:hover {
cursor: default;
}
span.tx_term:hover {
background-color: $highlightBackgroundColor;
color: $highlightTextColor;
cursor: copy;
}
}
}

View File

@@ -65,6 +65,7 @@ class RouteLoader
'/prod/share/' => Providers\Prod\Share::class,
'/prod/story' => Providers\Prod\Story::class,
'/prod/subdefs' => Providers\Prod\Subdefs::class,
'/prod/thesaurus/' => Providers\Prod\Thesaurus::class,
'/prod/tools/' => Providers\Prod\Tools::class,
'/prod/tooltip' => Providers\Prod\Tooltip::class,
'/prod/TOU/' => Providers\Prod\TOU::class,

View File

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\Controller\Prod;
use Alchemy\Phrasea\Application\Helper\DataboxLoggerAware;
use Alchemy\Phrasea\Application\Helper\DispatcherAware;
use Alchemy\Phrasea\Application\Helper\FilesystemAware;
use Alchemy\Phrasea\Controller\Controller;
use Alchemy\Phrasea\Controller\RecordsRequest;
use DOMElement;
use DOMXPath;
use Exception;
use Symfony\Component\HttpFoundation\Request;
class ThesaurusController extends Controller
{
use DataboxLoggerAware;
use DispatcherAware;
use FilesystemAware;
public function dropRecordsAction(Request $request): string
{
$sbas_id = $request->get('sbas_id');
$tx_term_id = $request->get('tx_term_id');
$records = RecordsRequest::fromRequest($this->app, $request, false);
// twig parameters
$twp = [
'error' => null,
'dlg_level' => $request->get('dlg_level'),
// 'fields' => [], // fields the can receive the value
// 'fvalue' => 'Europe',
'up_paths' => [],
];
// find which field(s) can be updated, that is what tbranches are linked to a parent of the term
try {
$dbox = $this->app->getApplicationBox()->get_databox($sbas_id);
if (!($domth = $dbox->get_dom_thesaurus())) {
throw new Exception("error fetching th");
}
$upPaths = [];
$xpath = new DOMXPath($domth);
foreach ($dbox->get_meta_structure() as $field) {
if (!($q = $field->get_tbranch())) {
continue;
}
$fieldName = $field->get_name();
$roots = $xpath->query($q); // linked nodes for this field
$q = '(' . $q . ')//sy[@id=\'' . $tx_term_id . '\']'; // can we find the term under the tbranch(es) ?
// normally we should find only one linked parent, since we search from a unique term
// ...but... a bad th could link a field onto 2 points of the same branch :
// root
// |---A <-- "keyword" is linked here...
// |---B <-- ...but also linked here (bad idea btw)
// |---terms
// going up, we decide to stop at the first link (B) (easier)
if (($droppedSy = $xpath->query($q))->length > 0) {
// yes (and since the query targets a unique id, there is only one result)
$droppedSy = $droppedSy->item(0);
/** @var DOMElement $droppedSy */
$droppedlng = $droppedSy->getAttribute('lng'); // the lng of the dropped term is prefered
// go from the sy upto a linked branch (possibly multiples if the field is linked to many branches)
$ok = true; // == the term (level up-to top) can populate the current field
for ($te = $droppedSy->parentNode; $te->nodeType === XML_ELEMENT_NODE; $te = $te->parentNode) {
/** @var DOMElement $te */
$teid = $te->getAttribute('id');
for ($i = 0; $i < $roots->length; $i++) {
if ($te->isSameNode($roots->item($i))) {
$ok = false; // we met the link point, upmost terms are not "values" anymore
}
}
if ($ok) { // acceptable value for the current field
if (!array_key_exists($teid, $upPaths)) {
$upPaths[$teid] = [
'synonyms' => [],
'fields' => []
];
// get all the sy so the user can choose which is prefered
$preferedId = null;
foreach ($te->childNodes as $sy) {
if ($sy->nodeName != 'sy') {
continue; // skip 'te' children
}
$lng = $sy->getAttribute('lng');
$id = $sy->getAttribute('id');
$s = [
'value' => $sy->getAttribute('v'),
'lng' => $lng,
'selected' => false
];
// this sy is prefered if...
if ($sy->getAttribute('lng') === $droppedlng) {
$preferedId = $id; // ... it has the same lng as the dropped
}
if ($sy->isSameNode($droppedSy)) {
$preferedId = $id; // ... better : it was the dropped target
}
$upPaths[$teid]['synonyms'][$id] = $s;
}
if ($preferedId) {
$upPaths[$teid]['synonyms'][$preferedId]['selected'] = true;
}
}
$upPaths[$teid]['fields'][$field->get_id()] = $field;
}
}
$twp['up_paths'] = array_reverse($upPaths);
$twp['fields'][] = $field;
// $field->
}
}
if (empty($upPaths)) {
// no fields (could happen if one drops on a top-level branch, or if the th is not linked, or...)
throw new Exception("this branch is not linked");
}
}
catch (Exception $e) {
$twp['error'] = $e->getMessage();
}
return $this->render('prod/Thesaurus/droppedrecords.html.twig', $twp);
}
}

View File

@@ -87,6 +87,7 @@ class ControllerProviderServiceProvider implements ServiceProviderInterface
Prod\Share::class => [],
Prod\Story::class => [],
Prod\Subdefs::class => [],
Prod\Thesaurus::class => [],
Prod\Tools::class => [],
Prod\Tooltip::class => [],
Prod\TOU::class => [],

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of Phraseanet
*
* (c) 2005-2016 Alchemy
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Phrasea\ControllerProvider\Prod;
use Alchemy\Phrasea\Application as PhraseaApplication;
use Alchemy\Phrasea\Controller\Prod\ThesaurusController;
use Alchemy\Phrasea\ControllerProvider\ControllerProviderTrait;
use Alchemy\Phrasea\Core\LazyLocator;
use Silex\Application;
use Silex\ControllerProviderInterface;
use Silex\ServiceProviderInterface;
class Thesaurus implements ControllerProviderInterface, ServiceProviderInterface
{
use ControllerProviderTrait;
public function register(Application $app)
{
$app['controller.prod.thesaurus'] = $app->share(function (PhraseaApplication $app) {
return (new ThesaurusController($app))
->setDataboxLoggerLocator($app['phraseanet.logger'])
->setDispatcher($app['dispatcher'])
->setFileSystemLocator(new LazyLocator($app, 'filesystem'))
;
});
}
public function boot(Application $app)
{
// no-op
}
public function connect(Application $app)
{
$controllers = $this->createAuthenticatedCollection($app);
// $firewall = $this->getFirewall($app);
// $controllers->before(function () use ($firewall) {
// $firewall->requireRight(\ACL::CANMODIFRECORD);
// });
$controllers->get('/droprecords', 'controller.prod.thesaurus:dropRecordsAction');
return $controllers;
}
}

View File

@@ -0,0 +1,122 @@
{% if error %}
{{ error }}
{% else %}
<form id="DroppedOnTH_form">
<table>
<tr>
<th>set value</th>
<th/>
<th>to field</th>
<th class="multiTitle" style="display: none">multi</th>
</tr>
{% if up_paths|length > 1 %}
<tr class="toggleMoreFields">
<td colspan="4">
<button>+</button>&nbsp;Other value(s)...
</td>
</tr>
{% endif %}
{% set n=1 %}
{% for tid,up_path in up_paths %}
{% set cl = (n == up_paths|length) ? "" : "class='other' style='display:none'"%}
<tr {{ cl|raw }}>
<td>
{% if 0 and up_path.synonyms|length == 1 %}
{% set synonym = up_path.synonyms|first %}
<option {{ synonym.selected ? "selected" : "" }}>{{ synonym.value }}</option>
{% else %}
<select>
{% for synonym in up_path.synonyms %}
<option {{ synonym.selected ? "selected" : "" }}>{{ synonym.value }}</option>
{% endfor%}
</select>
{% endif %}
</td>
<td style="alignment: center; font-size: xx-large">
&rarr;
</td>
<td>
<select class="fieldSelect" data-n="{{ n }}">
{% set sel="" %}
{% if n == up_paths|length %}
<option data-multi="0">select...</option>
{% if up_path.fields|length == 1 %}
{% set sel="selected" %}
{% endif %}
{% else %}
<option data-multi="0">none</option>
{% endif %}
{% for field in up_path.fields %}
<option {{ sel }} data-multi="{{ field.is_multi() ? 1 : 0 }}">{{ field.get_name() }}</option>
{% endfor %}
</select>
</td>
<td class="multiRadio _{{ n }}" style="padding-left:10px; display: none">
<input type="radio" checked>&nbsp;add
&nbsp;&nbsp;
<input type="radio">&nbsp;replace
</td>
</tr>
{% set n = n+1 %}
{% endfor %}
</table>
</form>
{% endif %}
<script type="application/javascript">
let $container = $('#DroppedOnTH_form');
let dlg = dialog.get({{ dlg_level }});
dlg.setOption("buttons",
[
{
text: "Ok",
click: function() {
$( this ).dialog( "close" );
}
},
{
text: "Cancel",
click: function() {
$( this ).dialog( "close" );
}
}
]
);
/**
* when a destination field is selected, show/hide the "multi" radio
*/
$(' .fieldSelect', $container)
.change(function (event) {
let select = $(this);
let n = select.data('n');
console.log(n);
if( $('option:eq(' + select.prop('selectedIndex') + ')', select).data('multi') ) {
// this select is on a multi-valued field
$(' .multiRadio._'+n, $container).show();
}
else {
// this select is on a mono-valued field
$(' .multiRadio._'+n, $container).hide();
}
// show the column title only if one field is multi
if( $(' .multiRadio:visible', $container).length > 0 ) {
$(' .multiTitle', $container).show();
}
else {
$(' .multiTitle', $container).hide();
}
})
.change(); // enforce initial update
$(' .toggleMoreFields BUTTON', $container).click(function (event) {
$(' .toggleMoreFields', $container).hide();
$(' .other').show();
return false;
})
</script>

View File

@@ -1082,8 +1082,10 @@
url += "&typ=" + typ;
url += "&sylng=" + encodeURIComponent(lng);
var title = typ=="TS" ? "{{ 'thesaurus:: Nouveau terme specifique' | trans }}" : "{{ 'thesaurus:: Nouveau synonyme' | trans }}";
// w = window.open(url, "NEWTERM", "directories=no, height=290, width=490, location=no, menubar=no, resizable=yes, scrollbars=yes, status=no, toolbar=no");
w = loadDataAjax(url,490, 350,"#NEWSY_DLG_CONFIRM",'{% if typ == "TS" %}{{ 'thesaurus:: Nouveau terme specifique' | trans }}{% else %}{{ 'thesaurus:: Nouveau synonyme' | trans }}{% endif %}');
w = loadDataAjax(url, 490, 350, "#NEWSY_DLG_CONFIRM", title);
}
}
],