mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
Merge pull request #1968 from umap-project/filter-layer-3
Refactor the table editor including mass actions and filters
This commit is contained in:
commit
f40387d5fc
24 changed files with 815 additions and 297 deletions
|
@ -277,6 +277,7 @@ button.flat,
|
||||||
width: initial;
|
width: initial;
|
||||||
display: initial;
|
display: initial;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
|
color: var(--text-color);
|
||||||
}
|
}
|
||||||
button.flat:hover,
|
button.flat:hover,
|
||||||
[type="button"].flat:hover,
|
[type="button"].flat:hover,
|
||||||
|
|
11
umap/static/umap/css/contextmenu.css
Normal file
11
umap/static/umap/css/contextmenu.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.umap-contextmenu {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
padding: calc(var(--box-padding) / 2) var(--box-padding);
|
||||||
|
position: absolute;
|
||||||
|
z-index: var(--zindex-contextmenu);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--block-shadow);
|
||||||
|
}
|
||||||
|
.umap-contextmenu li + li {
|
||||||
|
margin-top: var(--text-margin);
|
||||||
|
}
|
|
@ -8,7 +8,6 @@
|
||||||
z-index: var(--zindex-panels);
|
z-index: var(--zindex-panels);
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
opacity: 0.98;
|
|
||||||
cursor: initial;
|
cursor: initial;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: 1px solid var(--color-lightGray);
|
border: 1px solid var(--color-lightGray);
|
||||||
|
@ -27,7 +26,7 @@
|
||||||
.panel.full.on {
|
.panel.full.on {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
right: calc(var(--panel-gutter) * 2 + var(--control-size));
|
right: calc(var(--panel-gutter) * 2 + var(--control-size));
|
||||||
left: var(--panel-gutter);
|
left: calc(var(--panel-gutter) * 2 + var(--control-size));
|
||||||
height: initial;
|
height: initial;
|
||||||
max-height: initial;
|
max-height: initial;
|
||||||
}
|
}
|
||||||
|
@ -78,6 +77,9 @@
|
||||||
right: calc(var(--panel-gutter) * 2 + var(--control-size));
|
right: calc(var(--panel-gutter) * 2 + var(--control-size));
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
.panel-left-on .panel.full {
|
||||||
|
left: calc(var(--panel-gutter) * 3 + var(--control-size) + var(--panel-width));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media all and (orientation:portrait) {
|
@media all and (orientation:portrait) {
|
||||||
.panel {
|
.panel {
|
||||||
|
|
69
umap/static/umap/css/tableeditor.css
Normal file
69
umap/static/umap/css/tableeditor.css
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
.umap-table-editor {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.umap-table-editor table {
|
||||||
|
white-space: nowrap;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
border-top: 1px solid black;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
.umap-table-editor thead {
|
||||||
|
text-align: center;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
background-color: var(--color-darkGray);
|
||||||
|
}
|
||||||
|
.umap-table-editor thead tr {
|
||||||
|
border-bottom: 3px solid var(--color-accent);
|
||||||
|
}
|
||||||
|
.umap-table-editor thead th {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
}
|
||||||
|
.umap-table-editor .tbody tr input {
|
||||||
|
margin: 0;
|
||||||
|
border-right: none;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.umap-table-editor td {
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
.umap-table-editor td:focus {
|
||||||
|
outline: 1px solid var(--color-accent);
|
||||||
|
}
|
||||||
|
.umap-table-editor th, .umap-table-editor td {
|
||||||
|
padding: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.umap-table-editor tr:nth-child(even) {
|
||||||
|
background-color: var(--color-mediumGray);
|
||||||
|
}
|
||||||
|
.umap-table-editor tr {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: 1px solid black;
|
||||||
|
}
|
||||||
|
.umap-table-editor .formbox,
|
||||||
|
.umap-table-editor input {
|
||||||
|
margin: 0;
|
||||||
|
min-height: initial;
|
||||||
|
}
|
||||||
|
.umap-table-editor textarea,
|
||||||
|
.umap-table-editor input[type=text] {
|
||||||
|
border-radius: initial;
|
||||||
|
width: initial;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
.umap-table-editor th button {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.umap-table-editor th button:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
|
@ -3,13 +3,13 @@
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
width: auto;
|
width: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-shadow: 0 1px 7px #999999;
|
box-shadow: var(--block-shadow);
|
||||||
display: none;
|
display: none;
|
||||||
background-color: rgba(40, 40, 40, 0.8);
|
background-color: rgba(40, 40, 40, 0.9);
|
||||||
color: #eeeeec;
|
color: #eeeeec;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
z-index: calc(var(--zindex-panels) + 1);
|
z-index: var(--zindex-tooltip);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,5 +188,9 @@
|
||||||
<path id="path1-675" d="m87.167 966.62-3.1666-2.9964-3.1666 2.9964-0.83338-0.78858 3.4333-3.2487c0.31298-0.29613 0.82041-0.29613 1.1334 0l3.4332 3.2487zm-6.3333 3.4812 3.1666 2.9964 3.1666-2.9964 0.83336 0.78859-3.4333 3.2487c-0.31297 0.29608-0.82041 0.29608-1.1334 0l-3.4333-3.2487z" clip-rule="evenodd" fill="#efefef" fill-rule="evenodd"/>
|
<path id="path1-675" d="m87.167 966.62-3.1666-2.9964-3.1666 2.9964-0.83338-0.78858 3.4333-3.2487c0.31298-0.29613 0.82041-0.29613 1.1334 0l3.4332 3.2487zm-6.3333 3.4812 3.1666 2.9964 3.1666-2.9964 0.83336 0.78859-3.4333 3.2487c-0.31297 0.29608-0.82041 0.29608-1.1334 0l-3.4333-3.2487z" clip-rule="evenodd" fill="#efefef" fill-rule="evenodd"/>
|
||||||
<path id="path5" d="m63.167 974.36-3.1666-2.9964-3.1666 2.9964-0.83336-0.78859 3.4333-3.2487c0.31297-0.29608 0.82041-0.29608 1.1334 0l3.4333 3.2487z" fill="#efefef"/>
|
<path id="path5" d="m63.167 974.36-3.1666-2.9964-3.1666 2.9964-0.83336-0.78859 3.4333-3.2487c0.31297-0.29608 0.82041-0.29608 1.1334 0l3.4333 3.2487z" fill="#efefef"/>
|
||||||
<path id="path1-675-2" d="m56 963.15 3.4332 3.2487c0.31298 0.29613 0.82041 0.29613 1.1334 0l3.4333-3.2487-0.83338-0.78858-3.1666 2.9964-3.1666-2.9964z" fill="#efefef"/>
|
<path id="path1-675-2" d="m56 963.15 3.4332 3.2487c0.31298 0.29613 0.82041 0.29613 1.1334 0l3.4333-3.2487-0.83338-0.78858-3.1666 2.9964-3.1666-2.9964z" fill="#efefef"/>
|
||||||
|
<path id="path1-3" d="m16.584 844.26c0.12973-0.17297 0.25946-0.38919 0.3027-0.64865h2.6811c0.25946 0 0.43243-0.17297 0.43243-0.43243s-0.17297-0.43243-0.43243-0.43243h-2.6811c-0.08649-0.38919-0.34595-0.77838-0.69189-1.0378-0.82162-0.60541-1.9459-0.38919-2.5081 0.38919-0.12973 0.17297-0.21622 0.38919-0.3027 0.64865h-8.9514c-0.25946 0-0.43243 0.17297-0.43243 0.43243s0.17297 0.43243 0.43243 0.43243h8.9514c0.08649 0.38919 0.34595 0.77838 0.69189 1.0378 0.77838 0.6054 1.9459 0.43243 2.5081-0.38919zm-1.9892-0.3027c-0.21622-0.12973-0.34595-0.34595-0.38919-0.60541-0.04324-0.25946 0-0.47567 0.17297-0.69189 0.3027-0.43243 0.90811-0.51892 1.2973-0.21622 0.21622 0.12973 0.34595 0.34595 0.38919 0.60541v0.12973c0 0.21621-0.04324 0.38919-0.17297 0.56216-0.3027 0.43243-0.90811 0.51892-1.2973 0.21622zm-4.4108 5.2324c0.12973-0.17297 0.25946-0.38919 0.3027-0.64865h9.0811c0.25946 0 0.43243-0.17297 0.43243-0.43243s-0.17297-0.43243-0.43243-0.43243h-9.0811c-0.08649-0.38919-0.34594-0.77838-0.69189-1.0378-0.82162-0.60541-1.9459-0.38919-2.5081 0.38919-0.12973 0.17297-0.21622 0.38919-0.3027 0.64865h-2.5514c-0.25946 0-0.43243 0.17297-0.43243 0.43243s0.17297 0.43243 0.43243 0.43243h2.5514c0.086486 0.38919 0.34595 0.77838 0.69189 1.0378 0.77838 0.56216 1.9027 0.38919 2.5081-0.38919zm-1.9892-0.34595c-0.21622-0.12973-0.34595-0.34594-0.38919-0.6054-0.043243-0.25946 0-0.47568 0.17297-0.69189 0.3027-0.43244 0.90811-0.51892 1.2973-0.21622 0.21622 0.12973 0.34595 0.34595 0.38919 0.60541v0.12973c0 0.21621-0.043243 0.38918-0.17297 0.56216-0.3027 0.43243-0.90811 0.51892-1.2973 0.21621zm6.7027 5.2324c0.12973-0.17298 0.25946-0.38919 0.3027-0.64865h4.3676c0.25946 0 0.43243-0.17298 0.43243-0.43244 0-0.25945-0.17297-0.43243-0.43243-0.43243h-4.3676c-0.08649-0.38919-0.34595-0.77838-0.69189-1.0378-0.38919-0.3027-0.86486-0.38918-1.3405-0.34594-0.47568 0.0865-0.90811 0.34594-1.1676 0.73513-0.12973 0.21622-0.21622 0.38919-0.3027 0.64865h-7.2649c-0.25946 0-0.43243 0.17298-0.43243 0.43243 0 0.25946 0.17297 0.43244 0.43243 0.43244h7.3081c0.08649 0.38919 0.34594 0.77838 0.69189 1.0378 0.77838 0.56216 1.9027 0.38918 2.4649-0.38919zm-1.9892-0.30271c-0.21622-0.17297-0.34595-0.38919-0.38919-0.64865v-0.0865-0.0865c0-0.17297 0.08649-0.3027 0.17297-0.43243 0.12973-0.21622 0.34595-0.34595 0.6054-0.38919 0.25946-0.0432 0.47568 0.0432 0.69189 0.17297 0.21622 0.12973 0.34595 0.34595 0.38919 0.60541v0.12973c0 0.21621-0.04324 0.38919-0.17297 0.56216-0.3027 0.38919-0.86486 0.47568-1.2973 0.17297z" fill="#4d4d4d"/>
|
||||||
|
<path id="path7" transform="translate(0 812.36)" d="m14.422 32.437c-0.40475-0.18739-0.73248-0.52689-0.90324-0.93569l-0.12909-0.30904-4.552-0.0013c-2.5036-6.92e-4 -4.6011-0.0324-4.6612-0.07045-0.065629-0.04161-0.098477-0.18071-0.08241-0.34898l0.026716-0.27979 9.2694-0.04258 0.1288-0.30835c0.37798-0.90485 1.434-1.3344 2.3039-0.93706 0.41731 0.19061 0.83509 0.6171 0.95225 0.9721l0.09065 0.27468h1.4741c1.419 0 1.4776 0.0066 1.5679 0.17524 0.06808 0.12721 0.06936 0.22886 0.0047 0.37084-0.08898 0.19528-0.09153 0.1956-1.5679 0.1956h-1.4787l-0.09216 0.27925c-0.11475 0.34769-0.60086 0.85722-0.96164 1.008-0.37806 0.15796-0.99803 0.13902-1.3901-0.04249zm1.2595-0.79648c0.57967-0.40602 0.55738-1.2431-0.04298-1.6141-0.69112-0.42713-1.6132 0.1923-1.4646 0.98396 0.08466 0.45129 0.46519 0.75878 0.939 0.75878 0.2155 0 0.46584-0.05663 0.56862-0.12862z" fill="#f2f2f2" stroke="#999" stroke-width=".25" style="paint-order:fill markers stroke"/>
|
||||||
|
<path id="path8" transform="translate(0 812.36)" d="m8.1903 37.424c-0.48722-0.17411-0.79231-0.44207-0.99467-0.87363l-0.1908-0.40689-1.3703-0.0034c-1.4983-0.0037-1.5911-0.03214-1.5406-0.47225l0.026557-0.23161 2.8423-0.0451 0.19827-0.3895c0.34789-0.68343 0.86349-1.0091 1.5923-1.0058 0.71771 0.0033 1.3415 0.43242 1.6291 1.1207l0.11636 0.27848h4.6574c4.5203 0 4.66 0.0047 4.7428 0.1594 0.1159 0.21656 0.10767 0.30093-0.04419 0.45278-0.11466 0.11466-0.66063 0.1295-4.7632 0.1295h-4.6337l-0.10498 0.2903c-0.15257 0.42194-0.62749 0.84447-1.1103 0.98785-0.49622 0.14736-0.6615 0.14879-1.0524 0.0091zm0.96896-0.84513c0.698-0.33122 0.698-1.3772 0-1.7084-0.35156-0.16682-0.49258-0.16849-0.82704-0.0098-0.33761 0.16021-0.5445 0.51333-0.5445 0.92936 0 0.36801 0.16007 0.60614 0.53566 0.79693 0.31576 0.16039 0.48419 0.15875 0.83587-0.0081z" fill="#f2f2f2" stroke="#999" stroke-width=".25" style="paint-order:fill markers stroke"/>
|
||||||
|
<path id="path9" transform="translate(0 812.36)" d="m12.773 42.244c-0.4045-0.18728-0.73246-0.52685-0.9029-0.93487l-0.12875-0.30821-7.6214-0.04285-0.026557-0.23161c-0.055311-0.48238-0.16774-0.4688 3.8893-0.46961l3.7189-7.33e-4 0.21287-0.43192c0.65163-1.3222 2.4726-1.2846 3.1257 0.06446l0.17859 0.36892h2.2972c2.2584 0 2.2988 3e-3 2.391 0.17524 0.06808 0.12721 0.06936 0.22886 0.0047 0.37084l-0.08912 0.1956h-4.6108l-0.15493 0.35024c-0.38713 0.87516-1.4373 1.2865-2.2839 0.89448zm1.0632-0.70036c0.37802-0.15795 0.5443-0.42579 0.54116-0.87174-4e-3 -0.57358-0.36338-0.90877-0.9743-0.90877-0.31204 0-0.40386 0.04292-0.64992 0.30383-0.25777 0.27331-0.28248 0.33986-0.2461 0.66262 0.04524 0.40136 0.22819 0.65122 0.5956 0.81339 0.31718 0.14001 0.39991 0.14008 0.73356 6.68e-4z" fill="#f2f2f2" stroke="#999" stroke-width=".25" style="paint-order:fill markers stroke"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 40 KiB |
|
@ -16,7 +16,7 @@
|
||||||
<path d="M 16.0401,2.3158 H 2.005 v 14.0351 h 14.0351 z" fill="#ffffff" id="path1259" />
|
<path d="M 16.0401,2.3158 H 2.005 v 14.0351 h 14.0351 z" fill="#ffffff" id="path1259" />
|
||||||
</mask>
|
</mask>
|
||||||
</defs>
|
</defs>
|
||||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="10.391897" inkscape:cx="112.73207" inkscape:cy="34.401804" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="1920" inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" inkscape:snap-grids="true" inkscape:snap-to-guides="true" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1">
|
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="12.134503" inkscape:cx="19.32506" inkscape:cy="29.296626" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" inkscape:window-width="1920" inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" showguides="true" inkscape:guide-bbox="true" inkscape:snap-grids="true" inkscape:snap-to-guides="true" inkscape:showpageshadow="2" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1">
|
||||||
<inkscape:grid type="xygrid" id="grid3004" empspacing="4" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="0" originy="0" spacingy="1" spacingx="1" units="px" />
|
<inkscape:grid type="xygrid" id="grid3004" empspacing="4" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="0" originy="0" spacingy="1" spacingx="1" units="px" />
|
||||||
<sodipodi:guide orientation="-1,0" position="24,168" id="guide3084" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
<sodipodi:guide orientation="-1,0" position="24,168" id="guide3084" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
||||||
<sodipodi:guide orientation="0,1" position="0,96" id="guide3086" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
<sodipodi:guide orientation="0,1" position="0,96" id="guide3086" inkscape:locked="false" inkscape:label="" inkscape:color="rgb(0,134,229)" />
|
||||||
|
@ -210,5 +210,9 @@
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="m 87.166638,966.62162 -3.166579,-2.99638 -3.166613,2.99638 -0.833375,-0.78858 3.4333,-3.24873 c 0.312979,-0.29613 0.82041,-0.29613 1.133389,0 l 3.43324,3.24873 z m -6.333275,3.48122 3.166626,2.99639 3.166649,-2.99639 0.833362,0.78859 -3.433322,3.24872 c -0.312968,0.29608 -0.820411,0.29608 -1.133378,0 l -3.4333,-3.24872 z" fill="#efefef" id="path1-675" style="stroke-width:0.999996" />
|
<path fill-rule="evenodd" clip-rule="evenodd" d="m 87.166638,966.62162 -3.166579,-2.99638 -3.166613,2.99638 -0.833375,-0.78858 3.4333,-3.24873 c 0.312979,-0.29613 0.82041,-0.29613 1.133389,0 l 3.43324,3.24873 z m -6.333275,3.48122 3.166626,2.99639 3.166649,-2.99639 0.833362,0.78859 -3.433322,3.24872 c -0.312968,0.29608 -0.820411,0.29608 -1.133378,0 l -3.4333,-3.24872 z" fill="#efefef" id="path1-675" style="stroke-width:0.999996" />
|
||||||
<path style="fill:#efefef;fill-opacity:1;stroke-width:0.999996" d="m 63.166637,974.36218 -3.166626,-2.99639 -3.166649,2.99639 -0.833362,-0.78859 3.433322,-3.24872 c 0.312968,-0.29608 0.820411,-0.29608 1.133378,0 l 3.4333,3.24872 z" id="path5" />
|
<path style="fill:#efefef;fill-opacity:1;stroke-width:0.999996" d="m 63.166637,974.36218 -3.166626,-2.99639 -3.166649,2.99639 -0.833362,-0.78859 3.433322,-3.24872 c 0.312968,-0.29608 0.820411,-0.29608 1.133378,0 l 3.4333,3.24872 z" id="path5" />
|
||||||
<path style="fill:#efefef;fill-opacity:1;stroke-width:0.999996" d="m 56.000071,963.15076 3.43324,3.24873 c 0.312979,0.29613 0.82041,0.29613 1.133389,0 l 3.4333,-3.24873 -0.833375,-0.78858 -3.166613,2.99638 -3.166579,-2.99638 z" id="path1-675-2" />
|
<path style="fill:#efefef;fill-opacity:1;stroke-width:0.999996" d="m 56.000071,963.15076 3.43324,3.24873 c 0.312979,0.29613 0.82041,0.29613 1.133389,0 l 3.4333,-3.24873 -0.833375,-0.78858 -3.166613,2.99638 -3.166579,-2.99638 z" id="path1-675-2" />
|
||||||
|
<path d="m 16.583784,844.26236 c 0.129729,-0.17297 0.259459,-0.38919 0.302702,-0.64865 h 2.681081 c 0.25946,0 0.432433,-0.17297 0.432433,-0.43243 0,-0.25946 -0.172973,-0.43243 -0.432433,-0.43243 h -2.681081 c -0.08649,-0.38919 -0.345946,-0.77838 -0.691891,-1.03784 -0.821622,-0.60541 -1.945946,-0.38919 -2.508109,0.38919 -0.129729,0.17297 -0.216216,0.38919 -0.302702,0.64865 h -8.9513515 c -0.2594595,0 -0.4324325,0.17297 -0.4324325,0.43243 0,0.25946 0.172973,0.43243 0.4324325,0.43243 h 8.9513515 c 0.08649,0.38919 0.345946,0.77838 0.691892,1.03784 0.778378,0.6054 1.945946,0.43243 2.508108,-0.38919 z m -1.989189,-0.3027 c -0.216217,-0.12973 -0.345946,-0.34595 -0.38919,-0.60541 -0.04324,-0.25946 0,-0.47567 0.172973,-0.69189 0.302703,-0.43243 0.908109,-0.51892 1.297298,-0.21622 0.216216,0.12973 0.345946,0.34595 0.389189,0.60541 0,0.0432 0,0.0865 0,0.12973 0,0.21621 -0.04324,0.38919 -0.172973,0.56216 -0.302703,0.43243 -0.908108,0.51892 -1.297297,0.21622 z m -4.410811,5.23243 c 0.129729,-0.17297 0.259459,-0.38919 0.302702,-0.64865 h 9.081081 c 0.25946,0 0.432433,-0.17297 0.432433,-0.43243 0,-0.25946 -0.172973,-0.43243 -0.432433,-0.43243 h -9.081081 c -0.08649,-0.38919 -0.345945,-0.77838 -0.6918914,-1.03784 -0.8216216,-0.60541 -1.9459459,-0.38919 -2.5081081,0.38919 -0.1297297,0.17297 -0.2162162,0.38919 -0.3027027,0.64865 h -2.5513513 c -0.2594595,0 -0.4324325,0.17297 -0.4324325,0.43243 0,0.25946 0.172973,0.43243 0.4324325,0.43243 h 2.5513513 c 0.086486,0.38919 0.3459459,0.77838 0.6918919,1.03784 0.7783784,0.56216 1.9027027,0.38919 2.5081083,-0.38919 z m -1.9891894,-0.34595 c -0.2162162,-0.12973 -0.3459459,-0.34594 -0.3891892,-0.6054 -0.043243,-0.25946 0,-0.47568 0.172973,-0.69189 0.3027027,-0.43244 0.9081081,-0.51892 1.2972973,-0.21622 0.2162162,0.12973 0.3459459,0.34595 0.3891892,0.60541 0,0.0432 0,0.0865 0,0.12973 0,0 0,0 0,0 0,0 0,0 0,0 0,0.21621 -0.043243,0.38918 -0.172973,0.56216 -0.3027027,0.43243 -0.9081081,0.51892 -1.2972973,0.21621 z m 6.7027024,5.23244 c 0.12973,-0.17298 0.25946,-0.38919 0.302703,-0.64865 h 4.367567 c 0.25946,0 0.432433,-0.17298 0.432433,-0.43244 0,-0.25945 -0.172973,-0.43243 -0.432433,-0.43243 h -4.367567 c -0.08649,-0.38919 -0.345946,-0.77838 -0.691892,-1.03784 -0.389189,-0.3027 -0.864865,-0.38918 -1.34054,-0.34594 -0.475676,0.0865 -0.908109,0.34594 -1.167568,0.73513 -0.12973,0.21622 -0.216216,0.38919 -0.302703,0.64865 h -7.2648645 c -0.2594595,0 -0.4324325,0.17298 -0.4324325,0.43243 0,0.25946 0.172973,0.43244 0.4324325,0.43244 h 7.3081085 c 0.08649,0.38919 0.345945,0.77838 0.691891,1.03784 0.778379,0.56216 1.902703,0.38918 2.464865,-0.38919 z m -1.989189,-0.30271 c -0.216216,-0.17297 -0.345946,-0.38919 -0.389189,-0.64865 0,-0.0432 0,-0.0865 0,-0.0865 0,-0.0433 0,-0.0865 0,-0.0865 0,-0.17297 0.08649,-0.3027 0.172973,-0.43243 0.12973,-0.21622 0.345946,-0.34595 0.605405,-0.38919 0.25946,-0.0432 0.475676,0.0432 0.691892,0.17297 0.216216,0.12973 0.345946,0.34595 0.389189,0.60541 0,0.0432 0,0.0865 0,0.12973 0,0 0,0 0,0 0,0 0,0 0,0 0,0.21621 -0.04324,0.38919 -0.172973,0.56216 -0.302702,0.38919 -0.864864,0.47568 -1.297297,0.17297 z" id="path1-3" style="fill:#4d4d4d;fill-opacity:1;stroke-width:1" />
|
||||||
|
<path style="fill:#f2f2f2;stroke:#999999;stroke-width:0.25;paint-order:fill markers stroke" d="m 14.421687,32.436774 c -0.404754,-0.187394 -0.732475,-0.526889 -0.903241,-0.93569 l -0.129092,-0.309036 -4.5520263,-0.0013 c -2.5036146,-6.92e-4 -4.601133,-0.0324 -4.661152,-0.07045 -0.065629,-0.04161 -0.098477,-0.18071 -0.08241,-0.348979 l 0.026716,-0.279792 4.6347227,-0.02129 4.6347226,-0.02129 0.128805,-0.308351 c 0.377978,-0.904848 1.434044,-1.334376 2.303918,-0.937059 0.417306,0.190606 0.835088,0.617095 0.952249,0.972096 l 0.09065,0.274685 h 1.474083 c 1.418969,0 1.477589,0.0066 1.567871,0.175244 0.06808,0.127208 0.06936,0.228859 0.0047,0.370843 -0.08898,0.195282 -0.09153,0.1956 -1.567871,0.1956 h -1.478749 l -0.09216,0.279253 c -0.114748,0.347688 -0.600864,0.857219 -0.961643,1.007963 -0.378064,0.157965 -0.998032,0.139015 -1.390062,-0.04249 z m 1.259483,-0.796476 c 0.579671,-0.406017 0.557382,-1.24308 -0.04298,-1.614124 -0.691117,-0.427134 -1.613162,0.192303 -1.464646,0.983962 0.08466,0.451286 0.465191,0.758779 0.939003,0.758779 0.215497,0 0.46584,-0.05663 0.568623,-0.128617 z" id="path7" transform="translate(0,812.36218)" />
|
||||||
|
<path style="fill:#f2f2f2;stroke:#999999;stroke-width:0.25;paint-order:fill markers stroke" d="m 8.1902877,37.423896 c -0.4872187,-0.174113 -0.7923051,-0.442073 -0.9946703,-0.873627 l -0.1907982,-0.406887 -1.3702708,-0.0034 c -1.4982701,-0.0037 -1.5910875,-0.03214 -1.5406237,-0.47225 l 0.026557,-0.23161 1.4211632,-0.02255 1.4211632,-0.02255 0.1982698,-0.389502 c 0.347892,-0.683434 0.8634941,-1.009111 1.592299,-1.005764 0.7177082,0.0033 1.3415151,0.432422 1.6290831,1.120669 l 0.116359,0.278485 h 4.65745 c 4.520341,0 4.659962,0.0047 4.742759,0.159402 0.1159,0.216562 0.107667,0.300926 -0.04419,0.452784 -0.114657,0.114657 -0.660626,0.129501 -4.763204,0.129501 h -4.633703 l -0.104975,0.290304 c -0.152574,0.421935 -0.6274874,0.844474 -1.1103021,0.987854 -0.4962156,0.147361 -0.6614998,0.148792 -1.0523644,0.0091 z m 0.9689602,-0.845129 c 0.6980003,-0.331223 0.6980003,-1.377155 0,-1.708378 -0.3515556,-0.166824 -0.4925823,-0.168492 -0.8270409,-0.0098 -0.3376099,0.160207 -0.5444962,0.513326 -0.5444962,0.929363 0,0.368008 0.1600657,0.606145 0.5356626,0.796928 0.3157622,0.16039 0.4841923,0.158752 0.8358745,-0.0081 z" id="path8" transform="translate(0,812.36218)" />
|
||||||
|
<path style="fill:#f2f2f2;stroke:#999999;stroke-width:0.25;paint-order:fill markers stroke" d="m 12.773494,42.243521 c -0.4045,-0.187277 -0.732456,-0.526846 -0.902897,-0.934868 l -0.128748,-0.308213 -3.8106837,-0.02143 -3.8106834,-0.02142 -0.026557,-0.23161 c -0.055311,-0.482381 -0.1677415,-0.468805 3.8892984,-0.469606 l 3.7189457,-7.33e-4 0.21287,-0.431917 c 0.651632,-1.322166 2.472575,-1.284611 3.125651,0.06446 l 0.178591,0.36892 h 2.297219 c 2.258426,0 2.298803,0.003 2.391007,0.175244 0.06808,0.127207 0.06936,0.228859 0.0047,0.370843 l -0.08912,0.1956 h -2.305381 -2.30538 l -0.15493,0.350241 c -0.387132,0.875164 -1.437261,1.286451 -2.283867,0.894485 z m 1.063202,-0.700358 c 0.378023,-0.157949 0.544298,-0.425794 0.541165,-0.871741 -0.004,-0.573585 -0.363385,-0.908772 -0.974299,-0.908772 -0.312041,0 -0.40386,0.04292 -0.649922,0.303826 -0.257768,0.273313 -0.282484,0.339859 -0.246104,0.662621 0.04524,0.401361 0.228194,0.651219 0.595596,0.813394 0.317179,0.140006 0.399909,0.140082 0.733564,6.68e-4 z" id="path9" transform="translate(0,812.36218)" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 72 KiB |
|
@ -107,6 +107,7 @@ export default class Browser {
|
||||||
this.map.eachBrowsableDataLayer((datalayer) => {
|
this.map.eachBrowsableDataLayer((datalayer) => {
|
||||||
datalayer.resetLayer(true)
|
datalayer.resetLayer(true)
|
||||||
this.updateDatalayer(datalayer)
|
this.updateDatalayer(datalayer)
|
||||||
|
if (this.map.fullPanel?.isOpen()) datalayer.tableEdit()
|
||||||
})
|
})
|
||||||
this.toggleBadge()
|
this.toggleBadge()
|
||||||
}
|
}
|
||||||
|
@ -149,7 +150,7 @@ export default class Browser {
|
||||||
DomEvent.disableClickPropagation(container)
|
DomEvent.disableClickPropagation(container)
|
||||||
|
|
||||||
DomUtil.createTitle(container, translate('Data browser'), 'icon-layers')
|
DomUtil.createTitle(container, translate('Data browser'), 'icon-layers')
|
||||||
const formContainer = DomUtil.createFieldset(container, L._('Filters'), {
|
this.formContainer = DomUtil.createFieldset(container, L._('Filters'), {
|
||||||
on: this.mode === 'filters',
|
on: this.mode === 'filters',
|
||||||
className: 'filters',
|
className: 'filters',
|
||||||
icon: 'icon-filters',
|
icon: 'icon-filters',
|
||||||
|
@ -169,7 +170,7 @@ export default class Browser {
|
||||||
callback: () => this.onFormChange(),
|
callback: () => this.onFormChange(),
|
||||||
})
|
})
|
||||||
let filtersBuilder
|
let filtersBuilder
|
||||||
formContainer.appendChild(builder.build())
|
this.formContainer.appendChild(builder.build())
|
||||||
DomEvent.on(builder.form, 'reset', () => {
|
DomEvent.on(builder.form, 'reset', () => {
|
||||||
window.setTimeout(builder.syncAll.bind(builder))
|
window.setTimeout(builder.syncAll.bind(builder))
|
||||||
})
|
})
|
||||||
|
@ -181,12 +182,11 @@ export default class Browser {
|
||||||
DomEvent.on(filtersBuilder.form, 'reset', () => {
|
DomEvent.on(filtersBuilder.form, 'reset', () => {
|
||||||
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
|
||||||
})
|
})
|
||||||
formContainer.appendChild(filtersBuilder.build())
|
this.formContainer.appendChild(filtersBuilder.build())
|
||||||
}
|
}
|
||||||
const reset = DomUtil.createButton('flat', formContainer, '', () => {
|
const reset = DomUtil.createButton('flat', this.formContainer, '', () =>
|
||||||
builder.form.reset()
|
this.resetFilters()
|
||||||
if (filtersBuilder) filtersBuilder.form.reset()
|
)
|
||||||
})
|
|
||||||
DomUtil.createIcon(reset, 'icon-restore')
|
DomUtil.createIcon(reset, 'icon-restore')
|
||||||
DomUtil.element({
|
DomUtil.element({
|
||||||
tagName: 'span',
|
tagName: 'span',
|
||||||
|
@ -202,6 +202,12 @@ export default class Browser {
|
||||||
this.update()
|
this.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetFilters() {
|
||||||
|
for (const form of this.formContainer?.querySelectorAll('form') || []) {
|
||||||
|
form.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static backButton(map) {
|
static backButton(map) {
|
||||||
const button = DomUtil.createButtonIcon(
|
const button = DomUtil.createButtonIcon(
|
||||||
DomUtil.create('li', '', undefined),
|
DomUtil.create('li', '', undefined),
|
||||||
|
|
|
@ -12,8 +12,8 @@ export default class Facets {
|
||||||
const properties = {}
|
const properties = {}
|
||||||
let selected
|
let selected
|
||||||
|
|
||||||
names.forEach((name) => {
|
for (const name of names) {
|
||||||
const type = defined[name].type
|
const type = defined.get(name).type
|
||||||
properties[name] = { type: type }
|
properties[name] = { type: type }
|
||||||
selected = this.selected[name] || {}
|
selected = this.selected[name] || {}
|
||||||
selected.type = type
|
selected.type = type
|
||||||
|
@ -22,13 +22,13 @@ export default class Facets {
|
||||||
selected.choices = selected.choices || []
|
selected.choices = selected.choices || []
|
||||||
}
|
}
|
||||||
this.selected[name] = selected
|
this.selected[name] = selected
|
||||||
})
|
}
|
||||||
|
|
||||||
this.map.eachBrowsableDataLayer((datalayer) => {
|
this.map.eachBrowsableDataLayer((datalayer) => {
|
||||||
datalayer.eachFeature((feature) => {
|
datalayer.eachFeature((feature) => {
|
||||||
names.forEach((name) => {
|
for (const name of names) {
|
||||||
let value = feature.properties[name]
|
let value = feature.properties[name]
|
||||||
const type = defined[name].type
|
const type = defined.get(name).type
|
||||||
const parser = this.getParser(type)
|
const parser = this.getParser(type)
|
||||||
value = parser(value)
|
value = parser(value)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
@ -56,7 +56,7 @@ export default class Facets {
|
||||||
properties[name].choices.push(value)
|
properties[name].choices.push(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
return properties
|
return properties
|
||||||
|
@ -73,7 +73,7 @@ export default class Facets {
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
const defined = this.getDefined()
|
const defined = this.getDefined()
|
||||||
const names = Object.keys(defined)
|
const names = [...defined.keys()]
|
||||||
const facetProperties = this.compute(names, defined)
|
const facetProperties = this.compute(names, defined)
|
||||||
|
|
||||||
const fields = names.map((name) => {
|
const fields = names.map((name) => {
|
||||||
|
@ -90,7 +90,7 @@ export default class Facets {
|
||||||
handler = 'FacetSearchDateTime'
|
handler = 'FacetSearchDateTime'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
const label = defined[name].label
|
const label = defined.get(name).label
|
||||||
return [
|
return [
|
||||||
`selected.${name}`,
|
`selected.${name}`,
|
||||||
{
|
{
|
||||||
|
@ -107,12 +107,14 @@ export default class Facets {
|
||||||
getDefined() {
|
getDefined() {
|
||||||
const defaultType = 'checkbox'
|
const defaultType = 'checkbox'
|
||||||
const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime']
|
const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime']
|
||||||
|
const defined = new Map()
|
||||||
|
if (!this.map.options.facetKey) return defined
|
||||||
return (this.map.options.facetKey || '').split(',').reduce((acc, curr) => {
|
return (this.map.options.facetKey || '').split(',').reduce((acc, curr) => {
|
||||||
let [name, label, type] = curr.split('|')
|
let [name, label, type] = curr.split('|')
|
||||||
type = allowedTypes.includes(type) ? type : defaultType
|
type = allowedTypes.includes(type) ? type : defaultType
|
||||||
acc[name] = { label: label || name, type: type }
|
acc.set(name, { label: label || name, type: type })
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, defined)
|
||||||
}
|
}
|
||||||
|
|
||||||
getParser(type) {
|
getParser(type) {
|
||||||
|
@ -127,4 +129,32 @@ export default class Facets {
|
||||||
return (v) => String(v || '')
|
return (v) => String(v || '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dumps(parsed) {
|
||||||
|
const dumped = []
|
||||||
|
for (const [property, { label, type }] of parsed) {
|
||||||
|
dumped.push([property, label, type].filter(Boolean).join('|'))
|
||||||
|
}
|
||||||
|
return dumped.join(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
has(property) {
|
||||||
|
return this.getDefined().has(property)
|
||||||
|
}
|
||||||
|
|
||||||
|
add(property, label, type) {
|
||||||
|
const defined = this.getDefined()
|
||||||
|
if (!defined.has(property)) {
|
||||||
|
defined.set(property, { label, type })
|
||||||
|
this.map.options.facetKey = this.dumps(defined)
|
||||||
|
this.map.isDirty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(property) {
|
||||||
|
const defined = this.getDefined()
|
||||||
|
defined.delete(property)
|
||||||
|
this.map.options.facetKey = this.dumps(defined)
|
||||||
|
this.map.isDirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import Slideshow from './slideshow.js'
|
||||||
import { SyncEngine } from './sync/engine.js'
|
import { SyncEngine } from './sync/engine.js'
|
||||||
import Dialog from './ui/dialog.js'
|
import Dialog from './ui/dialog.js'
|
||||||
import { EditPanel, FullPanel, Panel } from './ui/panel.js'
|
import { EditPanel, FullPanel, Panel } from './ui/panel.js'
|
||||||
|
import TableEditor from './tableeditor.js'
|
||||||
import Tooltip from './ui/tooltip.js'
|
import Tooltip from './ui/tooltip.js'
|
||||||
import URLs from './urls.js'
|
import URLs from './urls.js'
|
||||||
import * as Utils from './utils.js'
|
import * as Utils from './utils.js'
|
||||||
|
@ -55,6 +56,7 @@ window.U = {
|
||||||
Share,
|
Share,
|
||||||
Slideshow,
|
Slideshow,
|
||||||
SyncEngine,
|
SyncEngine,
|
||||||
|
TableEditor,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
URLs,
|
URLs,
|
||||||
Utils,
|
Utils,
|
||||||
|
|
329
umap/static/umap/js/modules/tableeditor.js
Normal file
329
umap/static/umap/js/modules/tableeditor.js
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
|
import { translate } from './i18n.js'
|
||||||
|
import ContextMenu from './ui/contextmenu.js'
|
||||||
|
import { WithTemplate, loadTemplate } from './utils.js'
|
||||||
|
|
||||||
|
const TEMPLATE = `
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr data-ref="header"></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody data-ref="body">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
`
|
||||||
|
|
||||||
|
export default class TableEditor extends WithTemplate {
|
||||||
|
constructor(datalayer) {
|
||||||
|
super()
|
||||||
|
this.datalayer = datalayer
|
||||||
|
this.map = this.datalayer.map
|
||||||
|
this.contextmenu = new ContextMenu({ className: 'dark' })
|
||||||
|
this.table = this.loadTemplate(TEMPLATE)
|
||||||
|
if (!this.datalayer.isRemoteLayer()) {
|
||||||
|
this.elements.body.addEventListener('dblclick', (event) => {
|
||||||
|
if (event.target.closest('[data-property]')) this.editCell(event.target)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.elements.body.addEventListener('click', (event) => this.setFocus(event.target))
|
||||||
|
this.elements.body.addEventListener('keydown', (event) => this.onKeyDown(event))
|
||||||
|
this.elements.header.addEventListener('click', (event) => {
|
||||||
|
const property = event.target.dataset.property
|
||||||
|
if (property) this.openHeaderMenu(property)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
openHeaderMenu(property) {
|
||||||
|
const actions = []
|
||||||
|
let filterItem
|
||||||
|
if (this.map.facets.has(property)) {
|
||||||
|
filterItem = {
|
||||||
|
label: translate('Remove filter for this column'),
|
||||||
|
action: () => {
|
||||||
|
this.map.facets.remove(property)
|
||||||
|
this.map.browser.open('filters')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filterItem = {
|
||||||
|
label: translate('Add filter for this column'),
|
||||||
|
action: () => {
|
||||||
|
this.map.facets.add(property)
|
||||||
|
this.map.browser.open('filters')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actions.push(filterItem)
|
||||||
|
if (!this.datalayer.isRemoteLayer()) {
|
||||||
|
actions.push({
|
||||||
|
label: translate('Rename this column'),
|
||||||
|
action: () => this.renameProperty(property),
|
||||||
|
})
|
||||||
|
actions.push({
|
||||||
|
label: translate('Delete this column'),
|
||||||
|
action: () => this.deleteProperty(property),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.contextmenu.open([event.clientX, event.clientY], actions)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderHeaders() {
|
||||||
|
this.elements.header.innerHTML = ''
|
||||||
|
const th = loadTemplate('<th><input type="checkbox" /></th>')
|
||||||
|
const checkbox = th.firstChild
|
||||||
|
this.elements.header.appendChild(th)
|
||||||
|
for (const property of this.properties) {
|
||||||
|
this.elements.header.appendChild(
|
||||||
|
loadTemplate(
|
||||||
|
`<th>${property}<button data-property="${property}" class="flat" aria-label="${translate('Advanced actions')}">…</button></th>`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checkbox.addEventListener('change', (event) => {
|
||||||
|
if (checkbox.checked) this.checkAll()
|
||||||
|
else this.checkAll(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderBody() {
|
||||||
|
const bounds = this.map.getBounds()
|
||||||
|
const inBbox = this.map.browser.options.inBbox
|
||||||
|
let html = ''
|
||||||
|
for (const feature of Object.values(this.datalayer._layers)) {
|
||||||
|
if (feature.isFiltered()) continue
|
||||||
|
if (inBbox && !feature.isOnScreen(bounds)) continue
|
||||||
|
const tds = this.properties.map(
|
||||||
|
(prop) =>
|
||||||
|
`<td tabindex="0" data-property="${prop}">${feature.properties[prop] || ''}</td>`
|
||||||
|
)
|
||||||
|
html += `<tr data-feature="${feature.id}"><th><input type="checkbox" /></th>${tds.join('')}</tr>`
|
||||||
|
}
|
||||||
|
this.elements.body.innerHTML = html
|
||||||
|
}
|
||||||
|
|
||||||
|
resetProperties() {
|
||||||
|
this.properties = this.datalayer._propertiesIndex
|
||||||
|
if (this.properties.length === 0) {
|
||||||
|
this.properties = ['name', 'description']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateName(name) {
|
||||||
|
if (name.includes('.')) {
|
||||||
|
U.Alert.error(translate('Name “{name}” should not contain a dot.', { name }))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this.properties.includes(name)) {
|
||||||
|
U.Alert.error(translate('This name already exists: “{name}”', { name }))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
renameProperty(property) {
|
||||||
|
this.map.dialog
|
||||||
|
.prompt(translate('Please enter the new name of this property'))
|
||||||
|
.then(({ prompt }) => {
|
||||||
|
if (!prompt || !this.validateName(prompt)) return
|
||||||
|
this.datalayer.eachLayer((feature) => {
|
||||||
|
feature.renameProperty(property, prompt)
|
||||||
|
})
|
||||||
|
this.datalayer.deindexProperty(property)
|
||||||
|
this.datalayer.indexProperty(prompt)
|
||||||
|
this.open()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProperty(property) {
|
||||||
|
this.map.dialog
|
||||||
|
.confirm(
|
||||||
|
translate('Are you sure you want to delete this property on all the features?')
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.datalayer.eachLayer((feature) => {
|
||||||
|
feature.deleteProperty(property)
|
||||||
|
})
|
||||||
|
this.datalayer.deindexProperty(property)
|
||||||
|
this.resetProperties()
|
||||||
|
this.open()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addProperty() {
|
||||||
|
this.map.dialog
|
||||||
|
.prompt(translate('Please enter the name of the property'))
|
||||||
|
.then(({ prompt }) => {
|
||||||
|
if (!prompt || !this.validateName(prompt)) return
|
||||||
|
this.datalayer.indexProperty(prompt)
|
||||||
|
this.open()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
const id = 'tableeditor:edit'
|
||||||
|
this.resetProperties()
|
||||||
|
this.renderHeaders()
|
||||||
|
this.elements.body.innerHTML = ''
|
||||||
|
this.renderBody()
|
||||||
|
|
||||||
|
const actions = []
|
||||||
|
if (!this.datalayer.isRemoteLayer()) {
|
||||||
|
const addButton = loadTemplate(`
|
||||||
|
<button class="flat" type="button" data-ref="add">
|
||||||
|
<i class="icon icon-16 icon-add"></i>${translate('Add a new property')}
|
||||||
|
</button>`)
|
||||||
|
addButton.addEventListener('click', () => this.addProperty())
|
||||||
|
actions.push(addButton)
|
||||||
|
|
||||||
|
const deleteButton = loadTemplate(`
|
||||||
|
<button class="flat" type="button" data-ref="delete">
|
||||||
|
<i class="icon icon-16 icon-delete"></i>${translate('Delete selected rows')}
|
||||||
|
</button>`)
|
||||||
|
deleteButton.addEventListener('click', () => this.deleteRows())
|
||||||
|
actions.push(deleteButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterButton = loadTemplate(`
|
||||||
|
<button class="flat" type="button" data-ref="filters">
|
||||||
|
<i class="icon icon-16 icon-filters"></i>${translate('Filter data')}
|
||||||
|
</button>`)
|
||||||
|
filterButton.addEventListener('click', () => this.map.browser.open('filters'))
|
||||||
|
actions.push(filterButton)
|
||||||
|
|
||||||
|
this.map.fullPanel.open({
|
||||||
|
content: this.table,
|
||||||
|
className: 'umap-table-editor',
|
||||||
|
actions: actions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
editCell(cell) {
|
||||||
|
if (this.datalayer.isRemoteLayer()) return
|
||||||
|
const property = cell.dataset.property
|
||||||
|
const field = `properties.${property}`
|
||||||
|
const tr = event.target.closest('tr')
|
||||||
|
const feature = this.datalayer.getFeatureById(tr.dataset.feature)
|
||||||
|
const handler = property === 'description' ? 'Textarea' : 'Input'
|
||||||
|
const builder = new U.FormBuilder(feature, [[field, { handler }]], {
|
||||||
|
id: `umap-feature-properties_${L.stamp(feature)}`,
|
||||||
|
})
|
||||||
|
cell.innerHTML = ''
|
||||||
|
cell.appendChild(builder.build())
|
||||||
|
const input = builder.helpers[field].input
|
||||||
|
input.focus()
|
||||||
|
input.addEventListener('blur', () => {
|
||||||
|
cell.innerHTML = feature.properties[property] || ''
|
||||||
|
cell.focus()
|
||||||
|
})
|
||||||
|
input.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
builder.restoreField(field)
|
||||||
|
cell.innerHTML = feature.properties[property] || ''
|
||||||
|
cell.focus()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event) {
|
||||||
|
// Only on data <td>, not inputs or anything else
|
||||||
|
if (!event.target.dataset.property) return
|
||||||
|
const key = event.key
|
||||||
|
const actions = {
|
||||||
|
Enter: () => this.editCurrent(),
|
||||||
|
ArrowRight: () => this.moveRight(),
|
||||||
|
ArrowLeft: () => this.moveLeft(),
|
||||||
|
ArrowUp: () => this.moveUp(),
|
||||||
|
ArrowDown: () => this.moveDown(),
|
||||||
|
}
|
||||||
|
if (key in actions) {
|
||||||
|
actions[key]()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editCurrent() {
|
||||||
|
const current = this.getFocus()
|
||||||
|
if (current) {
|
||||||
|
this.editCell(current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveRight() {
|
||||||
|
const cell = this.getFocus()
|
||||||
|
if (cell.nextSibling) cell.nextSibling.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
moveLeft() {
|
||||||
|
const cell = this.getFocus()
|
||||||
|
if (cell.previousSibling) cell.previousSibling.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
moveDown() {
|
||||||
|
const cell = this.getFocus()
|
||||||
|
const tr = cell.closest('tr')
|
||||||
|
const property = cell.dataset.property
|
||||||
|
const nextTr = tr.nextSibling
|
||||||
|
if (nextTr) {
|
||||||
|
nextTr.querySelector(`td[data-property="${property}"`).focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveUp() {
|
||||||
|
const cell = this.getFocus()
|
||||||
|
const tr = cell.closest('tr')
|
||||||
|
const property = cell.dataset.property
|
||||||
|
const previousTr = tr.previousSibling
|
||||||
|
if (previousTr) {
|
||||||
|
previousTr.querySelector(`td[data-property="${property}"`).focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkAll(status = true) {
|
||||||
|
for (const checkbox of this.elements.body.querySelectorAll(
|
||||||
|
'input[type=checkbox]'
|
||||||
|
)) {
|
||||||
|
checkbox.checked = status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedRows() {
|
||||||
|
return Array.from(
|
||||||
|
this.elements.body.querySelectorAll('input[type=checkbox]:checked')
|
||||||
|
).map((checkbox) => checkbox.parentNode.parentNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFocus() {
|
||||||
|
return this.elements.body.querySelector(':focus')
|
||||||
|
}
|
||||||
|
|
||||||
|
setFocus(cell) {
|
||||||
|
cell.focus({ focusVisible: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRows() {
|
||||||
|
const selectedRows = this.getSelectedRows()
|
||||||
|
if (!selectedRows.length) return
|
||||||
|
this.map.dialog
|
||||||
|
.confirm(
|
||||||
|
translate('Found {count} rows. Are you sure you want to delete all?', {
|
||||||
|
count: selectedRows.length,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.datalayer.hide()
|
||||||
|
for (const row of selectedRows) {
|
||||||
|
const id = row.dataset.feature
|
||||||
|
const feature = this.datalayer.getFeatureById(id)
|
||||||
|
feature.del()
|
||||||
|
}
|
||||||
|
this.datalayer.show()
|
||||||
|
this.datalayer.fire('datachanged')
|
||||||
|
this.renderBody()
|
||||||
|
if (this.map.browser.isOpen()) {
|
||||||
|
this.map.browser.resetFilters()
|
||||||
|
this.map.browser.open('filters')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
93
umap/static/umap/js/modules/ui/base.js
Normal file
93
umap/static/umap/js/modules/ui/base.js
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
export class Positioned {
|
||||||
|
openAt({ anchor, position }) {
|
||||||
|
if (anchor && position === 'top') {
|
||||||
|
this.anchorTop(anchor)
|
||||||
|
} else if (anchor && position === 'left') {
|
||||||
|
this.anchorLeft(anchor)
|
||||||
|
} else if (anchor && position === 'bottom') {
|
||||||
|
this.anchorBottom(anchor)
|
||||||
|
} else {
|
||||||
|
this.anchorAbsolute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorAbsolute() {
|
||||||
|
this.container.className = ''
|
||||||
|
const left =
|
||||||
|
this.parent.offsetLeft +
|
||||||
|
this.parent.clientWidth / 2 -
|
||||||
|
this.container.clientWidth / 2
|
||||||
|
const top = this.parent.offsetTop + 75
|
||||||
|
this.setPosition({ top: top, left: left })
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorTop(el) {
|
||||||
|
this.container.className = 'tooltip-top'
|
||||||
|
const coords = this.getPosition(el)
|
||||||
|
this.setPosition({
|
||||||
|
left: coords.left - 10,
|
||||||
|
bottom: this.getDocHeight() - coords.top + 11,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorBottom(el) {
|
||||||
|
this.container.className = 'tooltip-bottom'
|
||||||
|
const coords = this.getPosition(el)
|
||||||
|
this.setPosition({
|
||||||
|
left: coords.left,
|
||||||
|
top: coords.bottom + 11,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
anchorLeft(el) {
|
||||||
|
this.container.className = 'tooltip-left'
|
||||||
|
const coords = this.getPosition(el)
|
||||||
|
this.setPosition({
|
||||||
|
top: coords.top,
|
||||||
|
right: document.documentElement.offsetWidth - coords.left + 11,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosition(el) {
|
||||||
|
return el.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
setPosition(coords) {
|
||||||
|
if (coords.left) this.container.style.left = `${coords.left}px`
|
||||||
|
else this.container.style.left = 'initial'
|
||||||
|
if (coords.right) this.container.style.right = `${coords.right}px`
|
||||||
|
else this.container.style.right = 'initial'
|
||||||
|
if (coords.top) this.container.style.top = `${coords.top}px`
|
||||||
|
else this.container.style.top = 'initial'
|
||||||
|
if (coords.bottom) this.container.style.bottom = `${coords.bottom}px`
|
||||||
|
else this.container.style.bottom = 'initial'
|
||||||
|
}
|
||||||
|
|
||||||
|
computePosition([x, y]) {
|
||||||
|
let left
|
||||||
|
let top
|
||||||
|
if (x < window.innerWidth / 2) {
|
||||||
|
left = x
|
||||||
|
} else {
|
||||||
|
left = x - this.container.offsetWidth
|
||||||
|
}
|
||||||
|
if (y < window.innerHeight / 2) {
|
||||||
|
top = y
|
||||||
|
} else {
|
||||||
|
top = y - this.container.offsetHeight
|
||||||
|
}
|
||||||
|
this.setPosition({ left, top })
|
||||||
|
}
|
||||||
|
|
||||||
|
getDocHeight() {
|
||||||
|
const D = document
|
||||||
|
return Math.max(
|
||||||
|
D.body.scrollHeight,
|
||||||
|
D.documentElement.scrollHeight,
|
||||||
|
D.body.offsetHeight,
|
||||||
|
D.documentElement.offsetHeight,
|
||||||
|
D.body.clientHeight,
|
||||||
|
D.documentElement.clientHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
50
umap/static/umap/js/modules/ui/contextmenu.js
Normal file
50
umap/static/umap/js/modules/ui/contextmenu.js
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { loadTemplate } from '../utils.js'
|
||||||
|
import { Positioned } from './base.js'
|
||||||
|
|
||||||
|
export default class ContextMenu extends Positioned {
|
||||||
|
constructor(options = {}) {
|
||||||
|
super()
|
||||||
|
this.options = options
|
||||||
|
this.container = document.createElement('ul')
|
||||||
|
this.container.className = 'umap-contextmenu'
|
||||||
|
if (options.className) {
|
||||||
|
this.container.classList.add(options.className)
|
||||||
|
}
|
||||||
|
this.container.addEventListener('focusout', (event) => {
|
||||||
|
if (!this.container.contains(event.relatedTarget)) this.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
open([x, y], items) {
|
||||||
|
this.container.innerHTML = ''
|
||||||
|
for (const item of items) {
|
||||||
|
const li = loadTemplate(
|
||||||
|
`<li class="${item.className || ''}"><button tabindex="0" class="flat">${item.label}</button></li>`
|
||||||
|
)
|
||||||
|
li.addEventListener('click', () => {
|
||||||
|
this.close()
|
||||||
|
item.action()
|
||||||
|
})
|
||||||
|
this.container.appendChild(li)
|
||||||
|
}
|
||||||
|
document.body.appendChild(this.container)
|
||||||
|
this.computePosition([x, y])
|
||||||
|
this.container.querySelector('button').focus()
|
||||||
|
this.container.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.stopPropagation()
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
try {
|
||||||
|
this.container.remove()
|
||||||
|
} catch {
|
||||||
|
// Race condition in Chrome: the focusout close has "half" removed the node
|
||||||
|
// So it's still visible in the DOM, but we calling .remove on it (or parentNode.removeChild)
|
||||||
|
// will crash.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ export class Panel {
|
||||||
// This will be set once according to the panel configurated at load
|
// This will be set once according to the panel configurated at load
|
||||||
// or by using panels as popups
|
// or by using panels as popups
|
||||||
this.mode = null
|
this.mode = null
|
||||||
this.classname = 'left'
|
this.className = 'left'
|
||||||
DomEvent.disableClickPropagation(this.container)
|
DomEvent.disableClickPropagation(this.container)
|
||||||
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
|
DomEvent.on(this.container, 'contextmenu', DomEvent.stopPropagation) // Do not activate our custom context menu.
|
||||||
DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation)
|
DomEvent.on(this.container, 'wheel', DomEvent.stopPropagation)
|
||||||
|
@ -25,9 +25,10 @@ export class Panel {
|
||||||
}
|
}
|
||||||
|
|
||||||
open({ content, className, actions = [] } = {}) {
|
open({ content, className, actions = [] } = {}) {
|
||||||
this.container.className = `with-transition panel window ${this.classname} ${
|
this.container.className = `with-transition panel window ${this.className} ${
|
||||||
this.mode || ''
|
this.mode || ''
|
||||||
}`
|
}`
|
||||||
|
document.body.classList.add(`panel-${this.className.split(' ')[0]}-on`)
|
||||||
this.container.innerHTML = ''
|
this.container.innerHTML = ''
|
||||||
const actionsContainer = DomUtil.create('ul', 'buttons', this.container)
|
const actionsContainer = DomUtil.create('ul', 'buttons', this.container)
|
||||||
const body = DomUtil.create('div', 'body', this.container)
|
const body = DomUtil.create('div', 'body', this.container)
|
||||||
|
@ -69,6 +70,7 @@ export class Panel {
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
document.body.classList.remove(`panel-${this.className.split(' ')[0]}-on`)
|
||||||
if (DomUtil.hasClass(this.container, 'on')) {
|
if (DomUtil.hasClass(this.container, 'on')) {
|
||||||
DomUtil.removeClass(this.container, 'on')
|
DomUtil.removeClass(this.container, 'on')
|
||||||
this.map.invalidateSize({ pan: false })
|
this.map.invalidateSize({ pan: false })
|
||||||
|
@ -80,14 +82,14 @@ export class Panel {
|
||||||
export class EditPanel extends Panel {
|
export class EditPanel extends Panel {
|
||||||
constructor(map) {
|
constructor(map) {
|
||||||
super(map)
|
super(map)
|
||||||
this.classname = 'right dark'
|
this.className = 'right dark'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FullPanel extends Panel {
|
export class FullPanel extends Panel {
|
||||||
constructor(map) {
|
constructor(map) {
|
||||||
super(map)
|
super(map)
|
||||||
this.classname = 'full dark'
|
this.className = 'full dark'
|
||||||
this.mode = 'expanded'
|
this.mode = 'expanded'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import { translate } from '../i18n.js'
|
import { translate } from '../i18n.js'
|
||||||
|
import { Positioned } from './base.js'
|
||||||
|
|
||||||
export default class Tooltip {
|
export default class Tooltip extends Positioned {
|
||||||
constructor(parent) {
|
constructor(parent) {
|
||||||
|
super()
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
this.container = DomUtil.create('div', 'with-transition', this.parent)
|
this.container = DomUtil.create('div', 'with-transition', this.parent)
|
||||||
this.container.id = 'umap-tooltip-container'
|
this.container.id = 'umap-tooltip-container'
|
||||||
|
@ -13,16 +15,8 @@ export default class Tooltip {
|
||||||
}
|
}
|
||||||
|
|
||||||
open(opts) {
|
open(opts) {
|
||||||
function showIt() {
|
const showIt = () => {
|
||||||
if (opts.anchor && opts.position === 'top') {
|
this.openAt(opts)
|
||||||
this.anchorTop(opts.anchor)
|
|
||||||
} else if (opts.anchor && opts.position === 'left') {
|
|
||||||
this.anchorLeft(opts.anchor)
|
|
||||||
} else if (opts.anchor && opts.position === 'bottom') {
|
|
||||||
this.anchorBottom(opts.anchor)
|
|
||||||
} else {
|
|
||||||
this.anchorAbsolute()
|
|
||||||
}
|
|
||||||
L.DomUtil.addClass(this.parent, 'umap-tooltip')
|
L.DomUtil.addClass(this.parent, 'umap-tooltip')
|
||||||
this.container.innerHTML = U.Utils.escapeHTML(opts.content)
|
this.container.innerHTML = U.Utils.escapeHTML(opts.content)
|
||||||
}
|
}
|
||||||
|
@ -39,43 +33,6 @@ export default class Tooltip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
anchorAbsolute() {
|
|
||||||
this.container.className = ''
|
|
||||||
const left =
|
|
||||||
this.parent.offsetLeft +
|
|
||||||
this.parent.clientWidth / 2 -
|
|
||||||
this.container.clientWidth / 2
|
|
||||||
const top = this.parent.offsetTop + 75
|
|
||||||
this.setPosition({ top: top, left: left })
|
|
||||||
}
|
|
||||||
|
|
||||||
anchorTop(el) {
|
|
||||||
this.container.className = 'tooltip-top'
|
|
||||||
const coords = this.getPosition(el)
|
|
||||||
this.setPosition({
|
|
||||||
left: coords.left - 10,
|
|
||||||
bottom: this.getDocHeight() - coords.top + 11,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
anchorBottom(el) {
|
|
||||||
this.container.className = 'tooltip-bottom'
|
|
||||||
const coords = this.getPosition(el)
|
|
||||||
this.setPosition({
|
|
||||||
left: coords.left,
|
|
||||||
top: coords.bottom + 11,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
anchorLeft(el) {
|
|
||||||
this.container.className = 'tooltip-left'
|
|
||||||
const coords = this.getPosition(el)
|
|
||||||
this.setPosition({
|
|
||||||
top: coords.top,
|
|
||||||
right: document.documentElement.offsetWidth - coords.left + 11,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
close(id) {
|
close(id) {
|
||||||
// Clear timetout even if a new tooltip has been added
|
// Clear timetout even if a new tooltip has been added
|
||||||
// in the meantime. Eg. after a mouseout from the anchor.
|
// in the meantime. Eg. after a mouseout from the anchor.
|
||||||
|
@ -86,31 +43,4 @@ export default class Tooltip {
|
||||||
this.setPosition({})
|
this.setPosition({})
|
||||||
L.DomUtil.removeClass(this.parent, 'umap-tooltip')
|
L.DomUtil.removeClass(this.parent, 'umap-tooltip')
|
||||||
}
|
}
|
||||||
|
|
||||||
getPosition(el) {
|
|
||||||
return el.getBoundingClientRect()
|
|
||||||
}
|
|
||||||
|
|
||||||
setPosition(coords) {
|
|
||||||
if (coords.left) this.container.style.left = `${coords.left}px`
|
|
||||||
else this.container.style.left = 'initial'
|
|
||||||
if (coords.right) this.container.style.right = `${coords.right}px`
|
|
||||||
else this.container.style.right = 'initial'
|
|
||||||
if (coords.top) this.container.style.top = `${coords.top}px`
|
|
||||||
else this.container.style.top = 'initial'
|
|
||||||
if (coords.bottom) this.container.style.bottom = `${coords.bottom}px`
|
|
||||||
else this.container.style.bottom = 'initial'
|
|
||||||
}
|
|
||||||
|
|
||||||
getDocHeight() {
|
|
||||||
const D = document
|
|
||||||
return Math.max(
|
|
||||||
D.body.scrollHeight,
|
|
||||||
D.documentElement.scrollHeight,
|
|
||||||
D.body.offsetHeight,
|
|
||||||
D.documentElement.offsetHeight,
|
|
||||||
D.body.clientHeight,
|
|
||||||
D.documentElement.clientHeight
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,11 +378,18 @@ export function toggleBadge(element, value) {
|
||||||
else delete element.dataset.badge
|
else delete element.dataset.badge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadTemplate(html) {
|
||||||
|
const template = document.createElement('template')
|
||||||
|
template.innerHTML = html
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.join('')
|
||||||
|
return template.content.firstElementChild
|
||||||
|
}
|
||||||
|
|
||||||
export class WithTemplate {
|
export class WithTemplate {
|
||||||
loadTemplate(html) {
|
loadTemplate(html) {
|
||||||
const template = document.createElement('template')
|
this.element = loadTemplate(html)
|
||||||
template.innerHTML = html.split('\n').map((line) => line.trim()).join('')
|
|
||||||
this.element = template.content.firstElementChild
|
|
||||||
this.elements = {}
|
this.elements = {}
|
||||||
for (const element of this.element.querySelectorAll('[data-ref]')) {
|
for (const element of this.element.querySelectorAll('[data-ref]')) {
|
||||||
this.elements[element.dataset.ref] = element
|
this.elements[element.dataset.ref] = element
|
||||||
|
|
|
@ -1159,7 +1159,7 @@ U.FormBuilder = L.FormBuilder.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
finish: function () {
|
finish: (event) => {
|
||||||
this.map.editPanel.close()
|
event.helper?.input?.blur()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -412,7 +412,9 @@ U.Layer.Categorized = U.RelativeColorLayer.extend({
|
||||||
if (colorbrewer[colorScheme]?.[this._classes]) {
|
if (colorbrewer[colorScheme]?.[this._classes]) {
|
||||||
this.options.colors = colorbrewer[colorScheme][this._classes]
|
this.options.colors = colorbrewer[colorScheme][this._classes]
|
||||||
} else {
|
} else {
|
||||||
this.options.colors = colorbrewer?.Accent[this._classes] ? colorbrewer?.Accent[this._classes] : U.COLORS
|
this.options.colors = colorbrewer?.Accent[this._classes]
|
||||||
|
? colorbrewer?.Accent[this._classes]
|
||||||
|
: U.COLORS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1032,7 +1034,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
this._index.splice(this._index.indexOf(id), 1)
|
this._index.splice(this._index.indexOf(id), 1)
|
||||||
delete this._layers[id]
|
delete this._layers[id]
|
||||||
delete this.map.features_index[feature.getSlug()]
|
delete this.map.features_index[feature.getSlug()]
|
||||||
if (this.hasDataLoaded()) this.fire('datachanged')
|
if (this.hasDataLoaded() && this.isVisible()) this.fire('datachanged')
|
||||||
},
|
},
|
||||||
|
|
||||||
indexProperties: function (feature) {
|
indexProperties: function (feature) {
|
||||||
|
@ -1045,6 +1047,7 @@ U.DataLayer = L.Evented.extend({
|
||||||
if (name.indexOf('_') === 0) return
|
if (name.indexOf('_') === 0) return
|
||||||
if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return
|
if (L.Util.indexOf(this._propertiesIndex, name) !== -1) return
|
||||||
this._propertiesIndex.push(name)
|
this._propertiesIndex.push(name)
|
||||||
|
this._propertiesIndex.sort()
|
||||||
},
|
},
|
||||||
|
|
||||||
deindexProperty: function (name) {
|
deindexProperty: function (name) {
|
||||||
|
@ -1800,9 +1803,9 @@ U.DataLayer = L.Evented.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
tableEdit: function () {
|
tableEdit: function () {
|
||||||
if (this.isRemoteLayer() || !this.isVisible()) return
|
if (!this.isVisible()) return
|
||||||
const editor = new U.TableEditor(this)
|
const editor = new U.TableEditor(this)
|
||||||
editor.edit()
|
editor.open()
|
||||||
},
|
},
|
||||||
|
|
||||||
getFilterKeys: function () {
|
getFilterKeys: function () {
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
U.TableEditor = L.Class.extend({
|
|
||||||
initialize: function (datalayer) {
|
|
||||||
this.datalayer = datalayer
|
|
||||||
this.table = L.DomUtil.create('div', 'table')
|
|
||||||
this.header = L.DomUtil.create('div', 'thead', this.table)
|
|
||||||
this.body = L.DomUtil.create('div', 'tbody', this.table)
|
|
||||||
this.resetProperties()
|
|
||||||
},
|
|
||||||
|
|
||||||
renderHeaders: function () {
|
|
||||||
this.header.innerHTML = ''
|
|
||||||
for (let i = 0; i < this.properties.length; i++) {
|
|
||||||
this.renderHeader(this.properties[i])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
renderHeader: function (property) {
|
|
||||||
const container = L.DomUtil.create('div', 'tcell', this.header)
|
|
||||||
const title = L.DomUtil.add('span', '', container, property)
|
|
||||||
const del = L.DomUtil.create('i', 'umap-delete', container)
|
|
||||||
const rename = L.DomUtil.create('i', 'umap-edit', container)
|
|
||||||
del.title = L._('Delete this property on all the features')
|
|
||||||
rename.title = L._('Rename this property on all the features')
|
|
||||||
L.DomEvent.on(del, 'click', () => this.deleteProperty(property))
|
|
||||||
L.DomEvent.on(rename, 'click', () => this.renameProperty(property))
|
|
||||||
},
|
|
||||||
|
|
||||||
renderRow: function (feature) {
|
|
||||||
const builder = new U.FormBuilder(feature, this.field_properties, {
|
|
||||||
id: `umap-feature-properties_${L.stamp(feature)}`,
|
|
||||||
className: 'trow',
|
|
||||||
callback: feature.resetTooltip,
|
|
||||||
})
|
|
||||||
this.body.appendChild(builder.build())
|
|
||||||
},
|
|
||||||
|
|
||||||
compileProperties: function () {
|
|
||||||
this.resetProperties()
|
|
||||||
if (this.properties.length === 0) this.properties = ['name']
|
|
||||||
// description is a forced textarea, don't edit it in a text input, or you lose cariage returns
|
|
||||||
if (this.properties.indexOf('description') !== -1)
|
|
||||||
this.properties.splice(this.properties.indexOf('description'), 1)
|
|
||||||
this.properties.sort()
|
|
||||||
this.field_properties = []
|
|
||||||
for (let i = 0; i < this.properties.length; i++) {
|
|
||||||
this.field_properties.push([
|
|
||||||
`properties.${this.properties[i]}`,
|
|
||||||
{ wrapper: 'div', wrapperClass: 'tcell' },
|
|
||||||
])
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetProperties: function () {
|
|
||||||
this.properties = this.datalayer._propertiesIndex
|
|
||||||
},
|
|
||||||
|
|
||||||
validateName: (name) => {
|
|
||||||
if (name.indexOf('.') !== -1) {
|
|
||||||
U.Alert.error(L._('Invalide property name: {name}', { name: name }))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
|
|
||||||
renameProperty: function (property) {
|
|
||||||
this.datalayer.map.dialog
|
|
||||||
.prompt(L._('Please enter the new name of this property'))
|
|
||||||
.then(({ prompt }) => {
|
|
||||||
if (!prompt || !this.validateName(prompt)) return
|
|
||||||
this.datalayer.eachLayer((feature) => {
|
|
||||||
feature.renameProperty(property, prompt)
|
|
||||||
})
|
|
||||||
this.datalayer.deindexProperty(property)
|
|
||||||
this.datalayer.indexProperty(prompt)
|
|
||||||
this.edit()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteProperty: function (property) {
|
|
||||||
this.datalayer.map.dialog
|
|
||||||
.confirm(
|
|
||||||
L._('Are you sure you want to delete this property on all the features?')
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.datalayer.eachLayer((feature) => {
|
|
||||||
feature.deleteProperty(property)
|
|
||||||
})
|
|
||||||
this.datalayer.deindexProperty(property)
|
|
||||||
this.resetProperties()
|
|
||||||
this.edit()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
addProperty: function () {
|
|
||||||
this.datalayer.map.dialog
|
|
||||||
.prompt(L._('Please enter the name of the property'))
|
|
||||||
.then(({ prompt }) => {
|
|
||||||
if (!prompt || !this.validateName(prompt)) return
|
|
||||||
this.datalayer.indexProperty(prompt)
|
|
||||||
this.edit()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
edit: function () {
|
|
||||||
const id = 'tableeditor:edit'
|
|
||||||
this.compileProperties()
|
|
||||||
this.renderHeaders()
|
|
||||||
this.body.innerHTML = ''
|
|
||||||
this.datalayer.eachLayer(this.renderRow, this)
|
|
||||||
const addButton = L.DomUtil.createButton(
|
|
||||||
'flat',
|
|
||||||
undefined,
|
|
||||||
L._('Add a new property')
|
|
||||||
)
|
|
||||||
const iconElement = L.DomUtil.createIcon(addButton, 'icon-add')
|
|
||||||
addButton.insertBefore(iconElement, addButton.firstChild)
|
|
||||||
L.DomEvent.on(addButton, 'click', this.addProperty, this)
|
|
||||||
this.datalayer.map.fullPanel.open({
|
|
||||||
content: this.table,
|
|
||||||
className: 'umap-table-editor',
|
|
||||||
actions: [addButton],
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -908,64 +908,6 @@ a.umap-control-caption,
|
||||||
padding-left: 31px;
|
padding-left: 31px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ********************************* */
|
|
||||||
/* Table Editor */
|
|
||||||
/* ********************************* */
|
|
||||||
|
|
||||||
.umap-table-editor .table {
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
table-layout: fixed;
|
|
||||||
}
|
|
||||||
.umap-table-editor .tbody {
|
|
||||||
display: table-row-group;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead,
|
|
||||||
.umap-table-editor .trow {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.umap-table-editor .tcell {
|
|
||||||
display: table-cell;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead {
|
|
||||||
text-align: center;
|
|
||||||
height: 48px;
|
|
||||||
line-height: 48px;
|
|
||||||
background-color: #2c3133;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead .tcell {
|
|
||||||
border-left: 1px solid #0b0c0c;
|
|
||||||
}
|
|
||||||
.umap-table-editor .tbody .trow input {
|
|
||||||
margin: 0;
|
|
||||||
border-right: none;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.umap-table-editor .tbody .trow + .trow input {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead i {
|
|
||||||
display: none;
|
|
||||||
width: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 10px 0;
|
|
||||||
height: 24px;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead i:before {
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead .tcell:hover i {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead .tcell i:hover {
|
|
||||||
background-color: #33393b;
|
|
||||||
}
|
|
||||||
.umap-table-editor .thead .tcell:hover span {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ********************************* */
|
/* ********************************* */
|
||||||
/* Tilelayer switcher */
|
/* Tilelayer switcher */
|
||||||
|
|
|
@ -44,9 +44,13 @@
|
||||||
--zindex-toolbar: 480;
|
--zindex-toolbar: 480;
|
||||||
--zindex-autocomplete: 470;
|
--zindex-autocomplete: 470;
|
||||||
--zindex-dialog: 460;
|
--zindex-dialog: 460;
|
||||||
|
--zindex-contextmenu: 455;
|
||||||
--zindex-icon-active: 450;
|
--zindex-icon-active: 450;
|
||||||
|
--zindex-tooltip: 445;
|
||||||
--zindex-panels: 440;
|
--zindex-panels: 440;
|
||||||
--zindex-dragover: 410;
|
--zindex-dragover: 410;
|
||||||
|
|
||||||
|
--block-shadow: 0 1px 7px var(--color-mediumGray);
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
--background-color: var(--color-darkGray);
|
--background-color: var(--color-darkGray);
|
||||||
|
|
|
@ -29,9 +29,11 @@
|
||||||
<link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/map.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/map.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/slideshow.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/slideshow.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'umap/css/contextmenu.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/panel.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/panel.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/window.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/window.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/tooltip.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/dialog.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/css/importers.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/css/importers.css' %}" />
|
||||||
|
<link rel="stylesheet" href="{% static 'umap/css/tableeditor.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
|
<link rel="stylesheet" href="{% static 'umap/theme.css' %}" />
|
||||||
|
|
|
@ -51,6 +51,5 @@
|
||||||
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/js/umap.layer.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.layer.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/js/umap.tableeditor.js' %}" defer></script>
|
|
||||||
<script src="{% static 'umap/js/umap.js' %}" defer></script>
|
<script src="{% static 'umap/js/umap.js' %}" defer></script>
|
||||||
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
||||||
|
|
|
@ -2,8 +2,68 @@ import json
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from playwright.sync_api import expect
|
||||||
|
|
||||||
from umap.models import DataLayer
|
from umap.models import DataLayer
|
||||||
|
|
||||||
|
from ..base import DataLayerFactory
|
||||||
|
|
||||||
|
DATALAYER_DATA = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "even",
|
||||||
|
"name": "Point 2",
|
||||||
|
"mynumber": 10,
|
||||||
|
"myboolean": True,
|
||||||
|
"mydate": "2024/04/14 12:19:17",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [0.065918, 48.385442]},
|
||||||
|
"id": "poin2", # Must be exactly 5 chars long so the frontend will keep it
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "odd",
|
||||||
|
"name": "Point 1",
|
||||||
|
"mynumber": 12,
|
||||||
|
"myboolean": False,
|
||||||
|
"mydate": "2024/03/13 12:20:20",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [3.55957, 49.767074]},
|
||||||
|
"id": "poin1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "even",
|
||||||
|
"name": "Point 4",
|
||||||
|
"mynumber": 10,
|
||||||
|
"myboolean": "true",
|
||||||
|
"mydate": "2024/08/18 13:14:15",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [0.856934, 45.290347]},
|
||||||
|
"id": "poin4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"mytype": "odd",
|
||||||
|
"name": "Point 3",
|
||||||
|
"mynumber": 14,
|
||||||
|
"mydate": "2024-04-14T10:19:17.000Z",
|
||||||
|
},
|
||||||
|
"geometry": {"type": "Point", "coordinates": [4.372559, 47.945786]},
|
||||||
|
"id": "poin3",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"_umap_options": {
|
||||||
|
"name": "Calque 2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_table_editor(live_server, openmap, datalayer, page):
|
def test_table_editor(live_server, openmap, datalayer, page):
|
||||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||||
|
@ -12,10 +72,11 @@ def test_table_editor(live_server, openmap, datalayer, page):
|
||||||
page.get_by_text("Add a new property").click()
|
page.get_by_text("Add a new property").click()
|
||||||
page.locator("dialog").locator("input").fill("newprop")
|
page.locator("dialog").locator("input").fill("newprop")
|
||||||
page.locator("dialog").get_by_role("button", name="OK").click()
|
page.locator("dialog").get_by_role("button", name="OK").click()
|
||||||
|
page.locator("td").nth(2).dblclick()
|
||||||
page.locator('input[name="newprop"]').fill("newvalue")
|
page.locator('input[name="newprop"]').fill("newvalue")
|
||||||
page.once("dialog", lambda dialog: dialog.accept())
|
page.keyboard.press("Enter")
|
||||||
page.hover(".umap-table-editor .tcell")
|
page.locator("thead button[data-property=name]").click()
|
||||||
page.get_by_title("Delete this property on all").first.click()
|
page.get_by_role("button", name="Delete this column").click()
|
||||||
page.locator("dialog").get_by_role("button", name="OK").click()
|
page.locator("dialog").get_by_role("button", name="OK").click()
|
||||||
with page.expect_response(re.compile(r".*/datalayer/update/.*")):
|
with page.expect_response(re.compile(r".*/datalayer/update/.*")):
|
||||||
page.get_by_role("button", name="Save").click()
|
page.get_by_role("button", name="Save").click()
|
||||||
|
@ -23,3 +84,94 @@ def test_table_editor(live_server, openmap, datalayer, page):
|
||||||
data = json.loads(Path(saved.geojson.path).read_text())
|
data = json.loads(Path(saved.geojson.path).read_text())
|
||||||
assert data["features"][0]["properties"]["newprop"] == "newvalue"
|
assert data["features"][0]["properties"]["newprop"] == "newvalue"
|
||||||
assert "name" not in data["features"][0]["properties"]
|
assert "name" not in data["features"][0]["properties"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_add_existing_property_name(live_server, openmap, datalayer, page):
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
page.get_by_text("Add a new property").click()
|
||||||
|
page.locator("dialog").locator("input").fill("name")
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(page.get_by_role("dialog")).to_contain_text(
|
||||||
|
"This name already exists: “name”"
|
||||||
|
)
|
||||||
|
expect(page.locator("table th button[data-property=name]")).to_have_count(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cannot_add_property_with_a_dot(live_server, openmap, datalayer, page):
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
page.get_by_text("Add a new property").click()
|
||||||
|
page.locator("dialog").locator("input").fill("foo.bar")
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(page.get_by_role("dialog")).to_contain_text(
|
||||||
|
"Name “foo.bar” should not contain a dot."
|
||||||
|
)
|
||||||
|
expect(page.locator("table th button[data-property=name]")).to_have_count(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rename_property(live_server, openmap, datalayer, page):
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
expect(page.locator("table th button[data-property=name]")).to_have_count(1)
|
||||||
|
page.locator("thead button[data-property=name]").click()
|
||||||
|
page.get_by_text("Rename this column").click()
|
||||||
|
page.locator("dialog").locator("input").fill("newname")
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(page.locator("table th button[data-property=newname]")).to_have_count(1)
|
||||||
|
expect(page.locator("table th button[data-property=name]")).to_have_count(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_selected_rows(live_server, openmap, page):
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
expect(page.locator("tbody tr")).to_have_count(4)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
|
||||||
|
page.locator("tr[data-feature=poin2]").get_by_role("checkbox").check()
|
||||||
|
page.get_by_role("button", name="Delete selected rows").click()
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(page.locator("tbody tr")).to_have_count(3)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(3)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_all_rows(live_server, openmap, page):
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA)
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
expect(page.locator("tbody tr")).to_have_count(4)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
|
||||||
|
page.locator("thead").get_by_role("checkbox").check()
|
||||||
|
page.get_by_role("button", name="Delete selected rows").click()
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(page.locator("tbody tr")).to_have_count(0)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_and_delete_rows(live_server, openmap, page):
|
||||||
|
DataLayerFactory(map=openmap, data=DATALAYER_DATA)
|
||||||
|
panel = page.locator(".panel.left.on")
|
||||||
|
table = page.locator(".panel.full table")
|
||||||
|
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit#6/48.093/1.890")
|
||||||
|
page.get_by_role("link", name="Manage layers").click()
|
||||||
|
page.locator(".panel").get_by_title("Edit properties in a table").click()
|
||||||
|
expect(table.locator("tbody tr")).to_have_count(4)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(4)
|
||||||
|
table.locator("thead button[data-property=mytype]").click()
|
||||||
|
page.get_by_role("button", name="Add filter for this column").click()
|
||||||
|
expect(panel).to_be_visible()
|
||||||
|
panel.get_by_label("even").check()
|
||||||
|
table.locator("thead").get_by_role("checkbox").check()
|
||||||
|
page.get_by_role("button", name="Delete selected rows").click()
|
||||||
|
page.get_by_role("button", name="OK").click()
|
||||||
|
expect(table.locator("tbody tr")).to_have_count(2)
|
||||||
|
expect(page.locator(".leaflet-marker-icon")).to_have_count(2)
|
||||||
|
expect(table.get_by_text("Point 1")).to_be_visible()
|
||||||
|
expect(table.get_by_text("Point 3")).to_be_visible()
|
||||||
|
expect(table.get_by_text("Point 2")).to_be_hidden()
|
||||||
|
expect(table.get_by_text("Point 4")).to_be_hidden()
|
||||||
|
|
Loading…
Reference in a new issue