Map customization

Open in CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import type {VectorCustomizationElements, VectorCustomizationItem} from '@mappable-world/mappable-types';
      import {
        buildingInitialCustomization,
        CustomizationControl,
        generateColor,
        groundInitialCustomization,
        roadsInitialCustomization,
        waterInitialCustomization
      } from './common';
      import {LOCATION} from '../variables';

      window.map = null;
      main();

      async function main() {
        // Waiting for all api elements to be loaded
        await mappable.ready;
        const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapControl} = mappable;

        let customization = [
          waterInitialCustomization,
          roadsInitialCustomization,
          groundInitialCustomization,
          buildingInitialCustomization
        ];

        // Initialize the map
        map = new MMap(
          // Pass the link to the HTMLElement of the container
          document.getElementById('app'),
          // Pass the map initialization parameters
          {location: LOCATION, showScaleInCopyrights: true}
        );

        // Create a function that we will call when changing the customization of the map through the control
        function updateCustomization(updatedCustomization) {
          console.log('updatedCustomization', updatedCustomization);
          layer.update({customization: JSON.parse(updatedCustomization)});
        }

        // Add a map schema layer with a custom customization props
        const layer = new MMapDefaultSchemeLayer({
          customization
        });

        map.addChild(layer);

        // Create a CustomizationControl to change the appearance of the road
        const roadControl = new CustomizationControl({
          title: 'Road',
          initialValues: roadsInitialCustomization,
          changeColorHandler: createChangeColorHandler(['road'], 'geometry'),
          changeOpacityHandler: createChangeOpacityHandler(['road'], 'geometry'),
          changeScaleHandler: createChangeScaleHandler(['road'], 'geometry'),
          withDivider: true
        });

        // Create a CustomizationControl to change the appearance of the water
        const waterControl = new CustomizationControl({
          title: 'Water',
          initialValues: waterInitialCustomization,
          changeColorHandler: createChangeColorHandler(['water'], 'geometry'),
          changeOpacityHandler: createChangeOpacityHandler(['water'], 'geometry'),
          changeScaleHandler: createChangeScaleHandler(['water'], 'geometry'),
          withDivider: true
        });

        // Create a CustomizationControl to change the appearance of the ground
        const groundControl = new CustomizationControl({
          title: 'Ground',
          initialValues: groundInitialCustomization,
          changeColorHandler: createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry'),
          changeOpacityHandler: createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')
        });

        // Create a CustomizationControl to change the appearance of the building
        const buildingControl = new CustomizationControl({
          title: 'Building',
          initialValues: buildingInitialCustomization,
          changeColorHandler: createChangeColorHandler(['building'], 'geometry'),
          changeOpacityHandler: createChangeOpacityHandler(['building'], 'geometry')
        });

        // Create a shared container for custom CustomizationControl's and add it to the map
        const controls = new MMapControls({position: 'top right', orientation: 'horizontal'});
        map.addChild(controls);

        // Add controls to the map
        controls
          .addChild(new MMapControl().addChild(waterControl).addChild(groundControl))
          .addChild(new MMapControl().addChild(roadControl).addChild(buildingControl));

        // A function that creates a handler for changing the color of geo objects
        function createChangeColorHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
          return () => {
            const color = generateColor();
            const customizationObject = customization.find(
              (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
            );

            if (customizationObject) {
              customizationObject.stylers[0]['color'] = color;
            } else {
              const newTagObject: VectorCustomizationItem = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{color}]
              };

              customization.push(newTagObject);
            }
            updateCustomization(JSON.stringify(customization, null, 2));
            return color;
          };
        }

        // A function that creates a handler to change the opacity of geo objects
        function createChangeOpacityHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
          return (diff: number) => {
            let opacity = 0.5;
            const customizationObject = customization.find(
              (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
            );

            if (!customizationObject) {
              const newTagObject: VectorCustomizationItem = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{opacity}]
              };
              customization.push(newTagObject);
            } else if (customizationObject.stylers[0]['opacity'] === undefined) {
              customizationObject.stylers[0]['opacity'] = opacity;
            }
            opacity = +(customizationObject.stylers[0]['opacity'] + diff).toFixed(1);
            customizationObject.stylers[0]['opacity'] = opacity;
            updateCustomization(JSON.stringify(customization, null, 2));
            return opacity;
          };
        }

        // A function that creates a handler to change the scale of geo objects
        function createChangeScaleHandler(controlTags: string[], controlElements: VectorCustomizationElements) {
          return (diff: number) => {
            let scale = 1;
            const customizationObject = customization.find(
              (item) => typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
            );

            if (!customizationObject) {
              const newTagObject: VectorCustomizationItem = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{scale}]
              };
              customization.push(newTagObject);
            } else if (customizationObject.stylers[0]['scale'] === undefined) {
              customizationObject.stylers[0]['scale'] = scale;
            }
            scale = Math.floor(customizationObject.stylers[0]['scale'] + diff);
            customizationObject.stylers[0]['scale'] = scale;
            updateCustomization(JSON.stringify(customization, null, 2));
            return scale;
          };
        }
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import type {
        VectorCustomization,
        VectorCustomizationElements,
        VectorCustomizationItem
      } from '@mappable-world/mappable-types';
      import {
        buildingInitialCustomization,
        CustomizationControl,
        generateColor,
        groundInitialCustomization,
        roadsInitialCustomization,
        waterInitialCustomization
      } from './common';
      import {LOCATION} from '../variables';

      window.map = null;

      main();

      async function main() {
        // For each object in the JS API, there is a React counterpart
        // To use the React version of the API, include the module @mappable-world/mappable-reactify
        const [mappableReact] = await Promise.all([
          mappable.import('@mappable-world/mappable-reactify'),
          mappable.ready
        ]);
        const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
        const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapControl} = reactify.module(mappable);

        // Using mappable-rectify, we turn a custom CustomizationControl into a React component
        const {CustomizationControl: CustomizationControlR} = reactify.module({CustomizationControl});

        const {useCallback, useState} = React;

        function App() {
          const [customization, setCustomization] =
            useState <
            VectorCustomization >
            [
              waterInitialCustomization,
              groundInitialCustomization,
              roadsInitialCustomization,
              buildingInitialCustomization
            ];

          // A function that creates a handler for changing the color of geo objects
          const createChangeColorHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return () => {
                const color = generateColor();
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (customizationObject) {
                    customizationObject.stylers[0]['color'] = color;
                  } else {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{color}]
                    };

                    customization.push(newTagObject);
                  }
                  return [...customization];
                });
                return color;
              };
            },
            []
          );

          // A function that creates a handler to change the opacity of geo objects
          const createChangeOpacityHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return (diff: number) => {
                let opacity = 0.5;
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );
                  if (!customizationObject) {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{opacity}]
                    };
                    customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['opacity'] === undefined) {
                    customizationObject.stylers[0]['opacity'] = opacity;
                  }
                  opacity = +(customizationObject.stylers[0]['opacity'] + diff).toFixed(1);
                  customizationObject.stylers[0]['opacity'] = opacity;

                  return [...customization];
                });
                return opacity;
              };
            },
            []
          );

          // A function that creates a handler to change the scale of geo objects
          const createChangeScaleHandler = useCallback(
            (controlTags: string[], controlElements: VectorCustomizationElements) => {
              return (diff: number) => {
                let scale = 1;
                setCustomization((customization) => {
                  const customizationObject = customization.find(
                    (item) =>
                      typeof item.tags === 'object' && JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
                  );

                  if (!customizationObject) {
                    const newTagObject: VectorCustomizationItem = {
                      tags: {any: controlTags},
                      elements: controlElements,
                      stylers: [{scale}]
                    };
                    customization.push(newTagObject);
                  } else if (customizationObject.stylers[0]['scale'] === undefined) {
                    customizationObject.stylers[0]['scale'] = scale;
                  }
                  scale = Math.floor(customizationObject.stylers[0]['scale'] + diff);
                  customizationObject.stylers[0]['scale'] = scale;

                  return [...customization];
                });
                return scale;
              };
            },
            []
          );

          return (
            <MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
              {/* Add a map scheme layer with a custom customization props */}
              <MMapDefaultSchemeLayer customization={customization} />

              {/* Add a shared container to the map for custom CustomizationControl's */}
              <MMapControls position={'top right'} orientation={'horizontal'}>
                <MMapControl>
                  {/* Add a CustomizationControl to the map to change the appearance of the water */}
                  <CustomizationControlR
                    initialValues={waterInitialCustomization}
                    title="Water"
                    changeColorHandler={createChangeColorHandler(['water'], 'geometry')}
                    changeOpacityHandler={createChangeOpacityHandler(['water'], 'geometry')}
                    changeScaleHandler={createChangeScaleHandler(['water'], 'geometry')}
                    withDivider
                  />
                  {/* Add a CustomizationControl to the map to change the appearance of the ground */}
                  <CustomizationControlR
                    initialValues={groundInitialCustomization}
                    title="Ground"
                    changeColorHandler={createChangeColorHandler(
                      ['landscape', 'admin', 'land', 'transit'],
                      'geometry'
                    )}
                    changeOpacityHandler={createChangeOpacityHandler(
                      ['landscape', 'admin', 'land', 'transit'],
                      'geometry'
                    )}
                  />
                </MMapControl>
                <MMapControl>
                  {/* Add a CustomizationControl to the map to change the appearance of the road */}
                  <CustomizationControlR
                    initialValues={roadsInitialCustomization}
                    title="Road"
                    changeColorHandler={createChangeColorHandler(['road'], 'geometry')}
                    changeOpacityHandler={createChangeOpacityHandler(['road'], 'geometry')}
                    changeScaleHandler={createChangeScaleHandler(['road'], 'geometry')}
                    withDivider
                  />
                  {/* Add a CustomizationControl to the map to change the appearance of the building */}
                  <CustomizationControlR
                    initialValues={buildingInitialCustomization}
                    title="Building"
                    changeColorHandler={createChangeColorHandler(['building'], 'geometry')}
                    changeOpacityHandler={createChangeOpacityHandler(['building'], 'geometry')}
                  />
                </MMapControl>
              </MMapControls>
            </MMap>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {
        buildingInitialCustomization,
        CustomizationControl,
        generateColor,
        groundInitialCustomization,
        roadsInitialCustomization,
        waterInitialCustomization
      } from './common';
      import type {VectorCustomization} from '@mappable-world/mappable-types';
      import {LOCATION} from '../variables';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapControls, MMapControl} = vuefy.module(mappable);

        // Using mappable-vuefy, we turn a custom CustomizationControl into a Vue component
        const {CustomizationControl: CustomizationControlV} = vuefy.module({CustomizationControl});

        const customization =
          Vue.shallowRef <
          VectorCustomization >
          [
            buildingInitialCustomization,
            waterInitialCustomization,
            roadsInitialCustomization,
            groundInitialCustomization
          ];

        // A function that creates a handler for changing the color of geo objects
        const createChangeColorHandler = (controlTags, controlElements) => {
          return () => {
            let color = generateColor();
            // @ts-ignore
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{color}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                // @ts-ignore
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              customizationObject.stylers[0]['color'] = color;
            }
            customization.value = [...customization.value];
            return color;
          };
        };

        // A function that creates a handler to change the opacity of geo objects
        const createChangeOpacityHandler = (controlTags, controlElements) => {
          return (diff) => {
            let opacity = 0.5;
            // @ts-ignore
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{opacity}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                // @ts-ignore
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              if (customizationObject.stylers[0]['opacity'] === undefined) {
                customizationObject.stylers[0]['opacity'] = opacity;
              }
              opacity = +(customizationObject.stylers[0]['opacity'] + diff).toFixed(1);
              customizationObject.stylers[0]['opacity'] = opacity;
            }
            customization.value = [...customization.value];
            return opacity;
          };
        };

        // A function that creates a handler to change the scale of geo objects
        const createChangeScaleHandler = (controlTags, controlElements) => {
          return (diff) => {
            let scale = 1;
            // @ts-ignore
            if (!customization.value.some((item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags))) {
              const newTagObject = {
                tags: {any: controlTags},
                elements: controlElements,
                stylers: [{scale}]
              };
              customization.value.push(newTagObject);
            } else {
              const customizationObject = customization.value.find(
                // @ts-ignore
                (item) => JSON.stringify(item.tags.any) === JSON.stringify(controlTags)
              );
              if (customizationObject.stylers[0]['scale'] === undefined) {
                customizationObject.stylers[0]['scale'] = scale;
              }
              scale = Math.floor(customizationObject.stylers[0]['scale'] + diff);
              customizationObject.stylers[0]['scale'] = scale;
            }
            customization.value = [...customization.value];
            return scale;
          };
        };

        const app = Vue.createApp({
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };

            const onChange = (newCustomization) => {
              customization.value = newCustomization;
            };

            return {
              LOCATION,
              refMap,
              customization,
              createChangeColorHandler,
              createChangeOpacityHandler,
              createChangeScaleHandler,
              onChange,
              groundInitialCustomization,
              waterInitialCustomization,
              roadsInitialCustomization,
              buildingInitialCustomization
            };
          },
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapControls,
            MMapControl,
            CustomizationControlV
          },
          template: `
      <!-- Initialize the map and pass initialization parameters -->
      <MMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
        <!-- Add a map scheme layer with a custom customization props -->
        <MMapDefaultSchemeLayer :customization="customization"/>
        <!-- Add a shared container to the map for custom CustomizationControl's -->
        <MMapControls position="top right" orientation="horizontal">
          <MMapControl>
            <!-- Add a CustomizationControl to the map to change the appearance of the water -->
            <CustomizationControlV
              title="Water"
              :initialValues="waterInitialCustomization"
              :changeColorHandler="createChangeColorHandler(['water'], 'geometry')"
              :changeOpacityHandler="createChangeOpacityHandler(['water'], 'geometry')"
              :changeScaleHandler="createChangeScaleHandler(['water'], 'geometry')"
              :withDivider="true"
            />
            <!-- Add a CustomizationControl to the map to change the appearance of the ground -->
            <CustomizationControlV
              title="Ground"
              :initialValues="groundInitialCustomization"
              :changeColorHandler="createChangeColorHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
              :changeOpacityHandler="createChangeOpacityHandler(['landscape', 'admin', 'land', 'transit'], 'geometry')"
            />
          </MMapControl>
          <MMapControl>
            <!-- Add a CustomizationControl to the map to change the appearance of the road -->
            <CustomizationControlV
              title="Road"
              :initialValues="roadsInitialCustomization"
              :changeColorHandler="createChangeColorHandler(['road'], 'geometry')"
              :changeOpacityHandler="createChangeOpacityHandler(['road'], 'geometry')"
              :changeScaleHandler="createChangeScaleHandler(['road'], 'geometry')"
              :withDivider="true"
            />
            <!-- Add a CustomizationControl to the map to change the appearance of the building -->
            <CustomizationControlV
              title="Building"
              :initialValues="buildingInitialCustomization"
              :changeColorHandler="createChangeColorHandler(['building'], 'geometry')"
              :changeOpacityHandler="createChangeOpacityHandler(['building'], 'geometry')"
            />
          </MMapControl>
        </MMapControls>
      </MMap>
    `
        });

        app.mount('#app');
      }

      main();
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
.customizationControl {
  padding: 8px 16px;
  min-width: 200px;
  display: flex;
  flex-direction: column;
}

