Map API Yandex 3: Подсказки при наведении на полигон

Нет, всё таки версия 3 еще сыровата для внедрения в прод. Ну или «лыжи не едут». Для решения простой задачи — отображение подсказки над полигоном, пришлось переписываться с тех поддержкой яндекса (и они даже ответили!, чем я безмерно удивлён). В результате родился скрипт, в котором требуется переопределение вызовов класса. Хм.. это определенно не то что хочется видеть для получения базовых вещей, которые в версии 2.1 работают «из коробки».

async function main() {
    await ymaps3.ready;
    ymaps3.import.registerCdn('https://cdn.jsdelivr.net/npm/{package}',['@yandex/ymaps3-hint@0.0.1']);
    const {YMap,YMapDefaultSchemeLayer,YMapFeature,YMapDefaultFeaturesLayer,YMapFeatureSource,YMapFeatureDataSource,YMapEntity} = ymaps3;
    const {YMapHint,YMapHintContext} = await ymaps3.import('@yandex/ymaps3-hint');


    map = new ymaps3.YMap(
        document.getElementById('app'),
        {location: 
            {
                center: [39.8491648, 59.2084992], 
                zoom: 9 
            },
            showScaleInCopyrights: false  //шкала масштаба рядом в логотипом
        }        
    );  
    

    const defaultScheme = new YMapDefaultSchemeLayer({});    // Добавляем рисованное изображение карты
    map.addChild(defaultScheme);

    const features = new YMapDefaultFeaturesLayer();
    map.addChild(features);

       
   //получаем список районов
    common.AjaxPost("index.php?r=site2/get-areas","").then(async areas_list=>{
        console.log("-прочитал список районов ",areas_list);
        if (areas_list.error==false){
            for (var i = 0; i < areas_list.result.length; i++) {                        
                await common.AjaxPost("areas/"+areas_list.result[i].poly_filename,"aa").then(res2=>{
                    console.log(areas_list.result[i].name);                    
                    res2=common.ReverseCoors(res2);
                    console.log(areas_list.result[i].poly_fill_color);
                    const polygon = new ymaps3.YMapFeature({
                        geometry: {
                            type: 'Polygon',
                            coordinates: res2
                        },
                        properties: {
                          hint: areas_list.result[i].name+" район"
                        },                        
                        style: {
                                stroke: [
                                    {
                                        color: '#196DFF99',
                                        dash: [5, 10],
                                        width: 3
                                        
                                    }
                                ],
                                fillOpacity: 0.2,
                                fill: "#"+areas_list.result[i].poly_fill_color
                            }
                    });
                    features.addChild(polygon);
                });                 
            };        
        };
    });    
    
      const hint = new YMapHint({
        layers: [features.layer],
        hint: object => object?.properties?.hint
      });
      map.addChild(hint);

      hint.addChild(
        new(
          class MyHint extends YMapEntity {
            _onAttach() {
                this._element =document.createElement('div');
                this._element.className ='hint';
                this._detachDom =ymaps3.useDomContext(this,this._element);
                this._watchContext(
                YMapHintContext,
                    () => {
                      const context =this._consumeContext(YMapHintContext);
                      this._element.textContent =(context?.hint || '');
                      console.log(this._element);
                    }, {
                      immediate: true
                    }
                  );
                }
            _onDetach() {this._detachDom();}
          }
        )()
      );

};    

window.map = null;            
main();

API MAP Yandex 3: Отрисовка районов на карте области

Пока на работе выдалась небольшая творческая пауза, решил потыкать API версии 3, который приходит на смену версии 2.1 И рано или поздно как я понимаю версия 2.1 будет отключена. А проекты на «старой» версии есть.

Так вот, что не понравилось: все примеры в «песочнице», на typescript, а не на чистом javascript. Ну и полностью всё конечно поломано, т.е. не получится просто взять старый код, и чуть адаптировать — нужно всё переписывать. Вплоть до того что, они даже координаты «перевернули» местами, пришлось для того чтобы наложить полигоны районов со старой карты на новую, их «переворачивать».

Ну собственно, вот код,

