Add homepage block, reactive controls, i18n, and full-width layout
- Implement ModuleBlockInterface for tree/user homepage embedding with configurable individual and generation settings - Make generation sliders reactive (auto-navigate on change) - Persist generation counts when clicking between persons - Expand chart to full viewport width and remaining height - Add German and Dutch translations for all custom strings - Pass translated labels to JS bio card tooltip via window.fullDiagramI18n - Use webtrees core translation keys where available - Increase default generations to 4, remove show siblings checkbox - Expose Chart class globally for block instantiation
This commit is contained in:
81
resources/views/modules/full-diagram/block-config.phtml
Normal file
81
resources/views/modules/full-diagram/block-config.phtml
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Fisharebest\Webtrees\I18N;
|
||||
use Fisharebest\Webtrees\Individual;
|
||||
use Fisharebest\Webtrees\Tree;
|
||||
|
||||
/**
|
||||
* @var Individual|null $individual
|
||||
* @var Tree $tree
|
||||
* @var int $ancestor_generations
|
||||
* @var int $descendant_generations
|
||||
* @var bool $show_siblings
|
||||
*/
|
||||
?>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label" for="xref">
|
||||
<?= I18N::translate('Individual') ?>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<?= view('components/select-individual', [
|
||||
'name' => 'xref',
|
||||
'individual' => $individual,
|
||||
'tree' => $tree,
|
||||
]) ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label" for="ancestor_generations">
|
||||
<?= I18N::translate('Generations of ancestors') ?>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="ancestor_generations"
|
||||
name="ancestor_generations"
|
||||
min="1"
|
||||
max="10"
|
||||
value="<?= $ancestor_generations ?>"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<label class="col-sm-3 col-form-label" for="descendant_generations">
|
||||
<?= I18N::translate('Generations of descendants') ?>
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="descendant_generations"
|
||||
name="descendant_generations"
|
||||
min="1"
|
||||
max="10"
|
||||
value="<?= $descendant_generations ?>"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-9 offset-sm-3">
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="show_siblings"
|
||||
name="show_siblings"
|
||||
value="1"
|
||||
<?= $show_siblings ? 'checked' : '' ?>
|
||||
>
|
||||
<label class="form-check-label" for="show_siblings">
|
||||
<?= I18N::translate('Show siblings') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
74
resources/views/modules/full-diagram/block.phtml
Normal file
74
resources/views/modules/full-diagram/block.phtml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Fisharebest\Webtrees\I18N;
|
||||
use Fisharebest\Webtrees\Individual;
|
||||
use Fisharebest\Webtrees\Module\ModuleChartInterface;
|
||||
use Fisharebest\Webtrees\Tree;
|
||||
|
||||
/**
|
||||
* @var ModuleChartInterface $module
|
||||
* @var Individual $individual
|
||||
* @var Tree $tree
|
||||
* @var string $tree_data
|
||||
* @var string $javascript_url
|
||||
* @var string $stylesheet_url
|
||||
* @var int $block_id
|
||||
* @var int $ancestor_generations
|
||||
* @var int $descendant_generations
|
||||
* @var bool $show_siblings
|
||||
*/
|
||||
|
||||
$containerId = 'full-diagram-block-' . $block_id;
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="<?= e($stylesheet_url) ?>">
|
||||
|
||||
<div id="<?= $containerId ?>" class="full-diagram-container full-diagram-block wt-chart" data-tree-name="<?= e($tree->name()) ?>" style="height:450px;">
|
||||
<div class="full-diagram-chart"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
if (!window.fullDiagramI18n) {
|
||||
window.fullDiagramI18n = <?= json_encode([
|
||||
'Born' => I18N::translate('Born'),
|
||||
'Baptism' => I18N::translate('Baptism'),
|
||||
'Marriage' => I18N::translate('Marriage'),
|
||||
'Died' => I18N::translate('Died'),
|
||||
'Occupation' => I18N::translate('Occupation'),
|
||||
'Residence' => I18N::translate('Residence'),
|
||||
'View profile' => I18N::translate('View profile'),
|
||||
'Died at age %s' => I18N::translate('Died at age %s', '__AGE__'),
|
||||
'Deceased' => I18N::translate('Deceased'),
|
||||
'Age ~%s' => I18N::translate('Age ~%s', '__AGE__'),
|
||||
], JSON_THROW_ON_ERROR) ?>;
|
||||
}
|
||||
var data = <?= $tree_data ?>;
|
||||
var baseUrl = <?= json_encode(route($module::ROUTE_NAME, [
|
||||
'tree' => $tree->name(),
|
||||
'xref' => '__XREF__',
|
||||
'ancestor_generations' => $ancestor_generations,
|
||||
'descendant_generations' => $descendant_generations,
|
||||
'show_siblings' => $show_siblings ? '1' : '0',
|
||||
]), JSON_THROW_ON_ERROR) ?>;
|
||||
|
||||
function initBlock() {
|
||||
if (typeof window.FullDiagramChart === 'undefined') {
|
||||
// JS not loaded yet, retry
|
||||
setTimeout(initBlock, 100);
|
||||
return;
|
||||
}
|
||||
var chart = new window.FullDiagramChart('#<?= $containerId ?>', data, baseUrl);
|
||||
chart.render();
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initBlock);
|
||||
} else {
|
||||
initBlock();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script src="<?= e($javascript_url) ?>"></script>
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Fisharebest\Webtrees\I18N;
|
||||
use Fisharebest\Webtrees\Individual;
|
||||
use Fisharebest\Webtrees\Module\ModuleChartInterface;
|
||||
use Fisharebest\Webtrees\Tree;
|
||||
@@ -13,17 +14,50 @@ use Fisharebest\Webtrees\Tree;
|
||||
* @var string $tree_data
|
||||
* @var string $javascript_url
|
||||
* @var string $stylesheet_url
|
||||
* @var int $ancestor_generations
|
||||
* @var int $descendant_generations
|
||||
* @var bool $show_siblings
|
||||
*/
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="<?= e($stylesheet_url) ?>">
|
||||
|
||||
<div id="full-diagram-container" class="full-diagram-container wt-chart" data-tree-name="<?= e($tree->name()) ?>" style="width:100%;height:700px;">
|
||||
<div id="full-diagram-container" class="full-diagram-container wt-chart" data-tree-name="<?= e($tree->name()) ?>">
|
||||
<div class="full-diagram-chart"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var el = document.getElementById('full-diagram-container');
|
||||
function resize() {
|
||||
var rect = el.getBoundingClientRect();
|
||||
el.style.height = Math.max(400, window.innerHeight - rect.top - 16) + 'px';
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
window.fullDiagramI18n = <?= json_encode([
|
||||
'Born' => I18N::translate('Born'),
|
||||
'Baptism' => I18N::translate('Baptism'),
|
||||
'Marriage' => I18N::translate('Marriage'),
|
||||
'Died' => I18N::translate('Died'),
|
||||
'Occupation' => I18N::translate('Occupation'),
|
||||
'Residence' => I18N::translate('Residence'),
|
||||
'View profile' => I18N::translate('View profile'),
|
||||
'Died at age %s' => I18N::translate('Died at age %s', '__AGE__'),
|
||||
'Deceased' => I18N::translate('Deceased'),
|
||||
'Age ~%s' => I18N::translate('Age ~%s', '__AGE__'),
|
||||
], JSON_THROW_ON_ERROR) ?>;
|
||||
window.fullDiagramData = <?= $tree_data ?>;
|
||||
window.fullDiagramBaseUrl = <?= json_encode(route($module::ROUTE_NAME, ['tree' => $tree->name(), 'xref' => '__XREF__']), JSON_THROW_ON_ERROR) ?>;
|
||||
window.fullDiagramBaseUrl = <?= json_encode(route($module::ROUTE_NAME, [
|
||||
'tree' => $tree->name(),
|
||||
'xref' => '__XREF__',
|
||||
'ancestor_generations' => $ancestor_generations,
|
||||
'descendant_generations' => $descendant_generations,
|
||||
'show_siblings' => $show_siblings ? '1' : '0',
|
||||
]), JSON_THROW_ON_ERROR) ?>;
|
||||
</script>
|
||||
<script src="<?= e($javascript_url) ?>"></script>
|
||||
|
||||
@@ -35,73 +35,94 @@ use FullDiagram\Configuration;
|
||||
|
||||
<h2 class="wt-page-title"><?= $title ?></h2>
|
||||
|
||||
<form method="post" class="wt-page-options wt-page-options-chart d-print-none mb-3">
|
||||
<?= csrf_field() ?>
|
||||
|
||||
<div class="wt-page-options wt-page-options-chart d-print-none mb-3">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-auto">
|
||||
<label for="ancestor_generations" class="form-label">
|
||||
<?= I18N::translate('Ancestor generations') ?>
|
||||
<?= I18N::translate('Generations of ancestors') ?>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
id="ancestor_generations"
|
||||
name="ancestor_generations"
|
||||
min="<?= $min_generations ?>"
|
||||
max="<?= $max_generations ?>"
|
||||
value="<?= $ancestor_generations ?>"
|
||||
oninput="document.getElementById('ancestor_gen_label').textContent = this.value"
|
||||
>
|
||||
<span id="ancestor_gen_label" class="badge bg-secondary"><?= $ancestor_generations ?></span>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<label for="descendant_generations" class="form-label">
|
||||
<?= I18N::translate('Descendant generations') ?>
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
id="descendant_generations"
|
||||
name="descendant_generations"
|
||||
min="<?= $min_generations ?>"
|
||||
max="<?= $max_generations ?>"
|
||||
value="<?= $descendant_generations ?>"
|
||||
oninput="document.getElementById('descendant_gen_label').textContent = this.value"
|
||||
>
|
||||
<span id="descendant_gen_label" class="badge bg-secondary"><?= $descendant_generations ?></span>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<div class="form-check">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-check-input"
|
||||
id="show_siblings"
|
||||
name="show_siblings"
|
||||
value="1"
|
||||
<?= $show_siblings ? 'checked' : '' ?>
|
||||
type="range"
|
||||
class="form-range"
|
||||
id="ancestor_generations"
|
||||
min="<?= $min_generations ?>"
|
||||
max="<?= $max_generations ?>"
|
||||
value="<?= $ancestor_generations ?>"
|
||||
style="width: 120px;"
|
||||
>
|
||||
<label class="form-check-label" for="show_siblings">
|
||||
<?= I18N::translate('Show siblings') ?>
|
||||
</label>
|
||||
<span id="ancestor_gen_label" class="badge bg-secondary"><?= $ancestor_generations ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<?= I18N::translate('View') ?>
|
||||
</button>
|
||||
<label for="descendant_generations" class="form-label">
|
||||
<?= I18N::translate('Generations of descendants') ?>
|
||||
</label>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
id="descendant_generations"
|
||||
min="<?= $min_generations ?>"
|
||||
max="<?= $max_generations ?>"
|
||||
value="<?= $descendant_generations ?>"
|
||||
style="width: 120px;"
|
||||
>
|
||||
<span id="descendant_gen_label" class="badge bg-secondary"><?= $descendant_generations ?></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<?= view($module->name() . '::modules/full-diagram/chart', [
|
||||
'module' => $module,
|
||||
'individual' => $individual,
|
||||
'tree' => $tree,
|
||||
'tree_data' => $tree_data,
|
||||
'javascript_url' => $javascript_url,
|
||||
'stylesheet_url' => $stylesheet_url,
|
||||
'module' => $module,
|
||||
'individual' => $individual,
|
||||
'tree' => $tree,
|
||||
'tree_data' => $tree_data,
|
||||
'javascript_url' => $javascript_url,
|
||||
'stylesheet_url' => $stylesheet_url,
|
||||
'ancestor_generations' => $ancestor_generations,
|
||||
'descendant_generations' => $descendant_generations,
|
||||
'show_siblings' => $show_siblings,
|
||||
]) ?>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var baseChartUrl = <?= json_encode(route($module::ROUTE_NAME, [
|
||||
'tree' => $tree->name(),
|
||||
'xref' => $individual->xref(),
|
||||
]), JSON_THROW_ON_ERROR) ?>;
|
||||
|
||||
var timer = null;
|
||||
|
||||
function navigate() {
|
||||
var ag = document.getElementById('ancestor_generations').value;
|
||||
var dg = document.getElementById('descendant_generations').value;
|
||||
var sep = baseChartUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
window.location.href = baseChartUrl + sep
|
||||
+ 'ancestor_generations=' + ag
|
||||
+ '&descendant_generations=' + dg
|
||||
+ '&show_siblings=1';
|
||||
}
|
||||
|
||||
function scheduleNavigate() {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(navigate, 400);
|
||||
}
|
||||
|
||||
var ag = document.getElementById('ancestor_generations');
|
||||
var dg = document.getElementById('descendant_generations');
|
||||
|
||||
ag.addEventListener('input', function() {
|
||||
document.getElementById('ancestor_gen_label').textContent = this.value;
|
||||
});
|
||||
dg.addEventListener('input', function() {
|
||||
document.getElementById('descendant_gen_label').textContent = this.value;
|
||||
});
|
||||
|
||||
ag.addEventListener('change', scheduleNavigate);
|
||||
dg.addEventListener('change', scheduleNavigate);
|
||||
})();
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user