.customizationControl:last-child {
  padding: 0 16px 8px 16px;
}

.customizationControl__title {
  font-size: 16px;
  height: 40px;
  font-weight: 600;
  align-content: center;
  color: #050d33;
}

.customizationControl__section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 40px;
}

.customizationControl__sectionButtons {
  width: 122px;
  display: flex;
  flex-direction: row;
  gap: 4px;
}

.customizationControl_color__section {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 40px;
}

.customizationControl__sectionTitle {
  flex-basis: 50%;
  color: #050d33;
}

.customizationControl__infoBlock {
  background-color: rgba(92, 94, 102, 0.06);
  width: 50px;
  text-align: center;
  border-radius: 8px;
  align-content: center;
  color: #050d33;
  font-size: 14px;
  font-style: normal;
  font-weight: 400;
  line-height: 32px;
}

.customizationControl__btn {
  border: none;
  cursor: pointer;
  width: 32px;
  height: 32px;
  text-align: center;
  padding: 8px;
  font-size: 14px;
  font-style: normal;
  font-weight: 400;
  line-height: 16px;
  color: #050d33;
  border-radius: 8px;
  background-color: rgba(92, 94, 102, 0.06);
  transition: background-color 0.2s;
  background-repeat: no-repeat;
  background-position: 50% 50%;
}