<script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
<script src="https://api-maps.yandex.ru/v3/?apikey=уууу&lang=ru_RU" type="text/javascript"></script>
<div id="app"></div> 

<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel" src="ts/variables.ts"></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel" src="ts/common.ts"></script>
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel" src="ts/receipts.ts"></script>
        
<script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
       
import * as common from '../common';    

async function main() {
    await ymaps3.ready;
    const {YMap, YMapDefaultSchemeLayer,YMapFeature,YMapDefaultFeaturesLayer} = ymaps3;
    map = new YMap(
        document.getElementById('app'),
        {location: 
            {
                center: [39.8491648, 59.2084992], 
                zoom: 9 
            },
            showScaleInCopyrights: true
        },
        [
            new YMapDefaultSchemeLayer({}),
            new YMapDefaultFeaturesLayer({})
        ],

        
    );
       

    //получаем список районов
    common.AjaxPost("index.php?r=site2/get-areas","").then(async areas_list=>{
        console.log("-прочитал список районов ",areas_list);
        if (areas_list.error==false){
            for (var i = 0; i < areas_list.result.length; i++) {                        
                await common.AjaxPost("areas/"+areas_list.result[i].poly_filename,"aa").then(res2=>{
                    console.log(areas_list.result[i].name);                    
                    res2=common.ReverseCoors(res2);
                    console.log(areas_list.result[i].poly_fill_color);
                    const polygon = new YMapFeature({
                        geometry: {
                            type: 'Polygon',
                            coordinates: res2
                        },
                        properties:{
                            hintContent: areas_list.result[i].name   
                        },                                                
                        // Описание тут: https://yandex.ru/maps-api/docs/js-api/ref/index.html#interface-drawingstyle
                        style: {
                                stroke: [
                                    {
                                        color: '#196DFF99',
                                        dash: [5, 10],
                                        width: 3
                                        
                                    }
                                ],
                                fillOpacity: 0.2,
                                fill: "#"+areas_list.result[i].poly_fill_color
                            }
                    });
                    console.log(polygon);
                    map.addChild(polygon);                    
                    
                });                 
            };        
        };
    });
    
    
};    

window.map = null;            
main();
</script>     

common.ts:

