wip: undo redo
Co-authored-by: Alexis Métaireau <alexis@notmyidea.org>
|
@ -14,7 +14,8 @@
|
|||
background-color: inherit;
|
||||
}
|
||||
.leaflet-container .edit-save,
|
||||
.leaflet-container .edit-cancel,
|
||||
.leaflet-container .edit-undo,
|
||||
.leaflet-container .edit-redo,
|
||||
.leaflet-container .edit-disable,
|
||||
.leaflet-container .connected-peers
|
||||
{
|
||||
|
@ -39,7 +40,8 @@
|
|||
color: var(--color-darkGray);
|
||||
}
|
||||
|
||||
.leaflet-container .edit-cancel:hover,
|
||||
.leaflet-container .edit-undo:hover,
|
||||
.leaflet-container .edit-redo:hover,
|
||||
.leaflet-container .edit-disable:hover {
|
||||
border: 0.5px solid rgba(153, 153, 153, 0.80);
|
||||
text-decoration: none;
|
||||
|
@ -76,14 +78,16 @@
|
|||
background: rgba(66, 236, 230, 0.10);
|
||||
}
|
||||
.leaflet-container .edit-save,
|
||||
.leaflet-container .edit-cancel,
|
||||
.leaflet-container .edit-undo,
|
||||
.leaflet-container .edit-redo,
|
||||
.leaflet-container .edit-disable,
|
||||
.umap-edit-enabled .edit-enable {
|
||||
display: none;
|
||||
}
|
||||
.umap-edit-enabled .edit-save,
|
||||
.umap-edit-enabled .edit-disable,
|
||||
.umap-edit-enabled.umap-is-dirty .edit-cancel {
|
||||
.umap-edit-enabled.umap-is-dirty .edit-undo,
|
||||
.umap-edit-enabled.umap-is-dirty .edit-redo {
|
||||
display: inline-block;
|
||||
}
|
||||
.umap-is-dirty .edit-disable {
|
||||
|
|
|
@ -167,11 +167,15 @@ html[dir="rtl"] .icon {
|
|||
.icon-profile {
|
||||
background-position: 0 calc(var(--tile) * 4);
|
||||
}
|
||||
.icon-redo {
|
||||
background-position: calc(var(--tile) * 3) calc(var(--tile) * 7);
|
||||
}
|
||||
.icon-resize {
|
||||
background-position: calc(var(--tile) * 3) calc(var(--tile) * 6);
|
||||
}
|
||||
.icon-undo,
|
||||
.icon-restore {
|
||||
background-position: calc(var(--tile) * 5) calc(var(--tile) * 3);
|
||||
background-position: calc(var(--tile) * 2) calc(var(--tile) * 7);
|
||||
}
|
||||
.expanded .icon-resize {
|
||||
background-position: calc(var(--tile) * 2) calc(var(--tile) * 6);
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
<clipPath id="clip0_3071_861">
|
||||
<rect id="rect3" width="18" height="20" fill="#fff"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip0_241_10857-3">
|
||||
<rect id="rect586-6" width="18.05" height="19.01" fill="#fff"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<metadata id="metadata7">
|
||||
<rdf:RDF>
|
||||
|
@ -67,9 +70,12 @@
|
|||
</g>
|
||||
</g>
|
||||
<path id="path4873" d="m108.15 816.36v3.8267h3.8544v-3.8267zm0.51755 4.3517-1.2459 2.3132 1.1669 0.61848 1.244-2.3151zm-1.8689 3.4717-0.27666 0.51571h-2.426v2.2441l-4.0916 4.064 1.3626 1.3528 3.862-3.8342h2.7214v-3.6959l0.015-0.028-0.015-8e-3v-0.0953h-0.17879l-0.97303-0.51571z" color="#000000" color-rendering="auto" fill="#f2f2f2" fill-rule="evenodd" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stroke="#999" stroke-width=".25" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<g id="g4244" transform="matrix(.51357 -.54309 .54309 .51357 -518.02 506.23)">
|
||||
<g id="g4244" transform="matrix(.51357 -.54309 .54309 .51357 -590.02 601.73)">
|
||||
<path id="path4240" transform="matrix(.91922 .97205 -.97205 .91922 152.14 647.93)" d="m220.49 133.52c-0.33017 0.01-0.66239 0.0456-0.99414 0.10742-2.2249 0.41425-4.0267 1.9575-4.832 4.0098l-0.87696-1.8164a0.50005 0.50005 0 0 0-0.83398-0.11328 0.50005 0.50005 0 0 0-0.0664 0.54883l1.459 3.0195a0.50005 0.50005 0 0 0 0.60742 0.25586l2.8438-0.94532a0.50028 0.50028 0 1 0-0.31641-0.94922l-2.002 0.66797c0.61312-1.8902 2.2073-3.3241 4.2012-3.6953 2.2474-0.41845 4.5146 0.59912 5.6953 2.5566 1.1807 1.9575 1.022 4.4377-0.39648 6.2305-1.4185 1.7928-3.7961 2.5154-5.9727 1.8164a0.50005 0.50005 0 1 0-0.30469 0.95117c2.5704 0.82539 5.3874-0.0294 7.0625-2.1465 0.4188-0.52924 0.74532-1.1114 0.97657-1.7207 0.69373-1.828 0.53599-3.9147-0.50977-5.6484-1.22-2.0227-3.4291-3.1977-5.7402-3.1289z" color="#000000" color-rendering="auto" fill="#f2f2f2" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stroke="#999" stroke-linecap="round" stroke-linejoin="round" stroke-width=".25" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
<g id="g4244-6" transform="matrix(-.51357 -.54309 -.54309 .51357 734.02 601.73)">
|
||||
<path id="path4240-7" transform="matrix(.91922 .97205 -.97205 .91922 152.14 647.93)" d="m220.49 133.52c-0.33017 0.01-0.66239 0.0456-0.99414 0.10742-2.2249 0.41425-4.0267 1.9575-4.832 4.0098l-0.87696-1.8164a0.50005 0.50005 0 0 0-0.83398-0.11328 0.50005 0.50005 0 0 0-0.0664 0.54883l1.459 3.0195a0.50005 0.50005 0 0 0 0.60742 0.25586l2.8438-0.94532a0.50028 0.50028 0 1 0-0.31641-0.94922l-2.002 0.66797c0.61312-1.8902 2.2073-3.3241 4.2012-3.6953 2.2474-0.41845 4.5146 0.59912 5.6953 2.5566 1.1807 1.9575 1.022 4.4377-0.39648 6.2305-1.4185 1.7928-3.7961 2.5154-5.9727 1.8164a0.50005 0.50005 0 1 0-0.30469 0.95117c2.5704 0.82539 5.3874-0.0294 7.0625-2.1465 0.4188-0.52924 0.74532-1.1114 0.97657-1.7207 0.69373-1.828 0.53599-3.9147-0.50977-5.6484-1.22-2.0227-3.4291-3.1977-5.7402-3.1289z" color="#000000" color-rendering="auto" fill="#f2f2f2" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stroke="#999" stroke-linecap="round" stroke-linejoin="round" stroke-width=".25" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
</g>
|
||||
<g id="delete-vertex" transform="translate(-90,-64)">
|
||||
<path id="path4349-2-2-9-3-8-3-5" transform="translate(0 852.36)" d="m227.55 53.105-6.0547 3.0273h-3.4414v3.5176l-2.9512 5.9023 1.7891 0.89453 3.1562-6.3145h2.0059v-2.043l6.3906-3.1953z" color="#000000" color-rendering="auto" fill="#f2f2f2" image-rendering="auto" shape-rendering="auto" solid-color="#000000" stroke="#999" stroke-width=".25" style="isolation:auto;mix-blend-mode:normal;text-decoration-color:#000000;text-decoration-line:none;text-decoration-style:solid;text-indent:0;text-transform:none;white-space:normal"/>
|
||||
<path id="path4353-1-6-1-3-5-1-9" d="m217 907.36 6 6" fill="none" stroke="#fff" stroke-width="2.482"/>
|
||||
|
@ -143,6 +149,10 @@
|
|||
<path id="path580" d="m1.07 4.41h9.42c0.9234-0.0066 1.8391 0.16957 2.6941 0.5184 0.8551 0.34883 1.6327 0.8634 2.288 1.5141s1.1754 1.4246 1.5303 2.2771c0.3549 0.85256 0.5376 1.767 0.5376 2.6904 0.0067 0.9277-0.1712 1.8474-0.5231 2.7058-0.3519 0.8583-0.871 1.6382-1.527 2.2941-0.656 0.656-1.4358 1.1751-2.2941 1.527-0.8584 0.352-1.7781 0.5298-2.7058 0.5231h-9.42"/>
|
||||
<path id="path582" d="m4.75 8.45-4.04-4.05 4.04-4.05"/>
|
||||
</g>
|
||||
<g id="undo-5" transform="matrix(-.71301 0 0 .66261 90.012 938.13)" clip-path="url(#clip0_241_10857-3)" fill="none" stroke="#f2f2f2" stroke-miterlimit="10" stroke-width="1.4549">
|
||||
<path id="path580-3" d="m1.07 4.41h9.42c0.9234-0.0066 1.8391 0.16957 2.6941 0.5184 0.8551 0.34883 1.6327 0.8634 2.288 1.5141s1.1754 1.4246 1.5303 2.2771c0.3549 0.85256 0.5376 1.767 0.5376 2.6904 0.0067 0.9277-0.1712 1.8474-0.5231 2.7058-0.3519 0.8583-0.871 1.6382-1.527 2.2941-0.656 0.656-1.4358 1.1751-2.2941 1.527-0.8584 0.352-1.7781 0.5298-2.7058 0.5231h-9.42"/>
|
||||
<path id="path582-5" d="m4.75 8.45-4.04-4.05 4.04-4.05"/>
|
||||
</g>
|
||||
<g id="g1" transform="translate(144 -24)" fill="none" stroke="#999">
|
||||
<path id="path438" d="m9 849.94v4.05c0 0.20708 0.1679 0.375 0.375 0.375h5.25c0.20708 0 0.375-0.16792 0.375-0.375v-4.05c0-0.20708-0.16792-0.375-0.375-0.375h-5.25c-0.2071 0-0.375 0.16792-0.375 0.375z"/>
|
||||
<path id="save" d="m15.213 842.36h-8.8376c-0.2071 0-0.375 0.1679-0.375 0.37499v11.25c0 0.20708 0.1679 0.375 0.375 0.375h11.25c0.20708 0 0.375-0.16792 0.375-0.375v-8.6766c0-0.0953-0.0363-0.18697-0.1014-0.25648l-2.4124-2.5733c-0.07095-0.0756-0.16995-0.11853-0.2736-0.11853z"/>
|
||||
|
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
|
@ -21,8 +21,11 @@
|
|||
<clipPath id="clip0_3071_861">
|
||||
<rect width="18" height="20" fill="#ffffff" id="rect3" x="0" y="0" />
|
||||
</clipPath>
|
||||
<clipPath id="clip0_241_10857-3">
|
||||
<rect width="18.049999" height="19.01" fill="#ffffff" id="rect586-6" x="0" y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="22.311152" inkscape:cx="196.69536" inkscape:cy="36.932203" 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="4.4320814" inkscape:cx="116.08541" inkscape:cy="109.65503" 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 id="grid1" units="px" originx="0" originy="0" spacingx="24" spacingy="24" empcolor="#203fff" empopacity="0.85490196" color="#3f3fff" opacity="0.1254902" empspacing="1" enabled="true" visible="true" />
|
||||
</sodipodi:namedview>
|
||||
|
@ -78,9 +81,12 @@
|
|||
</g>
|
||||
</g>
|
||||
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 108.14555,816.36218 v 3.8267 h 3.85445 v -3.8267 z m 0.51755,4.35174 -1.24591,2.31321 1.16687,0.61848 1.24404,-2.31507 z m -1.86888,3.47168 -0.27666,0.51571 h -2.42597 v 2.24408 l -4.09159,4.06399 1.36261,1.3528 3.86198,-3.83417 h 2.72145 v -3.6959 l 0.015,-0.028 -0.015,-0.008 v -0.0953 h -0.17879 l -0.97303,-0.51571 z" id="path4873" inkscape:connector-curvature="0" />
|
||||
<g id="g4244" transform="matrix(0.51357238,-0.54309229,0.54309229,0.51357238,-518.0199,506.22551)">
|
||||
<g id="g4244" transform="matrix(0.51357238,-0.54309229,0.54309229,0.51357238,-590.0195,601.72586)">
|
||||
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:#999999;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 220.49219,133.52344 c -0.33017,0.01 -0.66239,0.0456 -0.99414,0.10742 -2.22487,0.41425 -4.02666,1.95747 -4.83203,4.00976 l -0.87696,-1.8164 a 0.50004998,0.50004998 0 0 0 -0.83398,-0.11328 0.50004998,0.50004998 0 0 0 -0.0664,0.54883 l 1.45899,3.01953 a 0.50004998,0.50004998 0 0 0 0.60742,0.25586 l 2.84375,-0.94532 a 0.50028339,0.50028339 0 1 0 -0.31641,-0.94922 l -2.00195,0.66797 c 0.61312,-1.89015 2.20733,-3.32407 4.20117,-3.69531 2.24744,-0.41845 4.51458,0.59912 5.69531,2.55664 1.18073,1.95754 1.02202,4.43774 -0.39648,6.23047 -1.41851,1.79275 -3.79606,2.51535 -5.97266,1.81641 a 0.50004998,0.50004998 0 1 0 -0.30469,0.95117 c 2.57038,0.82539 5.38736,-0.0294 7.0625,-2.14649 0.4188,-0.52924 0.74532,-1.11137 0.97657,-1.7207 0.69373,-1.828 0.53599,-3.91467 -0.50977,-5.64844 -1.22005,-2.02271 -3.42908,-3.19767 -5.74023,-3.1289 z" transform="matrix(0.91921787,0.9720541,-0.9720541,0.91921787,152.1356,647.93271)" id="path4240" inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g id="g4244-6" transform="matrix(-0.51357238,-0.54309229,-0.54309229,0.51357238,734.0195,601.72586)">
|
||||
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:#999999;stroke-width:0.25;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 220.49219,133.52344 c -0.33017,0.01 -0.66239,0.0456 -0.99414,0.10742 -2.22487,0.41425 -4.02666,1.95747 -4.83203,4.00976 l -0.87696,-1.8164 a 0.50004998,0.50004998 0 0 0 -0.83398,-0.11328 0.50004998,0.50004998 0 0 0 -0.0664,0.54883 l 1.45899,3.01953 a 0.50004998,0.50004998 0 0 0 0.60742,0.25586 l 2.84375,-0.94532 a 0.50028339,0.50028339 0 1 0 -0.31641,-0.94922 l -2.00195,0.66797 c 0.61312,-1.89015 2.20733,-3.32407 4.20117,-3.69531 2.24744,-0.41845 4.51458,0.59912 5.69531,2.55664 1.18073,1.95754 1.02202,4.43774 -0.39648,6.23047 -1.41851,1.79275 -3.79606,2.51535 -5.97266,1.81641 a 0.50004998,0.50004998 0 1 0 -0.30469,0.95117 c 2.57038,0.82539 5.38736,-0.0294 7.0625,-2.14649 0.4188,-0.52924 0.74532,-1.11137 0.97657,-1.7207 0.69373,-1.828 0.53599,-3.91467 -0.50977,-5.64844 -1.22005,-2.02271 -3.42908,-3.19767 -5.74023,-3.1289 z" transform="matrix(0.91921787,0.9720541,-0.9720541,0.91921787,152.1356,647.93271)" id="path4240-7" inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g id="delete-vertex" transform="translate(-90,-64)">
|
||||
<path style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:nonzero;stroke:#999999;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" d="m 227.55273,53.105469 -6.05468,3.027343 h -3.44141 v 3.517579 l -2.95117,5.902343 1.78906,0.894532 3.15625,-6.314454 h 2.00586 v -2.042968 l 6.39063,-3.195313 z" transform="translate(0,852.36218)" id="path4349-2-2-9-3-8-3-5" inkscape:connector-curvature="0" />
|
||||
<path sodipodi:nodetypes="cc" style="fill:none;stroke:#ffffff;stroke-width:2.482;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 217,907.36218 6,6" id="path4353-1-6-1-3-5-1-9" inkscape:connector-curvature="0" />
|
||||
|
@ -154,6 +160,10 @@
|
|||
<path d="m 1.07001,4.41003 h 9.41999 c 0.9234,-0.0066 1.8391,0.16957 2.6941,0.5184 0.8551,0.34883 1.6327,0.8634 2.288,1.51407 0.6553,0.65067 1.1754,1.42458 1.5303,2.27713 0.3549,0.85256 0.5376,1.76697 0.5376,2.69037 0.0067,0.9277 -0.1712,1.8474 -0.5231,2.7058 -0.3519,0.8583 -0.871,1.6382 -1.527,2.2941 -0.656,0.656 -1.4358,1.1751 -2.2941,1.527 -0.8584,0.352 -1.7781,0.5298 -2.7058,0.5231 h -9.41999" stroke="#f2f2f2" stroke-miterlimit="10" id="path580" style="fill:none;stroke:#f2f2f2;stroke-width:1.45486;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 4.75002,8.44998 -4.039998,-4.05 4.039998,-4.050004" stroke="#f2f2f2" stroke-miterlimit="10" id="path582" style="fill:none;stroke:#f2f2f2;stroke-width:1.45486;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g clip-path="url(#clip0_241_10857-3)" id="undo-5" transform="matrix(-0.71300568,0,0,0.66260978,90.012499,938.13028)" style="fill:none;stroke:#f2f2f2;stroke-width:1.45488;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1">
|
||||
<path d="m 1.07001,4.41003 h 9.41999 c 0.9234,-0.0066 1.8391,0.16957 2.6941,0.5184 0.8551,0.34883 1.6327,0.8634 2.288,1.51407 0.6553,0.65067 1.1754,1.42458 1.5303,2.27713 0.3549,0.85256 0.5376,1.76697 0.5376,2.69037 0.0067,0.9277 -0.1712,1.8474 -0.5231,2.7058 -0.3519,0.8583 -0.871,1.6382 -1.527,2.2941 -0.656,0.656 -1.4358,1.1751 -2.2941,1.527 -0.8584,0.352 -1.7781,0.5298 -2.7058,0.5231 h -9.41999" stroke="#f2f2f2" stroke-miterlimit="10" id="path580-3" style="fill:none;stroke:#f2f2f2;stroke-width:1.45486;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path d="m 4.75002,8.44998 -4.039998,-4.05 4.039998,-4.050004" stroke="#f2f2f2" stroke-miterlimit="10" id="path582-5" style="fill:none;stroke:#f2f2f2;stroke-width:1.45486;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
<g id="g1" transform="translate(144,-24.00004)">
|
||||
<path d="m 9,849.93721 v 4.04997 c 0,0.20708 0.167895,0.375 0.375,0.375 h 5.25 c 0.207075,0 0.375,-0.16792 0.375,-0.375 v -4.04997 c 0,-0.20708 -0.167925,-0.375 -0.375,-0.375 h -5.25 c -0.207105,0 -0.375,0.16792 -0.375,0.375 z" stroke="#f2f2f2" id="path438" style="fill:none;stroke:#999999;stroke-width:0.999997;stroke-opacity:1" />
|
||||
<path d="m 15.21255,842.36218 h -8.83755 c -0.207105,0 -0.375,0.1679 -0.375,0.37499 v 11.24993 c 0,0.20708 0.167895,0.375 0.375,0.375 h 11.25 c 0.207075,0 0.375,-0.16792 0.375,-0.375 v -8.67664 c 0,-0.0953 -0.0363,-0.18697 -0.1014,-0.25648 l -2.41245,-2.57327 c -0.07095,-0.0756 -0.16995,-0.11853 -0.2736,-0.11853 z" stroke="#f2f2f2" id="save" style="fill:none;stroke:#999999;stroke-width:0.999997;stroke-opacity:1" />
|
||||
|
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 50 KiB |
|
@ -91,6 +91,7 @@ class Feature {
|
|||
}
|
||||
|
||||
set geometry(value) {
|
||||
this._geometry_bk = Utils.CopyJSON(this._geometry)
|
||||
this._geometry = value
|
||||
this.pushGeometry()
|
||||
}
|
||||
|
@ -104,13 +105,17 @@ class Feature {
|
|||
}
|
||||
|
||||
pullGeometry(sync = true) {
|
||||
const oldGeometry = Utils.CopyJSON(this._geometry)
|
||||
this.fromLatLngs(this._getLatLngs())
|
||||
if (sync) {
|
||||
this.sync.update('geometry', this.geometry)
|
||||
console.log('sync geometry')
|
||||
this.sync.update('geometry', this.geometry, oldGeometry)
|
||||
}
|
||||
}
|
||||
|
||||
fromLatLngs(latlngs) {
|
||||
console.log('fromLatLngs', latlngs)
|
||||
this._geometry_bk = Utils.CopyJSON(this._geometry)
|
||||
this._geometry = this.convertLatLngs(latlngs)
|
||||
}
|
||||
|
||||
|
@ -145,8 +150,15 @@ class Feature {
|
|||
onCommit() {
|
||||
// When the layer is a remote layer, we don't want to sync the creation of the
|
||||
// points via the websocket, as the other peers will get them themselves.
|
||||
const oldGeoJSON = this._just_married ? null : Utils.CopyJSON(this.toGeoJSON())
|
||||
this.pullGeometry(false)
|
||||
if (this.datalayer?.isRemoteLayer()) return
|
||||
this.sync.upsert(this.toGeoJSON())
|
||||
if (this._just_married) {
|
||||
this.sync.upsert(this.toGeoJSON(), null)
|
||||
this._just_married = false
|
||||
} else {
|
||||
this.sync.update('geometry', this.geometry, this._geometry_bk)
|
||||
}
|
||||
}
|
||||
|
||||
isReadOnly() {
|
||||
|
|
|
@ -417,7 +417,11 @@ export class DataLayer extends ServerStored {
|
|||
|
||||
removeFeature(feature, sync) {
|
||||
const id = stamp(feature)
|
||||
if (sync !== false) feature.sync.delete()
|
||||
if (sync !== false) {
|
||||
const oldValue = feature.toGeoJSON()
|
||||
console.log('oldValue in removeFeature', oldValue)
|
||||
feature.sync.delete(oldValue)
|
||||
}
|
||||
this.hideFeature(feature)
|
||||
delete this._umap.featuresIndex[feature.getSlug()]
|
||||
feature.disconnectFromDataLayer(this)
|
||||
|
@ -596,10 +600,11 @@ export class DataLayer extends ServerStored {
|
|||
}
|
||||
|
||||
del(sync = true) {
|
||||
const oldValue = Utils.CopyJSON(this.umapGeoJSON())
|
||||
this.erase()
|
||||
if (sync) {
|
||||
this.isDeleted = true
|
||||
this.sync.delete()
|
||||
this.sync.delete(oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,13 +190,14 @@ export class MutatingForm extends Form {
|
|||
}
|
||||
|
||||
setter(field, value) {
|
||||
const oldValue = this.getter(field)
|
||||
super.setter(field, value)
|
||||
this.obj.isDirty = true
|
||||
if ('render' in this.obj) {
|
||||
this.obj.render([field], this)
|
||||
}
|
||||
if ('sync' in this.obj) {
|
||||
this.obj.sync.update(field, value)
|
||||
this.obj.sync.update(field, value, oldValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -97,7 +97,6 @@ const FeatureMixin = {
|
|||
},
|
||||
|
||||
onCommit: function () {
|
||||
this.feature.pullGeometry(false)
|
||||
this.feature.onCommit()
|
||||
},
|
||||
}
|
||||
|
@ -112,7 +111,7 @@ const PointMixin = {
|
|||
this.on('dragend', (event) => {
|
||||
this.isDirty = true
|
||||
this.feature.edit(event)
|
||||
this.feature.pullGeometry(false)
|
||||
// this.feature.pullGeometry(false)
|
||||
})
|
||||
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
|
||||
this.on('mouseout', this._onMouseOut)
|
||||
|
@ -303,13 +302,13 @@ const PathMixin = {
|
|||
this._container = null
|
||||
FeatureMixin.onAdd.call(this, map)
|
||||
this.setStyle()
|
||||
if (this.editing?.enabled()) this.editing.addHooks()
|
||||
if (this.editor?.enabled()) this.editor.addHooks()
|
||||
this.resetTooltip()
|
||||
this._path.dataset.feature = this.feature.id
|
||||
},
|
||||
|
||||
onRemove: function (map) {
|
||||
if (this.editing?.enabled()) this.editing.removeHooks()
|
||||
if (this.editor?.enabled()) this.editor.removeHooks()
|
||||
FeatureMixin.onRemove.call(this, map)
|
||||
},
|
||||
|
||||
|
@ -362,6 +361,13 @@ const PathMixin = {
|
|||
isOnScreen: function (bounds) {
|
||||
return bounds.overlaps(this.getBounds())
|
||||
},
|
||||
|
||||
_setLatLngs: function (latlngs) {
|
||||
this.parentClass.prototype._setLatLngs.call(this, latlngs)
|
||||
if (this.editor?.enabled()) {
|
||||
this.editor.reset()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
export const LeafletPolyline = Polyline.extend({
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as SaveManager from '../saving.js'
|
||||
import * as Utils from '../utils.js'
|
||||
import { HybridLogicalClock } from './hlc.js'
|
||||
import { UndoManager } from './undo.js'
|
||||
import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
|
||||
import { WebSocketTransport } from './websocket.js'
|
||||
|
||||
|
@ -64,6 +65,7 @@ export class SyncEngine {
|
|||
this.websocketConnected = false
|
||||
this.closeRequested = false
|
||||
this.peerId = Utils.generateId()
|
||||
this._undoManager = new UndoManager(this.updaters, this)
|
||||
}
|
||||
|
||||
get isOpen() {
|
||||
|
@ -122,16 +124,38 @@ export class SyncEngine {
|
|||
await this.authenticate()
|
||||
}, this._reconnectDelay)
|
||||
}
|
||||
upsert(subject, metadata, value) {
|
||||
upsert(subject, metadata, value, oldValue) {
|
||||
this._undoManager.add({
|
||||
verb: 'upsert',
|
||||
subject,
|
||||
metadata,
|
||||
oldValue: oldValue,
|
||||
newValue: value,
|
||||
})
|
||||
this._send({ verb: 'upsert', subject, metadata, value })
|
||||
}
|
||||
|
||||
update(subject, metadata, key, value) {
|
||||
update(subject, metadata, key, value, oldValue) {
|
||||
this._undoManager.add({
|
||||
verb: 'update',
|
||||
subject,
|
||||
metadata,
|
||||
key,
|
||||
oldValue: oldValue,
|
||||
newValue: value,
|
||||
})
|
||||
this._send({ verb: 'update', subject, metadata, key, value })
|
||||
}
|
||||
|
||||
delete(subject, metadata, key) {
|
||||
this._send({ verb: 'delete', subject, metadata, key })
|
||||
delete(subject, metadata, oldValue) {
|
||||
console.log('oldValue', oldValue)
|
||||
this._undoManager.add({
|
||||
verb: 'delete',
|
||||
subject,
|
||||
metadata,
|
||||
oldValue: oldValue,
|
||||
})
|
||||
this._send({ verb: 'delete', subject, metadata })
|
||||
}
|
||||
|
||||
saved() {
|
||||
|
|
71
umap/static/umap/js/modules/sync/undo.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
import * as Utils from '../utils.js'
|
||||
import { DataLayerUpdater, FeatureUpdater, MapUpdater } from './updaters.js'
|
||||
|
||||
export class UndoManager {
|
||||
constructor(updaters, syncEngine) {
|
||||
this._syncEngine = syncEngine
|
||||
this.updaters = updaters
|
||||
this._undoStack = []
|
||||
this._redoStack = []
|
||||
}
|
||||
|
||||
toggleState() {
|
||||
document.querySelector('.edit-undo').disabled = !this._undoStack.length
|
||||
document.querySelector('.edit-redo').disabled = !this._redoStack.length
|
||||
}
|
||||
|
||||
add(operation) {
|
||||
console.debug('New entry in undo stack', operation)
|
||||
this._redoStack = []
|
||||
this._undoStack.push(operation)
|
||||
this.toggleState()
|
||||
}
|
||||
|
||||
undo(redo = false) {
|
||||
const fromStack = redo ? this._redoStack : this._undoStack
|
||||
const toStack = redo ? this._undoStack : this._redoStack
|
||||
const operation = fromStack.pop()
|
||||
if (!operation) return
|
||||
const syncOperation = Utils.CopyJSON(operation)
|
||||
console.log('old/new', syncOperation.oldValue, syncOperation.newValue)
|
||||
delete syncOperation.oldValue
|
||||
delete syncOperation.newValue
|
||||
syncOperation.value = redo ? operation.newValue : operation.oldValue
|
||||
this.applyOperation(syncOperation)
|
||||
toStack.push(operation)
|
||||
this.toggleState()
|
||||
}
|
||||
|
||||
redo() {
|
||||
this.undo(true)
|
||||
}
|
||||
|
||||
applyOperation(syncOperation) {
|
||||
const updater = this._getUpdater(syncOperation.subject, syncOperation.metadata)
|
||||
switch (syncOperation.verb) {
|
||||
case 'update':
|
||||
updater.update(syncOperation)
|
||||
this._syncEngine._send(syncOperation)
|
||||
break
|
||||
case 'delete':
|
||||
case 'upsert':
|
||||
console.log('undo upsert/delete', syncOperation.value)
|
||||
if (syncOperation.value === null || syncOperation.value === undefined) {
|
||||
console.log('case delete')
|
||||
updater.delete(syncOperation)
|
||||
} else {
|
||||
console.log('case upsert')
|
||||
updater.upsert(syncOperation)
|
||||
}
|
||||
this._syncEngine._send(syncOperation)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
_getUpdater(subject, metadata) {
|
||||
if (Object.keys(this.updaters).includes(subject)) {
|
||||
return this.updaters[subject]
|
||||
}
|
||||
throw new Error(`Unknown updater ${subject}, ${metadata}`)
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ class BaseUpdater {
|
|||
|
||||
export class MapUpdater extends BaseUpdater {
|
||||
update({ key, value }) {
|
||||
console.log('updating', key, value)
|
||||
if (fieldInSchema(key)) {
|
||||
this.updateObjectValue(this._umap, key, value)
|
||||
}
|
||||
|
@ -56,9 +57,17 @@ export class DataLayerUpdater extends BaseUpdater {
|
|||
upsert({ value }) {
|
||||
// Upsert only happens when a new datalayer is created.
|
||||
try {
|
||||
this.getDataLayerFromID(value.id)
|
||||
console.log(
|
||||
'found datalayer with id',
|
||||
value.id,
|
||||
this.getDataLayerFromID(value.id)
|
||||
)
|
||||
} catch {
|
||||
this._umap.createDataLayer(value, false)
|
||||
console.log('we are the fucking catch', value)
|
||||
const datalayer = this._umap.createDataLayer(value._umap_options || value, false)
|
||||
if (value.features) {
|
||||
datalayer.addData(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,11 +101,14 @@ export class FeatureUpdater extends BaseUpdater {
|
|||
|
||||
// Create or update an object at a specific position
|
||||
upsert({ metadata, value }) {
|
||||
console.log('updater.upsert for', metadata, value)
|
||||
const { id, layerId } = metadata
|
||||
const datalayer = this.getDataLayerFromID(layerId)
|
||||
const feature = this.getFeatureFromMetadata(metadata)
|
||||
console.log('feature', feature)
|
||||
|
||||
if (feature) {
|
||||
console.log('changing feature geometry')
|
||||
feature.geometry = value.geometry
|
||||
} else {
|
||||
datalayer.makeFeature(value, false)
|
||||
|
|
|
@ -22,9 +22,13 @@ const TOP_BAR_TEMPLATE = `
|
|||
<span class="username" data-ref="username"></span>
|
||||
</button>
|
||||
<button class="umap-help-link flat" type="button" title="${translate('Help')}" data-ref="help">${translate('Help')}</button>
|
||||
<button class="edit-cancel round" type="button" data-ref="cancel">
|
||||
<i class="icon icon-16 icon-restore"></i>
|
||||
<span class="">${translate('Cancel edits')}</span>
|
||||
<button class="edit-undo round" type="button" data-ref="undo" disabled>
|
||||
<i class="icon icon-16 icon-undo"></i>
|
||||
<span class="">${translate('Undo')}</span>
|
||||
</button>
|
||||
<button class="edit-redo round" type="button" data-ref="redo" disabled>
|
||||
<i class="icon icon-16 icon-redo"></i>
|
||||
<span class="">${translate('Redo')}</span>
|
||||
</button>
|
||||
<button class="edit-disable round" type="button" data-ref="view">
|
||||
<i class="icon icon-16 icon-eye"></i>
|
||||
|
@ -118,11 +122,12 @@ export class TopBar extends WithTemplate {
|
|||
})
|
||||
|
||||
this.elements.help.addEventListener('click', () => this._umap.help.showGetStarted())
|
||||
this.elements.cancel.addEventListener('click', () => this._umap.askForReset())
|
||||
this.elements.cancel.addEventListener('mouseover', () => {
|
||||
this.elements.redo.addEventListener('click', () => this._umap.redo())
|
||||
this.elements.undo.addEventListener('click', () => this._umap.undo())
|
||||
this.elements.undo.addEventListener('mouseover', () => {
|
||||
this._umap.tooltip.open({
|
||||
content: this._umap.help.displayLabel('CANCEL'),
|
||||
anchor: this.elements.cancel,
|
||||
anchor: this.elements.undo,
|
||||
position: 'bottom',
|
||||
delay: 500,
|
||||
duration: 5000,
|
||||
|
@ -154,7 +159,7 @@ export class TopBar extends WithTemplate {
|
|||
redraw() {
|
||||
const syncEnabled = this._umap.getProperty('syncEnabled')
|
||||
this.elements.peers.hidden = !syncEnabled
|
||||
this.elements.cancel.hidden = syncEnabled
|
||||
// this.elements.cancel.hidden = syncEnabled
|
||||
this.elements.saveLabel.hidden = this._umap.permissions.isDraft()
|
||||
this.elements.saveDraftLabel.hidden = !this._umap.permissions.isDraft()
|
||||
}
|
||||
|
|
|
@ -608,6 +608,7 @@ export default class Umap extends ServerStored {
|
|||
}
|
||||
|
||||
createDataLayer(options = {}, sync = true) {
|
||||
console.log('createDatalayer', options)
|
||||
options.name =
|
||||
options.name || `${translate('Layer')} ${this.datalayersIndex.length + 1}`
|
||||
const datalayer = new DataLayer(this, this._leafletMap, options)
|
||||
|
@ -1777,4 +1778,12 @@ export default class Umap extends ServerStored {
|
|||
getStaticPathFor(name) {
|
||||
return SCHEMA.iconUrl.default.replace('marker.svg', name)
|
||||
}
|
||||
|
||||
undo() {
|
||||
this.sync._undoManager.undo()
|
||||
}
|
||||
|
||||
redo() {
|
||||
this.sync._undoManager.redo()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -297,6 +297,7 @@ U.TileLayerChooser = L.Control.extend({
|
|||
el,
|
||||
'click',
|
||||
() => {
|
||||
const oldTileLayer = this.map._umap.properties.tilelayer
|
||||
this.map.selectTileLayer(tilelayer)
|
||||
this.map._controls.tilelayers.setLayers()
|
||||
if (options?.edit) {
|
||||
|
@ -304,7 +305,8 @@ U.TileLayerChooser = L.Control.extend({
|
|||
this.map._umap.isDirty = true
|
||||
this.map._umap.sync.update(
|
||||
'properties.tilelayer',
|
||||
this.map._umap.properties.tilelayer
|
||||
this.map._umap.properties.tilelayer,
|
||||
oldTileLayer
|
||||
)
|
||||
}
|
||||
},
|
||||
|
@ -606,7 +608,7 @@ U.Editable = L.Editable.extend({
|
|||
this.on('editable:editing', (event) => {
|
||||
const feature = event.layer.feature
|
||||
feature.isDirty = true
|
||||
feature.pullGeometry(false)
|
||||
// feature.pullGeometry(false)
|
||||
})
|
||||
this.on('editable:vertex:ctrlclick', (event) => {
|
||||
const index = event.vertex.getIndex()
|
||||
|
@ -624,18 +626,18 @@ U.Editable = L.Editable.extend({
|
|||
|
||||
createPolyline: function (latlngs) {
|
||||
const datalayer = this._umap.defaultEditDataLayer()
|
||||
const point = new U.LineString(this._umap, datalayer, {
|
||||
const line = new U.LineString(this._umap, datalayer, {
|
||||
geometry: { type: 'LineString', coordinates: [] },
|
||||
})
|
||||
return point.ui
|
||||
return line.ui
|
||||
},
|
||||
|
||||
createPolygon: function (latlngs) {
|
||||
const datalayer = this._umap.defaultEditDataLayer()
|
||||
const point = new U.Polygon(this._umap, datalayer, {
|
||||
const poly = new U.Polygon(this._umap, datalayer, {
|
||||
geometry: { type: 'Polygon', coordinates: [] },
|
||||
})
|
||||
return point.ui
|
||||
return poly.ui
|
||||
},
|
||||
|
||||
createMarker: function (latlng) {
|
||||
|
@ -643,6 +645,7 @@ U.Editable = L.Editable.extend({
|
|||
const point = new U.Point(this._umap, datalayer, {
|
||||
geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
|
||||
})
|
||||
point._just_married = true
|
||||
return point.ui
|
||||
},
|
||||
|
||||
|
|
|
@ -986,7 +986,7 @@ a.umap-control-caption,
|
|||
.umap-main-edit-toolbox .umap-user span,
|
||||
.leaflet-container .leaflet-control-edit-save span,
|
||||
.leaflet-container .leaflet-control-edit-disable span,
|
||||
.leaflet-container .leaflet-control-edit-cancel span {
|
||||
.leaflet-container .edit-cancel span {
|
||||
display: none;
|
||||
}
|
||||
.umap-main-edit-toolbox .umap-help-button {
|
||||
|
|
|
@ -41,7 +41,7 @@ class LicenceFactory(factory.django.DjangoModelFactory):
|
|||
|
||||
|
||||
class TileLayerFactory(factory.django.DjangoModelFactory):
|
||||
name = "Test zoom layer"
|
||||
name = "Test tilelayer"
|
||||
url_template = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
attribution = "Test layer attribution"
|
||||
|
||||
|
|
231
umap/tests/integration/test_undo_redo.py
Normal file
|
@ -0,0 +1,231 @@
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import expect
|
||||
|
||||
from umap.models import Map, TileLayer
|
||||
|
||||
from ..base import DataLayerFactory
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
DATALAYER_DATA = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"name": "name poly",
|
||||
},
|
||||
"id": "gyNzM",
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[11.25, 53.585984],
|
||||
[10.151367, 52.975108],
|
||||
[12.689209, 52.167194],
|
||||
[14.084473, 53.199452],
|
||||
[12.634277, 53.618579],
|
||||
[11.25, 53.585984],
|
||||
[11.25, 53.585984],
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def map_with_polygon(map, live_server):
|
||||
map.settings["properties"]["zoom"] = 6
|
||||
map.settings["geometry"] = {
|
||||
"type": "Point",
|
||||
"coordinates": [8.429, 53.239],
|
||||
}
|
||||
map.edit_status = Map.ANONYMOUS
|
||||
map.save()
|
||||
DataLayerFactory(map=map, data=DATALAYER_DATA)
|
||||
return map
|
||||
|
||||
|
||||
def test_can_undo_redo_map_name_change(page, live_server, tilelayer):
|
||||
page.goto(f"{live_server.url}/en/map/new/")
|
||||
|
||||
expect(page.locator(".edit-undo")).to_be_disabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
page.get_by_title("Edit map name and caption").click()
|
||||
name_input = page.locator('.map-metadata input[name="name"]')
|
||||
expect(name_input).to_be_visible()
|
||||
name_input.click()
|
||||
name_input.press("Control+a")
|
||||
name_input.fill("New map name")
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
map_name = page.locator(".umap-main-edit-toolbox .map-name")
|
||||
expect(map_name).to_have_text("New map name")
|
||||
name_input.fill("New name again")
|
||||
expect(map_name).to_have_text("New name again")
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
expect(map_name).to_have_text("New map name")
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_enabled()
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
expect(map_name).to_have_text("New name again")
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
expect(map_name).to_have_text("New map name")
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_enabled()
|
||||
|
||||
|
||||
def test_can_undo_redo_layer_color_change(
|
||||
page, map_with_polygon, live_server, tilelayer
|
||||
):
|
||||
page.goto(f"{live_server.url}{map_with_polygon.get_absolute_url()}?edit")
|
||||
|
||||
expect(page.locator(".edit-undo")).to_be_disabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
page.get_by_role("button", name="Manage layers").click()
|
||||
page.locator(".panel").get_by_title("Edit", exact=True).click()
|
||||
page.get_by_text("Shape properties").click()
|
||||
page.locator(".umap-field-color .define").click()
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
|
||||
page.get_by_title("DarkRed").first.click()
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(1)
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(1)
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(0)
|
||||
expect(page.locator(".edit-undo")).to_be_disabled()
|
||||
expect(page.locator(".edit-redo")).to_be_enabled()
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkRed']")).to_have_count(1)
|
||||
expect(page.locator(".leaflet-overlay-pane path[fill='DarkBlue']")).to_have_count(0)
|
||||
expect(page.locator(".edit-undo")).to_be_enabled()
|
||||
expect(page.locator(".edit-redo")).to_be_disabled()
|
||||
|
||||
|
||||
def test_can_undo_redo_tilelayer_change(live_server, page, openmap, tilelayer):
|
||||
TileLayer.objects.create(
|
||||
url_template="https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
|
||||
attribution="OSM/Carto",
|
||||
name="Black Tiles",
|
||||
)
|
||||
page.goto(f"{live_server.url}{openmap.get_absolute_url()}?edit")
|
||||
old_pattern = re.compile(
|
||||
r"https://[abc]{1}.tile.openstreetmap.fr/osmfr/\d+/\d+/\d+.png"
|
||||
)
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
expect(tiles.first).to_have_attribute("src", old_pattern)
|
||||
|
||||
new_pattern = re.compile(
|
||||
r"https://[abcd]{1}.basemaps.cartocdn.com/dark_all/\d+/\d+/\d+.png"
|
||||
)
|
||||
page.get_by_role("button", name="Change tilelayers").click()
|
||||
page.locator("li").filter(has_text="Black Tiles").get_by_role("img").click()
|
||||
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
expect(tiles.first).to_have_attribute("src", new_pattern)
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
expect(tiles.first).to_have_attribute("src", old_pattern)
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
tiles = page.locator(".leaflet-tile-pane img")
|
||||
expect(tiles.first).to_have_attribute("src", new_pattern)
|
||||
|
||||
|
||||
def test_can_undo_redo_marker_drag(live_server, page, tilelayer):
|
||||
page.goto(f"{live_server.url}/en/map/new")
|
||||
|
||||
marker = page.locator(".leaflet-marker-icon")
|
||||
map = page.locator("#map")
|
||||
|
||||
# Create a marker
|
||||
page.get_by_title("Draw a marker").click()
|
||||
map.click(position={"x": 225, "y": 225})
|
||||
expect(marker).to_have_count(1)
|
||||
|
||||
# Drag marker
|
||||
old_bbox = marker.bounding_box()
|
||||
marker.first.drag_to(map, target_position={"x": 250, "y": 250})
|
||||
assert marker.bounding_box() != old_bbox
|
||||
|
||||
# Undo
|
||||
page.locator(".edit-undo").click()
|
||||
assert marker.bounding_box() == old_bbox
|
||||
|
||||
# Redo
|
||||
page.locator(".edit-redo").click()
|
||||
assert marker.bounding_box() != old_bbox
|
||||
|
||||
|
||||
def test_can_undo_redo_polygon_geometry_change(live_server, page, tilelayer):
|
||||
page.goto(f"{live_server.url}/en/map/new")
|
||||
|
||||
# Click on the Draw a polygon button on a new map.
|
||||
page.get_by_title("Draw a polygon").click()
|
||||
|
||||
polygon = page.locator("path[fill='DarkBlue']")
|
||||
expect(polygon).to_have_count(0)
|
||||
|
||||
# Click on the map, it will create a polygon.
|
||||
map = page.locator("#map")
|
||||
map.click(position={"x": 200, "y": 200})
|
||||
map.click(position={"x": 100, "y": 200})
|
||||
map.click(position={"x": 100, "y": 100})
|
||||
map.click(position={"x": 100, "y": 100})
|
||||
|
||||
# It is created on peerA, and should be on peerB
|
||||
expect(polygon).to_have_count(1)
|
||||
old_bbox = polygon.bounding_box()
|
||||
|
||||
edited_vertex = page.locator(".leaflet-middle-icon:nth-child(3)").first
|
||||
edited_vertex.drag_to(map, target_position={"x": 250, "y": 250})
|
||||
page.keyboard.press("Escape")
|
||||
|
||||
assert polygon.bounding_box() != old_bbox
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
assert polygon.bounding_box() == old_bbox
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
assert polygon.bounding_box() != old_bbox
|
||||
|
||||
|
||||
def test_can_undo_redo_marker_create(live_server, page, tilelayer):
|
||||
page.goto(f"{live_server.url}/en/map/new")
|
||||
|
||||
page.get_by_title("Open Browser").click()
|
||||
marker = page.locator(".leaflet-marker-icon")
|
||||
map = page.locator("#map")
|
||||
|
||||
# Create a marker
|
||||
page.get_by_title("Draw a marker").click()
|
||||
map.click(position={"x": 600, "y": 100})
|
||||
expect(marker).to_have_count(1)
|
||||
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
expect(marker).to_have_count(0)
|
||||
# Layer still exists
|
||||
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
||||
|
||||
page.locator(".edit-undo").click()
|
||||
expect(page.locator(".panel .datalayer")).to_have_count(0)
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
expect(page.locator(".panel .datalayer")).to_have_count(1)
|
||||
|
||||
page.locator(".edit-redo").click()
|
||||
expect(marker).to_have_count(1)
|