.customizationControl__btn:disabled {
  opacity: 0.4;
}

.customizationControl_color__btn {
  border: none;
  cursor: pointer;
  width: 122px;
  height: 32px;
  padding: 8px;
  font-size: 14px;
  font-style: normal;
  font-weight: 400;
  line-height: 16px;
  color: #050d33;
  border-radius: 8px;
  text-align: left;
  background-color: rgba(92, 94, 102, 0.06);
  transition: background-color 0.2s;
  background-image: url('./shuffle.svg');
  background-repeat: no-repeat;
  background-position: 95% 50%;
}

.customizationControl__btn:hover {
  background-color: rgba(92, 94, 102, 0.12);
}

.customizationControl__btn:active {
  background-color: rgba(92, 94, 102, 0.12);
}

.customizationControl_color__btn:hover {
  background-color: rgba(92, 94, 102, 0.12);
}

.customizationControl_color__btn:active {
  background-color: rgba(92, 94, 102, 0.12);
}

.customizationControl__btn:disabled {
  cursor: not-allowed;
  background-color: rgba(92, 94, 102, 0.04);
}

.customizationControl__btn.minus {
  background-image: url('./minus.svg');
}

.customizationControl__btn.plus {
  background-image: url('./plus.svg');
}

.divider {
  margin: 8px 0 0 0;
  width: 100%;
  height: 1px;
  background-color: rgba(92, 94, 102, 0.14);
  border: none;
}
import {VectorCustomizationItem} from '@mappable-world/mappable-types/common/types/data-source-description';