/**
Получаем информацию об очертаниях районов (округов)
*/
export async function AjaxPost(url : string,payload: any): any {
  try {
    const response = await fetch(url, {
      method: 'POST', 
      headers: {'Content-Type': 'application/json; charset=UTF-8' },
      body: JSON.stringify(payload) 
    });
    if (!response.ok) {
      throw new Error(HTTP error! Status: ${response.status});
    }
    return response.json();
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

/**
Сделать паузу в милисекундах
*/
export async function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

/**
Перевернуть все координаты в массиве
*/
export function ReverseCoors(coors : any): any {
  
    for (var ii = 0; ii < coors.length; ii++) {                                
         for (var iii = 0; iii < coors[ii].length; iii++) {                                
            var x=0;
            var y=0;                        
            x=coors[ii][iii][0];
            y=coors[ii][iii][1];
            coors[ii][iii][1]=x;
            coors[ii][iii][0]=y;                             
         };
    };
    return coors;
}

Grafana: выборка в запросе диапазона дат из переменных

Если требуется обработать и показать данные по выбранному диапазону, то необходимо использовать в запросе переменные $__from и $__to. Однако стоит учитывать, что они в запросе встают как время с эпохи unix. А именно большое длинное число. Поэтому если например в БД у даты типа дата, без времени, то приходится использовать преобразование вида:

EXTRACT(EPOCH from agreements_dt)*1000

Чтобы из даты вида 2026-01-01 получить 1767214800000. Ну и соответственно в запросе:

select to_char(date_trunc('day', dt), 'YYYY-MM-DD'),count(*) as "Великоустюгское территориальное отделение" from (
                    select 
                            date_trunc('month', agreements_dt)::date as dt
                    from 
                            ls 
                    where EXTRACT(EPOCH from agreements_dt)*1000 between $__from and $__to and owner='000000003' and agreements_dt is not null
                    ) as agr
            group by dt 
            order by dt	asc

Max: Исключение повторных нажатий кнопок до ответа сервера

Как бы не ругались некоторые товарищи на Habr, что «разрабатывать под Max, себя не уважать», но тем не менее не все имеют этот выбор 😉 Так что проблемы решать таки нужно..

Итак, задача: между нажатием кнопки с кэлбеком и ответом сервера происходит некоторая пауза. Иногда в несколько секунд. Пользователи не всегда дожидаются ответа, и нажимают кнопки по нескольку раз.

Решение: т.к. на основной ответ сервера требуется некоторое время на его подготовку, то перед «настоящим» ответом сервера, отправим «промежуточный», который сообщит пользователю «Подождите ответа сервера», а заодно скроет все кнопки из предыдущего сообщения пользователя, чтоб не было соблазна их нажимать. Код получается примерно такой:

...
    if (isset($in->update_type)){
        if ($in->update_type=="message_callback"){
          $params=[];
          $params["callback_id"]=$in->callback->callback_id;
          $data=[];
          $data["message"]["attachments"]=[];
          $data["notification"]="Подождите ответа сервера...";
          $res=$Max->Reqwest("answers",$params,$data,"POST");    
        };
        if (($in->update_type=="message_created") or ($in->update_type=="message_callback") or ($in->update_type=="bot_started")){                       
          $res=$Api1c->reqwest("MaxCallback2",["body"=>$body]);  // пересылаем всё что пришло в 1С  и получаем ответ
          if ($res==null){
            $params=[];  
            $params["chat_id"]=$in->message->recipient->chat_id;
            $data=[];
            $data["text"]="Временная ошибка серверера. Попробуйте позже.";    
            $data["format"]= "html";            
                $buttons=[];                
                    $button=[];
                    $button["type"]="callback";
                    $button["text"]="Перейти в главное меню";
                    $button["payload"]="Go2MainMenu";                
                $buttons[]=$button;                                
                    $attachment=[];
                    $attachment["type"]="inline_keyboard";
                    $attachment["payload"]["buttons"]=[];
                    $attachment["payload"]["buttons"][]=$buttons;                                                     
            $data["attachments"]=[];    
            $data["attachments"][]=$attachment;
            $res=$Max->Reqwest("messages",$params,$data,"POST");    
            die();  
          };
    // если 1С вернула ошибку то её и показываем пользователю
          if ($res->error==true){
            $params=[];  
            if (isset($in->message)){$params["chat_id"]=$in->message->recipient->chat_id;};
            if (isset($in->chat_id)){$params["chat_id"]=$in->chat_id;};        
            $data=[];
            $data["text"]=$res->result;    
            $data["format"]= "html";            
                $buttons=[];                
                    $button=[];
                    $button["type"]="callback";
                    $button["text"]="Перейти в главное меню";
                    $button["payload"]="Go2MainMenu";                
                $buttons[]=$button;                                
                    $attachment=[];
                    $attachment["type"]="inline_keyboard";
                    $attachment["payload"]["buttons"]=[];
                    $attachment["payload"]["buttons"][]=$buttons;                                                     
            $data["attachments"]=[];    
            $data["attachments"][]=$attachment;
            
            $res=$Max->Reqwest("messages",$params,$data,"POST");          
            die();  
          };
          // Что пришло из 1С, то и отдаём в сообщения пользователю..
          $params=$res->result->params;
          $data=$res->result->data;
          $res=$Max->Reqwest("messages",$params,$data,"POST");          
          die();
...

Javascript: формирование csv файла

Задача: на чистом javascript сформировать и сохранить из браузера файл формата csv, адекватно открывающийся табличным редактором (например OnlyOffice)

Решение:

rows=[];
rows.push(["aa","bb"]);

let csvContent = "data:text/csv;charset=utf-8," + rows.map(e => e.join(",")).join("\n");
var encodedUri = encodeURI(csvContent);
var link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "download.csv");
document.body.appendChild(link); 
link.click();

Что тут происходит: заполняется некий массив, далее создаётся ссылка в DOM, и в элемент ссылки записываются данные массива. Далее эмулируется нажатие собственно на эту ссылку.

1 2 3 57