export const roadsInitialCustomization: VectorCustomizationItem = {
  tags: {
    any: ['road']
  },
  elements: 'geometry',
  stylers: [
    {
      color: '#4E4E4E'
    }
  ]
};

export const waterInitialCustomization: VectorCustomizationItem = {
  tags: {
    any: ['water']
  },
  elements: 'geometry',
  stylers: [
    {
      color: '#000000'
    }
  ]
};

export const groundInitialCustomization: VectorCustomizationItem = {
  tags: {
    any: ['landscape', 'admin', 'land', 'transit']
  },
  elements: 'geometry',
  stylers: [
    {
      color: '#212121'
    }
  ]
};

export const buildingInitialCustomization: VectorCustomizationItem = {
  tags: {
    any: ['building']
  },
  elements: 'geometry',
  stylers: [
    {
      color: '#757474'
    }
  ]
};

// Function generates a random color in HEX format
export const generateColor = () => {
  return '#' + Math.floor(Math.random() * 16777215).toString(16);
};

// Create a custom control to change the customization of the map
export let CustomizationControl = null;

interface CustomizationControlProps {
  title: string;
  initialValues: VectorCustomizationItem;
  changeColorHandler?: () => void;
  changeOpacityHandler?: (diff: number) => number;
  changeScaleHandler?: (diff: number) => number;
  withDivider: boolean;
}

// Wait for the api to load to access the entity system (MMapComplexEntity)
mappable.ready.then(() => {
  class CustomizationControlClass extends mappable.MMapComplexEntity<CustomizationControlProps> {
    private _element: HTMLDivElement;
    private _detachDom: () => void;
    constructor(props: CustomizationControlProps) {
      super(props);
      this._element = this._createElement(props);
    }

    // Creates a control's DOM element based on the passed properties
    _createElement(props: CustomizationControlProps) {
      const {title, changeColorHandler, changeOpacityHandler, changeScaleHandler, initialValues} = props;

      const customizationElement = document.createElement('div');
      customizationElement.classList.add('customizationControl');

      const myControlTitle = document.createElement('div');
      myControlTitle.classList.add('customizationControl__title');
      myControlTitle.textContent = title;
      myControlTitle.id = title;

      customizationElement.appendChild(myControlTitle);

      if (changeColorHandler) {
        const colorSection = this._createColorControlSection(
          'color',
          changeColorHandler,
          initialValues.stylers[0].color
        );
        customizationElement.appendChild(colorSection);
      }

      if (changeOpacityHandler) {
        const opacitySection = this._createControlSection('opacity', changeOpacityHandler, 0.1, 0.5, [0, 1]);
        customizationElement.appendChild(opacitySection);
      }

      if (changeScaleHandler) {
        const scaleSection = this._createControlSection('scale', changeScaleHandler, 1, 1, [0, 10]);
        customizationElement.appendChild(scaleSection);
      }

      if (props.withDivider) {
        const divider = document.createElement('hr');
        divider.classList.add('divider');
        customizationElement.appendChild(divider);
      }

      return customizationElement;
    }

    _createControlSection(
      title: string,
      onClickHandler: (diff: number) => number,
      diff: number = 0.1,
      initialValue: number = 0.5,
      minMax: number[]
    ) {
      const [min, max] = minMax;
      const section = document.createElement('div');
      section.classList.add('customizationControl__section');

      const sectionTitle = document.createElement('div');
      sectionTitle.classList.add('customizationControl__sectionTitle');
      sectionTitle.textContent = title;
      section.appendChild(sectionTitle);

      const sectionButtons = document.createElement('div');
      sectionButtons.classList.add('customizationControl__sectionButtons');

      const decrementButton = document.createElement('button');
      decrementButton.classList.add('customizationControl__btn', 'minus');
      decrementButton.addEventListener('click', function () {
        const number = onClickHandler(-diff);
        this.disabled = number === min;
        (this.parentNode.lastChild as HTMLButtonElement).disabled = number === max;
        this.parentNode.children.item(1).textContent = number.toString();
      });
      sectionButtons.appendChild(decrementButton);

      const infoBlock = document.createElement('div');
      infoBlock.classList.add('customizationControl__infoBlock');
      infoBlock.textContent = initialValue.toString();
      infoBlock.id = 'info';
      sectionButtons.appendChild(infoBlock);

      const incrementButton = document.createElement('button');
      incrementButton.classList.add('customizationControl__btn', 'plus');
      incrementButton.addEventListener('click', function () {
        const number = onClickHandler(diff);
        this.disabled = number === max;
        (this.parentNode.firstChild as HTMLButtonElement).disabled = number === min;
        this.parentNode.children.item(1).textContent = number.toString();
      });
      sectionButtons.appendChild(incrementButton);

      section.appendChild(sectionButtons);

      return section;
    }

    _createColorControlSection(title: string, onClickHandler: EventListener, initialValue: string) {
      const section = document.createElement('div');
      section.classList.add('customizationControl_color__section');

      const sectionTitle = document.createElement('div');
      sectionTitle.classList.add('customizationControl_color__sectionTitle');
      sectionTitle.textContent = title;
      section.appendChild(sectionTitle);

      const sectionButton = document.createElement('button');
      sectionButton.classList.add('customizationControl_color__btn');
      sectionButton.textContent = initialValue;
      sectionButton.addEventListener('click', function (event) {
        const color = onClickHandler(event);
        this.textContent = color as unknown as string;
      });
      section.appendChild(sectionButton);

      return section;
    }

    // Handler for attaching the control to the map
    _onAttach() {
      this._detachDom = mappable.useDomContext(this, this._element, this._element);
    }

    // Handler for detaching control from the map
    _onDetach() {
      this._detachDom();
      this._detachDom = null;
      this._element = null;
    }
  }

  CustomizationControl = CustomizationControlClass;
});
:root {
}
import type {MMapLocationRequest} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.1666, 25.0628], // starting position [lng, lat]
  zoom: 15.3 // starting zoom
};