mirror of https://gitee.com/antv-l7/antv-l7
Feat: L7 Component 完备性升级 (#1391)
* feat: 1.BaseControl 升级 * build: 将 css 迁移至 less 中 * feat: 1.BaseControl 升级 * fix: l7-utils 添加对 lodash 的依赖 * feat: 1.修复控件布局问题 2.拆分 BaseControl 样式文件 3.新增 Control 文档 * Feat yanxiong merge (#1380) * feat: 1.新增扫光中心sweepCenter 2.完善扫光Layer配置API和demo * feat: 1.新增wind图层 * fix: 1.eslint问题 * feat: 1.新增WindLayer 文档和Example * fix: 1.风场图层文档新增示例图片 * feat: 1.新增轨迹/围墙Example * feat: 1.新增线图层Demo * chore: 工程配置优化 (#1278) * docs: 文档的相关配置 * chore: package json 测试覆盖 * test(source): test * fix: test ci * chore: ci * chore: ci add worker build * chore: glsl 支持热更新 * chore: test cover view * chore: ci test * feat: raisingHeight/heightFixed 补全 (#1280) * feat: 去除多余 demo、提取图层共用类型定义 * feat: 点图层支持 heightfixed * feat: point fillImage 支持 raisingHeight * feat: 点图层 fill、fillImage 补全 raigingHeight\heightFixed * feat: 点图层 point image 完善对 raisingHeight\heightFixed 的支持 * feat: 修改类型定义 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 优化图片的显示效果 (#1282) * feat: 优化栅格瓦片的显示效果 * style: lint style * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 1.修复控件布局问题 2.拆分 BaseControl 样式文件 3.新增 Control 文档 * fix: 还原 eslintrc 变更 * Chore test preCommit 增加测试用例校验 (#1281) * docs: 文档的相关配置 * chore: package json 测试覆盖 * test(source): test * fix: test ci * chore: ci * chore: ci add worker build * chore: glsl 支持热更新 * chore: test cover view * chore: ci test * chore: precommit 增加 test 校验 * chore: remove stroybook lint add dumo demo lint * chore: pre-commit build 校验 * chore: update ci 触发 * chore: lint 相关配置 * fix: 还原 eslintrc 变更 * feat: 1.新增 ButtonControl 基类 2.完成 Fullscreen UI 开发 * chore: lint check (#1284) * docs: 文档的相关配置 * chore: package json 测试覆盖 * test(source): test * fix: test ci * chore: ci * chore: ci add worker build * chore: glsl 支持热更新 * chore: test cover view * chore: ci test * chore: precommit 增加 test 校验 * chore: remove stroybook lint add dumo demo lint * chore: pre-commit build 校验 * chore: update ci 触发 * chore: lint 相关配置 * chore: 合并ci 任务 * fix: lint error * fix: lint command * chore: ci test & lint * feat: 新增枚举类型的色带 (#1283) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * Chore: 修复部分lint 警告,pre-commit 移除build命令 (#1285) * chore: lint unuse * chore: pre commit command * chore: pre-commit 去除build 命令 * feat: add test case (#1286) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 完善 ButtonControl 基类 * feat: 修改 simple line 的网格构建和渲染方式 (#1288) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 新增 popperControl 控件基类 * feat: 完成 popper UI测逻辑 * Chore: lint warn 移除未使用的import (#1287) * chore: lint unuse * chore: pre commit command * chore: pre-commit 去除build 命令 * fix: 修复unuse import * feat: 1.完善 popper 自动判定位置能力 * feat: 1. SelectControl 开发完成 * fix: 修复丢失代码 * chore: 去除无用依赖 * fix: 修复 PopperControl trigger 为 hover 时,气泡消失异常问题 * fix: 去除选中后关闭气泡的配置项 * fix: 1. Popper 参数 closeOther => unique 2. 补充方法注释 * feat: 1.新增 PopperControl 和 ButtonControl 的 setOptions 方法 2.完成 Logo 组件的升级开发 * feat: 完成 fullscreen 组件的开发 * feat: 适量图层支持透传 transfrom (#1294) * feat: 矢量图层支持 join/transfrom 透传 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * test: 测试用例支持 new scene 对象 (#1291) * chore: lint unuse * chore: pre commit command * chore: pre-commit 去除build 命令 * fix: 修复unuse import * test: 增加gl 模拟能力 * fix: lint error * chore: 提取测试utils 为单独包 * chore: 调整测试覆盖率值 * feat: support simple line vector layer (#1295) * feat: 矢量图层支持 join/transfrom 透传 * style: lint style * feat: support simple line vector tile layer Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 提供线图层偏移点位计算的通用方法 (#1293) * feat: 增加线偏移点的计算 * style: lint style * feat: 支持对 greatcircle 的偏移点位的计算 * feat: 完善弧线偏移点的 featureId 过滤 * style: lint style * style: lint style * feat: 支持对普通线偏移点的计算支持 * feat: 增加线图层偏移点位计算的测试用例 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.23 -> 2.9.24 (#1296) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: change publish config (#1297) * chore: update version 2.9.23 -> 2.9.24 * chore: change test-utils packages.json * chore: change @antv/l7-test-utils into private Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 1.完成截图组件开发 * feat: 1.完成定位组件的开发 * feat: 兼容不同amapjs 加载方式 (#1265) * feat: 1.完成定位组件的开发 * feat: 新增 LayerControl 组件 * chore: 删除无用 Example * fix: 修复 AMap 初始化问题 * fix: 1.锁死 screenfull 版本 2.修复 fullscreen 的按钮默认文本和样式问题 * feat: 1. Fullscreen 构造器中对当前环境是否支持全屏进行监测 * feat: 1.LayerService 中图层发生变动时触发 layerChange 事件 * feat: LayerControl 监听 LayerService 图层发生变更时的事件 * fix: 修复加载高德地图判断加载导致的缓存问题 (#1301) * fix: 去除 amap 加载的判断 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 矢量图层支持 geojson-vt (#1302) * feat: 矢量图层支持 geojson-vt * feat: 优化 parser 类型获取、内置 sourceLayer * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * docs: scale demo & IE 兼容 (#1304) * docs: 增加demo,关闭 any lint * docs: demo& ie 兼容问题 * chore: dev-build * feat: Scale 宽度发生变化时添加渐变效果 * feat: 控件 MapStyleControl => MapTheme * fix: heatmap render error (#1307) * fix: 修复热力图渲染失效 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: father 配置 glsl 内联 (#1305) * docs: 增加demo,关闭 any lint * docs: demo& ie 兼容问题 * chore: dev-build * chore: father 配置 glsl 内联 * chore: update version 2.9.24 -> 2.9.25 (#1308) * fix: 修复矢量图层判断错误 * chore: update version 2.9.24 -> 2.9.25 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 新增 MapTheme 切换主题控件 * test: 新增marker测试用例 (#1309) Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> * feat: source 支持 json 下新增 geometry 字段 (#1312) * feat: source 支持 json 下 geometry 解析 * refactor: 优化循环逻辑 * fix: 解决聚合情况下清空图层点数据未清空BUG (#1311) * fix: 修复 feature scale 可能存在的 source 取值问题 (#1315) * fix: 修复 featureScale 错误 * style: lint style * fix: 修复 feature scale 映射问题 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 修复栅格图片瓦片的混合问题 (#1316) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * fix: 修复栅格图片瓦片的混合问题 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 新增 Control 相关单测 * feat: 矢量文本计算优化、性能优化 (#1310) * feat: 新增测试瓦片图层 * style: lint style * feat: 矢量文本图层性能优化 * chore: change tiletestlayer demo * style: lint style * feat: 封装 TileDebugLayer 的source 模块,优化图层默认数据的配置,测试图层样式调整 * style: line style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * [chores] Remove unused dependencie (#1314) * feat: 自定义图层 - marker&markerLayer - 多个marker节点性能优化 (#1300) * chore: 新增marker开发demo * style: 增加格式化空格 * marker性能优化 - 缓存计算变量:ignore (#1298) Co-authored-by: linlb <linlb@homeking365.com> * feat: 自定义图层 - marker&markerLayer - markerLayer多节点kmarker性能优化 * feat: 添加demo * feat: 删除冗余代码 Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> Co-authored-by: bolry <909559682@qq.com> Co-authored-by: linlb <linlb@homeking365.com> * feat: 新增 MouseLocation 组件 * chore: add demo (#1319) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: add demo * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.25 -> 2.9.26 (#1318) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: update version 2.9.25 -> 2.9.26 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 修复光标经纬度不更新问题 * feat: 1.完善 Zoom 组件 2.去除旧 LayerControl 对应样式代码 * feat: 1.完善 Scale 组件升级 * test: 1.完善 LayerControl 单测 * feat: 调整 Popup 目录结构 * fix: 修复弹框类型组件隐藏时未把弹框隐藏问题 * chore: 增加覆盖率Ci (#1323) * chore: 增加覆盖率Ci * chore: jest locv * fix: layer 单测样例 * fix: SelectControl 样式字体缩小 * fix: 修复 ExportImage 参数 imageType 为 jpg 不生效问题 * fix: 完善 setOptions 方法 * feat: 完成 Popup 的基础拆分 * feat: 气泡 Popup 新增互斥开关 autoClose * feat: 气泡 Popup 新增配置按 ESC 关闭 Popup * feat: 气泡 Popup 支持设置自定义 className 和 style * feat: 气泡 Popup 支持设置自定义 className 和 style * feat: 优化 Popup less 样式 * fix: 修复gaodev2下MarkerLayer清除有误问题 (#1324) * fix: markerLayer在GaodeMapV2做底图时removeMarkerLayer视图图层层级改变时会复原被删除的图层 (#1322) 去除clear方法中调用注销监听事件 * fix: 修复gaodev2下layer清除有误问题 Co-authored-by: bolry <909559682@qq.com> Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> * docs: remove website demos (#1328) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * docs: remove demos Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 完善 Popup 能力和代码注释 * docs: 移除地球模式点图层demo (#1330) Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> * feat: 完善 Popup setOptions 方法 * test: 补充 Popup 单测文件 * feat: 初始化 LayerPopup * feat: 气泡 Popup 新增气泡标题 title 配置 * fix: 默认source 配置 (#1331) * fix: 修改source 为空的问题 * chore: 添加默认渲染 * feat: 完成 LayerPopup 组件的开发 * test: 补充 LayerPopup 相关的单测 * fix: source empty err (#1332) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * fix: fix empty source bug * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 自定义图层 - markerLayer - 修复图层执行clear后聚合能力失效问题 (#1333) Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> * docs: website remove mapbox demos (#1334) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: website remove mapbox demos Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * Feat custom map (#1326) * chore(map): 地图模块重构 * feat: lealetMap for l7 * chore: amap baseservice 重构 * fix: 修改amap 样式 * fix: 单词拼写 * docs: remove website docs (#1336) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: website remove mapbox demos Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 将 iconfont 引入方式从 css => svg * build: worker 内联打包添补 (#1338) * feat: 1.场景 Scene 新增 boxSelect 能力 * feat: 组件层适配 gatsby 改造 * docs: 新增 Control 和 Logo 控件文档 * fix: 按照交互稿修复组件样式 * feat: 瓦片图层、地图图层渲染性能/体验优化 (#1329) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: 优化简单矢量线瓦片的计算 * feat: 优化简单线图层的网格计算构建 * style: lint style * style: lint style * feat: 矢量瓦片更新渲染优化 * fix: 修复 tileLayer 重复创建导致的瓦片更新错误 * feat: 优化矢量瓦片图层本身的性能 * style: lint style * feat: 优化 reRender 的调用 * feat: 合并瓦片销毁时的重绘 * feat: 去除矢量文本图层的 remapping 映射 * feat: 网格构建异步改造修复 * feat: 瓦片渲染流程优化 * feat: 通用瓦片流程的优化(主线程阻塞优化) * feat: 补全瓦片更新触发 * feat: 默认顶点属性构建的优化 * style: lint style * feat: 调整矢量点 uniform 参数 * chore: 去除矢量图层对偏移坐标的支持(不统一) * style: lint style * feat: 合并不同瓦片图层触发的重绘 * style: lint style * chore: 调整瓦片代码结构 * feat: 矢量图层初始化优化 * chore: code clean * feat: 矢量图层地图绘制数据属性映射优化 * chore: lint style * feat: 矢量图层地图绘制初始化优化 * feat: maskLayer 初始化优化、debugtestLayer 默认为 basemap 模式 * chore: style * feat: 绘制指令优化 - picking drawCommand * style: lint style * feat: 优化矢量图层初始化资源的创建 * feat: 简化矢量瓦片图层加载完成触发的重绘 * chore: 统一地图图层的样式写法 color、size * style: lint style * style: lint style * style: lint style * feat: 瓦片渲染执行优化 Please enter the commit message for your changes. Lines starting * feat: 优化拾取渲染 * style: lint style * chore: style change * style: lint style * feat: layer plugin list clean * style: lint style * feat: 合并shader 使用 * style: lint style * feat: 优化 source 计算 * feat: 去除 source 中对创建 tileset 的多余判断 * chore: 优化代码写法 * feat: 地图瓦片图层类型定义优化 * chore: data clean * style: lint style * style: lint style * feat: debugLayer add basemap attr * chore: demo 调整 * feat: 优化瓦片图层的渲染 * feat: 修改瓦片显示更新 * fix: 修复动画模式传值导致的显示效果问题 * fix: 修复 mapbox version 设置错误的问题 * fix: 修复 CanvasLayer render * style: lint style * chore: clean citybuilding demo * style: lint style * chore: clean point simple code * chore: clean point text iconfont * chore: clean polygon fill code * style: lint style * chore: clean billboard demo * chore: clean polygon water/ocean demos * chore: clean point radar demos * chore: clean point normal demos * chore: clean wind layer demos * chore: clean demos * feat: clean demos & fix half line insert attri * style: lint style * chore: clean mask demo * chore: adjust website demo * style: lint style * chore: clean worker demos * style: lint style * fix: 修复箭头顶点重复插入的问题 * fix: 修复线图层弧线的纹理分布 * chore: website demos code clean * chore: layerService/renderlayers move clear place * feat: 优化图片瓦片的颜色映射 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: fix spelling mistake (#1344) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: fix spelling mistake * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 瓦片性能和代码优化 (#1347) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: 优化数据栅格瓦片的渲染、瓦片管理流程完善 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: add Hill shade demo (#1349) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: add hillShade demo - openlayers Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 设置瓦片多服务重构、支持加载多文件 (#1350) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: 优化简单矢量线瓦片的计算 * feat: 优化简单线图层的网格计算构建 * style: lint style * style: lint style * feat: 矢量瓦片更新渲染优化 * fix: 修复 tileLayer 重复创建导致的瓦片更新错误 * feat: 优化矢量瓦片图层本身的性能 * style: lint style * feat: 优化 reRender 的调用 * feat: 合并瓦片销毁时的重绘 * feat: 去除矢量文本图层的 remapping 映射 * feat: 网格构建异步改造修复 * feat: 瓦片渲染流程优化 * feat: 通用瓦片流程的优化(主线程阻塞优化) * feat: 补全瓦片更新触发 * feat: 默认顶点属性构建的优化 * style: lint style * feat: 调整矢量点 uniform 参数 * chore: 去除矢量图层对偏移坐标的支持(不统一) * style: lint style * feat: 合并不同瓦片图层触发的重绘 * style: lint style * chore: 调整瓦片代码结构 * feat: 矢量图层初始化优化 * chore: code clean * feat: 矢量图层地图绘制数据属性映射优化 * chore: lint style * feat: 矢量图层地图绘制初始化优化 * feat: maskLayer 初始化优化、debugtestLayer 默认为 basemap 模式 * chore: style * feat: 绘制指令优化 - picking drawCommand * style: lint style * feat: 优化矢量图层初始化资源的创建 * feat: 简化矢量瓦片图层加载完成触发的重绘 * chore: 统一地图图层的样式写法 color、size * style: lint style * style: lint style * style: lint style * feat: 瓦片渲染执行优化 Please enter the commit message for your changes. Lines starting * feat: 优化拾取渲染 * style: lint style * chore: style change * style: lint style * feat: layer plugin list clean * style: lint style * feat: 合并shader 使用 * style: lint style * feat: 优化 source 计算 * feat: 去除 source 中对创建 tileset 的多余判断 * chore: 优化代码写法 * feat: 地图瓦片图层类型定义优化 * chore: data clean * style: lint style * style: lint style * feat: debugLayer add basemap attr * chore: demo 调整 * feat: 优化瓦片图层的渲染 * feat: 修改瓦片显示更新 * fix: 修复动画模式传值导致的显示效果问题 * fix: 修复 mapbox version 设置错误的问题 * fix: 修复 CanvasLayer render * style: lint style * chore: clean citybuilding demo * style: lint style * chore: clean point simple code * chore: clean point text iconfont * chore: clean polygon fill code * style: lint style * chore: clean billboard demo * chore: clean polygon water/ocean demos * chore: clean point radar demos * chore: clean point normal demos * chore: clean wind layer demos * chore: clean demos * feat: clean demos & fix half line insert attri * style: lint style * chore: clean mask demo * chore: adjust website demo * style: lint style * chore: clean worker demos * style: lint style * fix: 修复箭头顶点重复插入的问题 * fix: 修复线图层弧线的纹理分布 * chore: website demos code clean * chore: layerService/renderlayers move clear place * chore: update version 2.9.26 -> 2.9.27-alpha.0 * chore: update version 2.9.27-alpha.0 -> 2.9.27-alpha.3 * feat: 支持 multi raster tile * feat: add hillshade demo * feat: 瓦片服务地址设置重构、多文件请求代码优化 * chore: 案例瓦片多服务用法修改 * chore: 类型定义优化 & api rename Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: demo address err (#1351) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * docs: 完善官方文档 * feat: 优化瓦片金字塔的计算 (#1355) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: opmitize tile cal * style: lint style * chore: update api name Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 修复更新图层属性 bug (#1356) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: add fix demo * fix: 修复样式更新的状态问题 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.27 -> 2.9.28 (#1357) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: update version 2.9.27 -> 2.9.28 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 修复 layer model 更新时候存在闪烁的问题 (#1358) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * fix: 优化 layer model 的更新链路,避免闪烁 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.28 -> 2.9.29 (#1360) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * docs: change website url * chore: update version 2.9.28 -> 2.9.29 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: add l7-three father umd build config (#1361) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: add l7-three father/umd * feat: remove depecenied lib Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.29 -> 2.9.30 (#1363) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: update version 2.9.29 -> 2.9.30 * chore: add three ignorefile * chore: update version 2.9.30 -> 2.9.31 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 瓦片事件监听失效 (#1365) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * fix: 修复瓦片图层事件失效 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * docs: 完善 L7 组件文档和示例 * feat: 合并代码 * feat: 合并代码 * fix: 修复代码合并丢失代码 * fix: 修改 gcoord 的依赖类型 * fix: 修复 fillShade 文件名称大小写问题 * fix: 修复拾取高亮状态异常 (#1368) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * fix: 修复拾取高亮状态异常 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: font 加载逻辑,添加font 加载完成事件 (#1364) * fix: 文件名大小写 * fix: font load 逻辑 * fix: font 加载问题 * chore: update version 2.9.31 -> 2.9.32 (#1373) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * chore: update version 2.9.31 -> 2.9.32 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 修复 setData 引发的高德2 图层抖动问题 (#1376) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * Fix: css 打包方式内联 (#1375) * fix: 文件名大小写 * fix: font load 逻辑 * fix: font 加载问题 * fix: lint error * fix: css lib 打包配置 * docs: 补充 L7 Component 文档相关 * fix: 修复当前 lerna 打包顺序导致报错的问题 * fix: 修复 source 模块 parser 为 Json geometry 情况数据拾取问题 (#1378) * chore: publish alpha version 2.9.32-alpha.2 * feat: 栅格瓦片的多波段计算 (#1367) * fix: 修复 featureScale 错误 * style: lint style * fix: remove featureScalePlugin async * feat: 增加多波段瓦片的 operation 操作 * style: lint style * feat: 增加波段指定逻辑 * style: lint style * feat: 优化多波段的请求操作路径代码 * style: lint style * feat: multi raster layer support express operation 1.0 * style: lint style * feat: 增加栅格图层的多波段分析能力 * chore: add raster layer demo * style: lint style * style: lint style * style: lint style * style: lint style * style: lint style * feat: 补充栅格数据表达式计算 * chore: 调整栅格计算方法的位置 * chore: 优化栅格计算代码结构 * chore: 优化数据栅格代码 * style: lint style * style: lint style * style: lint style * feat: band operation handle empty data * feat: 支持彩色多通道栅格 * feat: 数据栅格瓦片支持彩色多通道波段计算 * style: lint style * style: lint style * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 解决 turf 依赖问题 * feat: 升级版本号 * feat: 升级版本号 * chore: update version 2.9.32 -> 2.9.33 (#1379) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 修复 IControlOption 报错问题 * fix: css lib (#1381) * chore: 更新版本 2.9.34 (#1383) Co-authored-by: 象数 <zhengxue.lzx@antgroup.com> * fix: 优化多图层 setData 效果不同步的现象 (#1384) * fix: 优化多图层 setData 效果不同步的现象 * style: lint style Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 新增矢量瓦片类型 - 掩模图层 (#1382) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.34 -> 2.9.35 (#1385) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * fix: 修复设置地图中心点引发的问题 (#1386) * fix: 修复设置地图中心点引发的问题 * style: lint style * feat: 补充空值时图层绘制采用默认地图中心点 Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * chore: update version 2.9.35 -> 2.9.36 (#1387) Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> * feat: 去除 Control 中 abstract 的描述 Co-authored-by: yanxiong <oujinhui.ojh@antgroup.com> Co-authored-by: @thinkinggis <lzx199065@gmail.com> Co-authored-by: YiQianYao <42212176+yiiiiiiqianyao@users.noreply.github.com> Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> Co-authored-by: qxang <qxkang@126.com> Co-authored-by: Dreammy23 <caomengyuan2015@163.com> Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> Co-authored-by: lvisei <yunji.me@outlook.com> Co-authored-by: MarkLei7 <33211377+MarkLei7@users.noreply.github.com> Co-authored-by: Jeffrey <color.dove@gmail.com> Co-authored-by: bolry <909559682@qq.com> Co-authored-by: linlb <linlb@homeking365.com> Co-authored-by: 象数 <zhengxue.lzx@antgroup.com> * feat: 去除 selectBoxClassName 属性 * feat: 将 Control Option 接口定义放到 @antv/l7-component 中 * fix: 将 l7-scene 中的 turf 方法转移到 l7-utils 中 * fix: 添加对 BoxSelect 选择归宿子包的 TODO * feat: 替换 Control Iconfont 图标 * feat: 组件中涉及到的图标支持传输 Fragment 类型 * feat: 组件层 Control 更新配置时需要传入默认配置 Co-authored-by: yanxiong <oujinhui.ojh@antgroup.com> Co-authored-by: @thinkinggis <lzx199065@gmail.com> Co-authored-by: YiQianYao <42212176+yiiiiiiqianyao@users.noreply.github.com> Co-authored-by: shihui <yiqianyao.yqy@alibaba-inc.com> Co-authored-by: qxang <qxkang@126.com> Co-authored-by: Dreammy23 <caomengyuan2015@163.com> Co-authored-by: Dreammy23 <echo.cmy@antgroup.com> Co-authored-by: lvisei <yunji.me@outlook.com> Co-authored-by: MarkLei7 <33211377+MarkLei7@users.noreply.github.com> Co-authored-by: Jeffrey <color.dove@gmail.com> Co-authored-by: bolry <909559682@qq.com> Co-authored-by: linlb <linlb@homeking365.com> Co-authored-by: 象数 <zhengxue.lzx@antgroup.com>
This commit is contained in:
parent
050da8bc72
commit
d4c1921b1e
|
@ -41,4 +41,4 @@ module.exports = {
|
|||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 导出图片
|
||||
order: 9
|
||||
---
|
||||
|
||||
<code src="./exportImage.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,86 @@
|
|||
import { GaodeMapV2, Scene, ExportImage, PointLayer } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | undefined>();
|
||||
const [imgSrc, setImgSrc] = useState('');
|
||||
const [control, setControl] = useState<ExportImage | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMapV2({
|
||||
style: 'normal',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
WebGLParams: {
|
||||
preserveDrawingBuffer: true,
|
||||
},
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newControl = new ExportImage({
|
||||
onExport: (base64) => {
|
||||
setImgSrc(base64);
|
||||
},
|
||||
});
|
||||
newScene.addControl(newControl);
|
||||
setControl(newControl);
|
||||
|
||||
fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const pointLayer = new PointLayer({
|
||||
autoFit: true,
|
||||
})
|
||||
.source(data)
|
||||
.shape('circle')
|
||||
.size('mag', [1, 25])
|
||||
.color('mag', (mag) => {
|
||||
return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
|
||||
})
|
||||
.active(true)
|
||||
.style({
|
||||
opacity: 0.3,
|
||||
strokeWidth: 1,
|
||||
});
|
||||
newScene.addLayer(pointLayer);
|
||||
setScene(newScene);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.setOptions({
|
||||
imageType: 'jpeg',
|
||||
});
|
||||
}}
|
||||
>
|
||||
设置导出格式为 JPG
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<div>截图展示:</div>
|
||||
<img src={imgSrc} style={{ width: 200, height: 100 }} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 全屏
|
||||
order: 8
|
||||
---
|
||||
|
||||
<code src="./fullscreen.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,38 @@
|
|||
import { GaodeMap, Scene, Fullscreen } from '@antv/l7';
|
||||
import React from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'normal',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
scene.on('loaded', () => {
|
||||
const newFullscreen = new Fullscreen();
|
||||
scene.addControl(newFullscreen);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 显示/隐藏
|
||||
order: 3
|
||||
---
|
||||
|
||||
<code src="./hide.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,59 @@
|
|||
import { GaodeMap, PositionType, Scene, MapTheme } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const POSITION_LIST = Object.values(PositionType);
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [zoom, setZoom] = useState(() => {
|
||||
return new MapTheme({
|
||||
position: 'topleft',
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addControl(zoom);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
zoom.show();
|
||||
}}
|
||||
>
|
||||
展示
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
zoom.hide();
|
||||
}}
|
||||
>
|
||||
隐藏
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 图层控制
|
||||
order: 13
|
||||
---
|
||||
|
||||
<code src="./layerControl.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,148 @@
|
|||
import {
|
||||
GaodeMapV2,
|
||||
Scene,
|
||||
LayerControl,
|
||||
ILayer,
|
||||
PointLayer,
|
||||
LineLayer,
|
||||
PolygonLayer,
|
||||
} from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [layers, setLayers] = useState<ILayer[]>([]);
|
||||
const [scene, setScene] = useState<Scene | undefined>();
|
||||
const [newLayer, setNewLayer] = useState<ILayer | null>(null);
|
||||
const [control, setControl] = useState<LayerControl | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMapV2({
|
||||
style: 'normal',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newLayers: ILayer[] = [];
|
||||
window.Promise.all([
|
||||
fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const pointLayer = new PointLayer({
|
||||
name: '点图层',
|
||||
autoFit: true,
|
||||
})
|
||||
.source(data)
|
||||
.shape('circle')
|
||||
.size('mag', [1, 25])
|
||||
.color('mag', (mag) => {
|
||||
return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
|
||||
})
|
||||
.active(true)
|
||||
.style({
|
||||
opacity: 0.3,
|
||||
strokeWidth: 1,
|
||||
});
|
||||
setNewLayer(pointLayer);
|
||||
newScene.addLayer(pointLayer);
|
||||
}),
|
||||
fetch(
|
||||
// 'https://gw.alipayobjects.com/os/bmw-prod/1981b358-28d8-4a2f-9c74-a857d5925ef1.json' // 获取行政区划P噢利用
|
||||
'https://gw.alipayobjects.com/os/bmw-prod/d6da7ac1-8b4f-4a55-93ea-e81aa08f0cf3.json',
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const chinaPolygonLayer = new PolygonLayer({
|
||||
name: '中国填充',
|
||||
autoFit: true,
|
||||
})
|
||||
.source(data)
|
||||
.color('name', [
|
||||
'rgb(239,243,255)',
|
||||
'rgb(189,215,231)',
|
||||
'rgb(107,174,214)',
|
||||
'rgb(49,130,189)',
|
||||
'rgb(8,81,156)',
|
||||
])
|
||||
.shape('fill')
|
||||
.style({
|
||||
opacity: 1,
|
||||
});
|
||||
// 图层边界
|
||||
const layer2 = new LineLayer({
|
||||
name: '中国边框',
|
||||
zIndex: 2,
|
||||
})
|
||||
.source(data)
|
||||
.color('rgb(93,112,146)')
|
||||
.size(0.6)
|
||||
.style({
|
||||
opacity: 1,
|
||||
});
|
||||
|
||||
layer2.hide();
|
||||
|
||||
newScene.addLayer(chinaPolygonLayer);
|
||||
newScene.addLayer(layer2);
|
||||
newLayers.push(chinaPolygonLayer, layer2);
|
||||
}),
|
||||
]).then(() => {
|
||||
const newControl = new LayerControl({
|
||||
layers: newLayers,
|
||||
});
|
||||
setControl(newControl);
|
||||
newScene.addControl(newControl);
|
||||
setLayers(newLayers);
|
||||
setScene(newScene);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
const layer = layers[0];
|
||||
if (layer?.isVisible()) {
|
||||
layer?.hide();
|
||||
} else {
|
||||
layer?.show();
|
||||
}
|
||||
}}
|
||||
>
|
||||
切换点图层显隐
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (newLayer && control && !layers.includes(newLayer)) {
|
||||
const newLayers = [...layers, newLayer];
|
||||
control?.setOptions({
|
||||
layers: newLayers,
|
||||
});
|
||||
setLayers(newLayers);
|
||||
}
|
||||
}}
|
||||
>
|
||||
添加新的管理图层
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Logo
|
||||
order: 10
|
||||
---
|
||||
|
||||
<code src="./logo.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,73 @@
|
|||
import { GaodeMap, Scene, Logo } from '@antv/l7';
|
||||
import React from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
logoVisible: false,
|
||||
});
|
||||
|
||||
scene.on('loaded', () => {
|
||||
const logo1 = new Logo({
|
||||
position: 'leftbottom',
|
||||
});
|
||||
scene.addControl(logo1);
|
||||
|
||||
setTimeout(() => {
|
||||
logo1.setOptions({
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
href: '',
|
||||
style: 'height: 40px; width: 40px;',
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
const logo2 = new Logo({
|
||||
position: 'rightbottom',
|
||||
href: undefined,
|
||||
});
|
||||
scene.addControl(logo2);
|
||||
|
||||
const logo3 = new Logo({
|
||||
position: 'topright',
|
||||
style: 'height: 40px; width: 40px;',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
href: '',
|
||||
});
|
||||
scene.addControl(logo3);
|
||||
|
||||
const logo4 = new Logo({
|
||||
position: 'topleft',
|
||||
style: 'height: 40px; width: 40px;',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg',
|
||||
href: 'https://ant.design/index-cn',
|
||||
});
|
||||
scene.addControl(logo4);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 地图样式
|
||||
order: 12
|
||||
---
|
||||
|
||||
<code src="./mapTheme.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,53 @@
|
|||
import { GaodeMap, Scene, MapTheme } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
style: 'normal',
|
||||
}),
|
||||
// map: new GaodeMapV2({
|
||||
// style: 'dark',
|
||||
// center: [120, 30],
|
||||
// pitch: 0,
|
||||
// zoom: 6.45,
|
||||
// }),
|
||||
// map: new GaodeMap({
|
||||
// style: 'dark',
|
||||
// center: [120, 30],
|
||||
// pitch: 0,
|
||||
// zoom: 6.45,
|
||||
// }),
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newControl = new MapTheme({
|
||||
// defaultValue: 'normal',
|
||||
});
|
||||
newScene.addControl(newControl);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 鼠标经纬度
|
||||
order: 12
|
||||
---
|
||||
|
||||
<code src="./mouseLocation.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,42 @@
|
|||
import { GaodeMapV2, Scene, MouseLocation } from '@antv/l7';
|
||||
import React from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMapV2({
|
||||
style: 'normal',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newControl = new MouseLocation({});
|
||||
newScene.addControl(newControl);
|
||||
// const zoom = new Zoom({
|
||||
// position: 'topright',
|
||||
// });
|
||||
// newScene.addControl(zoom);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 定位
|
||||
order: 10
|
||||
---
|
||||
|
||||
<code src="./navigation.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,49 @@
|
|||
import { GaodeMapV2, GeoLocate, Scene } from '@antv/l7';
|
||||
import gcoord from 'gcoord';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMapV2({
|
||||
style: 'normal',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
preserveDrawingBuffer: true,
|
||||
// WebGLParams: {
|
||||
// preserveDrawingBuffer: true,
|
||||
// },
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newControl = new GeoLocate({
|
||||
transform: (position) => {
|
||||
return gcoord.transform(position, gcoord.WGS84, gcoord.GCJ02);
|
||||
},
|
||||
});
|
||||
newScene.addControl(newControl);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 控件位置
|
||||
order: 1
|
||||
---
|
||||
|
||||
<code src="./position.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,64 @@
|
|||
import { GaodeMap, Logo, PositionName, Scale, Scene, Zoom } from '@antv/l7';
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
scene.on('loaded', () => {
|
||||
function createTestControl(position: PositionName) {
|
||||
scene.addControl(
|
||||
new Zoom({
|
||||
position,
|
||||
}),
|
||||
);
|
||||
scene.addControl(
|
||||
new Scale({
|
||||
position,
|
||||
}),
|
||||
);
|
||||
|
||||
scene.addControl(
|
||||
new Logo({
|
||||
position,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
createTestControl('topleft');
|
||||
createTestControl('topright');
|
||||
createTestControl('bottomleft');
|
||||
createTestControl('bottomright');
|
||||
|
||||
createTestControl('lefttop');
|
||||
createTestControl('leftbottom');
|
||||
createTestControl('righttop');
|
||||
createTestControl('rightbottom');
|
||||
|
||||
createTestControl('topcenter');
|
||||
createTestControl('leftcenter');
|
||||
createTestControl('rightcenter');
|
||||
createTestControl('bottomcenter');
|
||||
});
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 插入/移除
|
||||
order: 4
|
||||
---
|
||||
|
||||
<code src="./remove.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,65 @@
|
|||
import { GaodeMap, PositionType, Scene, Zoom } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const POSITION_LIST = Object.values(PositionType);
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | null>(null);
|
||||
const [zoom, setZoom] = useState<Zoom | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
setScene(newScene);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
disabled={!!zoom}
|
||||
onClick={() => {
|
||||
if (!zoom) {
|
||||
const newZoom = new Zoom();
|
||||
scene?.addControl(newZoom);
|
||||
setZoom(newZoom);
|
||||
}
|
||||
}}
|
||||
>
|
||||
插入
|
||||
</button>
|
||||
<button
|
||||
disabled={!zoom}
|
||||
onClick={() => {
|
||||
if (zoom) {
|
||||
scene?.removeControl(zoom);
|
||||
setZoom(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
移除
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 比例尺
|
||||
order: 14
|
||||
---
|
||||
|
||||
<code src="./scale.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,63 @@
|
|||
import { GaodeMapV2, Scene, Scale } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [control, setControl] = useState<Scale | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
// map: new GaodeMap({
|
||||
// center: [120, 30],
|
||||
// pitch: 0,
|
||||
// zoom: 6.45,
|
||||
// }),
|
||||
map: new GaodeMapV2({
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
style: 'normal',
|
||||
}),
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const scale = new Scale({
|
||||
metric: true,
|
||||
position: 'rightbottom',
|
||||
// imperial: true,
|
||||
});
|
||||
// const zoom = new Zoom({
|
||||
// position: 'rightbottom',
|
||||
// });
|
||||
newScene.addControl(scale);
|
||||
// newScene.addControl(zoom);
|
||||
setControl(scale);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.setOptions({
|
||||
imperial: true,
|
||||
metric: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
更新配置
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 更新配置
|
||||
order: 2
|
||||
---
|
||||
|
||||
<code src="./setOptions.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,54 @@
|
|||
import { GaodeMap, PositionType, Scene, Zoom } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const POSITION_LIST = Object.values(PositionType);
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [zoom, setZoom] = useState(() => {
|
||||
return new Zoom({
|
||||
position: 'topleft',
|
||||
});
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
scene.on('loaded', () => {
|
||||
scene.addControl(zoom);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onChangePosition = () => {
|
||||
const randomIndex = Math.floor(Math.random() * POSITION_LIST.length);
|
||||
zoom.setOptions({
|
||||
position: POSITION_LIST[randomIndex],
|
||||
className: `random-class-${Math.floor(Math.random() * 100)}`,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={onChangePosition}>设置随机Options</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 缩放
|
||||
order: 13
|
||||
---
|
||||
|
||||
<code src="./zoom.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,77 @@
|
|||
import { GaodeMap, Scene, Zoom } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [control, setControl] = useState<Zoom | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [120, 30],
|
||||
pitch: 0,
|
||||
zoom: 6.45,
|
||||
}),
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newControl = new Zoom();
|
||||
newScene.addControl(newControl);
|
||||
setControl(newControl);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.setOptions({
|
||||
zoomInText: '加',
|
||||
zoomInTitle: 'in',
|
||||
zoomOutText: '减',
|
||||
zoomOutTitle: 'out',
|
||||
});
|
||||
}}
|
||||
>
|
||||
更新配置
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.setOptions({
|
||||
zoomInText: undefined,
|
||||
zoomInTitle: undefined,
|
||||
zoomOutText: undefined,
|
||||
zoomOutTitle: undefined,
|
||||
});
|
||||
}}
|
||||
>
|
||||
还原配置
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.enable();
|
||||
}}
|
||||
>
|
||||
启用
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
control?.disable();
|
||||
}}
|
||||
>
|
||||
禁用
|
||||
</button>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 图层气泡
|
||||
order: 2
|
||||
---
|
||||
|
||||
<code src="./layerPopup.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,118 @@
|
|||
import {
|
||||
GaodeMap,
|
||||
LayerPopup,
|
||||
PointLayer,
|
||||
Scene,
|
||||
LineLayer,
|
||||
// anchorType,
|
||||
} from '@antv/l7';
|
||||
import { featureCollection, point } from '@turf/turf';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | null>(null);
|
||||
const [popup, setPopup] = useState<LayerPopup | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120.104697, 30.260704],
|
||||
pitch: 0,
|
||||
zoom: 15,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const pointLayer = new PointLayer({
|
||||
name: 'pointLayer',
|
||||
});
|
||||
pointLayer
|
||||
.source(
|
||||
featureCollection([
|
||||
point([120.104697, 30.260704], {
|
||||
name: '测试点1',
|
||||
lng: 120.104697,
|
||||
lat: 30.260704,
|
||||
}),
|
||||
point([120.104697, 30.261715], {
|
||||
name: '测试点2',
|
||||
lng: 120.104697,
|
||||
lat: 30.261715,
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.color('#ff0000')
|
||||
.size(10);
|
||||
const lineString = new LineLayer({
|
||||
name: 'lineLayer',
|
||||
});
|
||||
lineString
|
||||
.source(
|
||||
featureCollection([
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {
|
||||
name: '测试线3',
|
||||
},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: [
|
||||
[120.103615, 30.262026],
|
||||
[120.103172, 30.261771],
|
||||
[120.102697, 30.261934],
|
||||
],
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
.size(6)
|
||||
.color('#00ff00');
|
||||
newScene.addLayer(pointLayer);
|
||||
newScene.addLayer(lineString);
|
||||
const newPopup = new LayerPopup({
|
||||
config: [
|
||||
{
|
||||
layer: 'pointLayer',
|
||||
fields: [
|
||||
{
|
||||
field: 'name',
|
||||
formatField: (key) => {
|
||||
return '名称';
|
||||
},
|
||||
},
|
||||
'lng',
|
||||
'lat',
|
||||
],
|
||||
},
|
||||
{
|
||||
layer: 'lineLayer',
|
||||
fields: ['name'],
|
||||
},
|
||||
],
|
||||
trigger: 'hover',
|
||||
});
|
||||
newScene.addPopup(newPopup);
|
||||
setPopup(newPopup);
|
||||
setScene(newScene);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: 气泡
|
||||
order: 1
|
||||
---
|
||||
|
||||
<code src="./popup.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,256 @@
|
|||
import {
|
||||
GaodeMap,
|
||||
PointLayer,
|
||||
Popup,
|
||||
Scene,
|
||||
Fullscreen,
|
||||
anchorType,
|
||||
// anchorType,
|
||||
} from '@antv/l7';
|
||||
import { featureCollection, point } from '@turf/turf';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | null>(null);
|
||||
const [popup, setPopup] = useState<Popup | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120.104697, 30.260704],
|
||||
pitch: 0,
|
||||
zoom: 15,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
const newPopup = new Popup({
|
||||
closeOnClick: true,
|
||||
closeOnEsc: true,
|
||||
lngLat: {
|
||||
lng: 120.104697,
|
||||
lat: 30.260704,
|
||||
},
|
||||
anchor: 'bottom-right',
|
||||
title: 'Popup Title',
|
||||
html: 'Popup Content',
|
||||
});
|
||||
newScene.addPopup(newPopup);
|
||||
|
||||
const pointLayer = new PointLayer();
|
||||
pointLayer
|
||||
.source(featureCollection([point([120.104697, 30.260704])]))
|
||||
.color('#ff0000')
|
||||
.size(10);
|
||||
|
||||
newScene.addLayer(pointLayer);
|
||||
setPopup(newPopup);
|
||||
|
||||
const fullscreen = new Fullscreen();
|
||||
newScene.addControl(fullscreen);
|
||||
|
||||
setScene(newScene);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.show();
|
||||
}}
|
||||
>
|
||||
show
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.hide();
|
||||
}}
|
||||
>
|
||||
hide
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
closeButton: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
closeButton
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
closeButtonOffsets: [10, 10],
|
||||
});
|
||||
}}
|
||||
>
|
||||
closeButtonOffsets
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
closeOnClick: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
closeOnClick
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
closeOnEsc: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
closeOnEsc
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
maxWidth: '50px',
|
||||
});
|
||||
}}
|
||||
>
|
||||
maxWidth
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
anchor: anchorType['BOTTOM-LEFT'],
|
||||
});
|
||||
}}
|
||||
>
|
||||
anchor
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
offsets: [10, 10],
|
||||
});
|
||||
}}
|
||||
>
|
||||
offsets
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
autoPan: true,
|
||||
});
|
||||
popup?.setLnglat({
|
||||
lng: 120,
|
||||
lat: 30,
|
||||
});
|
||||
}}
|
||||
>
|
||||
autoPan
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
autoClose: false,
|
||||
});
|
||||
}}
|
||||
>
|
||||
autoClose
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
followCursor: true,
|
||||
});
|
||||
}}
|
||||
>
|
||||
followCursor
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
className: 'text-class',
|
||||
});
|
||||
}}
|
||||
>
|
||||
className
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
style: 'background-color: #ff0000',
|
||||
});
|
||||
}}
|
||||
>
|
||||
style
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
text: 'text',
|
||||
});
|
||||
}}
|
||||
>
|
||||
text
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
html: 'html',
|
||||
});
|
||||
}}
|
||||
>
|
||||
html
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
lngLat: {
|
||||
lng: 120.103797,
|
||||
lat: 30.260804,
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
lngLat
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
popup?.setOptions({
|
||||
title: undefined,
|
||||
});
|
||||
}}
|
||||
>
|
||||
title
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newPopup = new Popup({
|
||||
// autoPan: true,
|
||||
html: 'fjdksl',
|
||||
lngLat: {
|
||||
lng: 120.103797,
|
||||
lat: 30.260804,
|
||||
},
|
||||
});
|
||||
scene?.addPopup(newPopup);
|
||||
}}
|
||||
>
|
||||
addPopup
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Scene
|
||||
order: 3
|
||||
---
|
||||
|
||||
<code src="./boxSelect.tsx" compact defaultShowCode></code>
|
|
@ -0,0 +1,70 @@
|
|||
import { GaodeMap, Scene } from '@antv/l7';
|
||||
import React, { useState } from 'react';
|
||||
// tslint:disable-next-line:no-duplicate-imports
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
const Demo: FunctionComponent = () => {
|
||||
const [scene, setScene] = useState<Scene | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'dark',
|
||||
center: [120.104697, 30.260704],
|
||||
pitch: 0,
|
||||
zoom: 15,
|
||||
}),
|
||||
// logoVisible: false,
|
||||
});
|
||||
|
||||
newScene.on('loaded', () => {
|
||||
setScene(newScene);
|
||||
|
||||
newScene.on('selectstart', (...params) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('selectstart', ...params);
|
||||
});
|
||||
|
||||
newScene.on('selecting', (...params) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('selecting', ...params);
|
||||
});
|
||||
|
||||
newScene.on('selectend', (...params) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('selectend', ...params);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<button
|
||||
onClick={() => {
|
||||
scene?.enableBoxSelect(true);
|
||||
}}
|
||||
>
|
||||
开始框选
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
scene?.disableBoxSelect();
|
||||
}}
|
||||
>
|
||||
结束框选
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
height: '500px',
|
||||
position: 'relative',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Demo;
|
|
@ -29,23 +29,31 @@ module.exports = {
|
|||
babelConfig: require('./babel.config.js'),
|
||||
},
|
||||
},
|
||||
moduleFileExtensions: [ 'ts', 'tsx', 'js' ],
|
||||
modulePathIgnorePatterns: [ 'dist' ],
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
moduleNameMapper: {
|
||||
'@antv/l7-(.+)$': '<rootDir>packages/$1/src'
|
||||
'@antv/l7-(.+)$': '<rootDir>packages/$1/src',
|
||||
},
|
||||
notify: true,
|
||||
notifyMode: 'always',
|
||||
roots: [ '<rootDir>packages' ],
|
||||
testMatch: [ '**/__tests__/*.spec.+(ts|tsx|js)', '**/*.test.+(ts|tsx|js)', '**/__tests__/*/*.spec.+(ts|tsx|js)' ],
|
||||
roots: ['<rootDir>packages'],
|
||||
testMatch: [
|
||||
'**/__tests__/*.spec.+(ts|tsx|js)',
|
||||
'**/*.test.+(ts|tsx|js)',
|
||||
'**/__tests__/*/*.spec.+(ts|tsx|js)',
|
||||
],
|
||||
transform: {
|
||||
// '^.+\\.(ts|tsx)$': 'ts-jest',
|
||||
// @see https://github.com/kulshekhar/ts-jest/issues/1130
|
||||
'^.+\\.(ts|tsx)$': 'babel-jest'
|
||||
'^.+\\.(ts|tsx)$': 'babel-jest',
|
||||
screenfull: 'babel-jest',
|
||||
'\\.(less|css)$': 'jest-less-loader',
|
||||
'\\.png$': 'jest-file-loader',
|
||||
},
|
||||
setupFilesAfterEnv: [ '<rootDir>jest/setupTests.ts' ],
|
||||
snapshotSerializers: [ 'enzyme-to-json/serializer' ],
|
||||
coverageReporters: ['html', 'lcov', 'clover'],
|
||||
coveragePathIgnorePatterns: ['/node_modules/', '/iconfont/'],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
branches: 9,
|
||||
|
|
|
@ -101,10 +101,13 @@
|
|||
"husky": "^3.0.9",
|
||||
"jest": "^24.9.0",
|
||||
"jest-canvas-mock": "^2.4.0",
|
||||
"jest-file-loader": "^1.0.2",
|
||||
"jest-less-loader": "^0.1.2",
|
||||
"jest-styled-components": "^6.2.1",
|
||||
"leaflet": "^1.8.0",
|
||||
"lerc": "^3.0.0",
|
||||
"lerna": "^3.16.4",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^9.2.4",
|
||||
"mockjs": "^1.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
|
@ -153,7 +156,8 @@
|
|||
"webpack-dev-server": "^3.1.7",
|
||||
"webpack-merge": "^4.1.4",
|
||||
"worker-loader": "^2.0.0",
|
||||
"yorkie": "^2.0.0"
|
||||
"yorkie": "^2.0.0",
|
||||
"gcoord": "^0.3.2"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "npm run worker && dumi dev",
|
||||
|
@ -221,6 +225,5 @@
|
|||
},
|
||||
"tnpm": {
|
||||
"mode": "yarn"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import ButtonControl from '../src/control/baseControl/buttonControl';
|
||||
import { createL7Icon } from '../src/utils/icon';
|
||||
|
||||
class TestControl extends ButtonControl {}
|
||||
|
||||
describe('buttonControl', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new TestControl();
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('disable', () => {
|
||||
const control = new TestControl();
|
||||
scene.addControl(control);
|
||||
control.setIsDisable(true);
|
||||
|
||||
expect(control.getContainer().getAttribute('disabled')).not.toBeNull();
|
||||
control.setIsDisable(false);
|
||||
|
||||
expect(control.getContainer().getAttribute('disabled')).toBeNull();
|
||||
});
|
||||
|
||||
it('options', () => {
|
||||
const control = new TestControl({
|
||||
title: '导出图片',
|
||||
btnText: '导出图片',
|
||||
btnIcon: createL7Icon('l7-icon-tupian'),
|
||||
});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.classList).toContain('l7-button-control');
|
||||
expect(container.getAttribute('title')).toContain('导出图片');
|
||||
const textContainer = container.querySelector('.l7-button-control__text')!;
|
||||
expect(textContainer).toBeInstanceOf(HTMLElement);
|
||||
|
||||
control.setOptions({
|
||||
title: undefined,
|
||||
btnText: '替换文本',
|
||||
btnIcon: createL7Icon('l7-icon-tupian1'),
|
||||
});
|
||||
|
||||
expect(container.getAttribute('title')).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,68 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { Control } from '../src/control/baseControl';
|
||||
|
||||
class TestControl extends Control {
|
||||
public onAdd(): HTMLElement {
|
||||
return DOM.create('div');
|
||||
}
|
||||
public onRemove(): void {}
|
||||
}
|
||||
|
||||
describe('control', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const className1 = 'testControl1';
|
||||
const className2 = 'testControl2';
|
||||
const control1 = new TestControl({
|
||||
className: className1,
|
||||
});
|
||||
const control2 = new TestControl({
|
||||
className: className2,
|
||||
});
|
||||
scene.addControl(control1);
|
||||
scene.addControl(control2);
|
||||
|
||||
const dom1 = document.querySelector(`.${className1}`);
|
||||
expect(dom1).toBeInstanceOf(HTMLElement);
|
||||
const dom2 = document.querySelector(`.${className2}`);
|
||||
expect(dom2).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control1);
|
||||
scene.removeControl(control2);
|
||||
const dom3 = document.querySelector(`.${className1}`);
|
||||
expect(dom3).toBeNull();
|
||||
const dom4 = document.querySelector(`.${className2}`);
|
||||
expect(dom4).toBeNull();
|
||||
});
|
||||
|
||||
it('show hide', () => {
|
||||
const control = new TestControl();
|
||||
scene.addControl(control);
|
||||
control.hide();
|
||||
expect(control.getContainer().classList).toContain('l7-control--hide');
|
||||
expect(control.getIsShow()).toEqual(false);
|
||||
control.show();
|
||||
expect(control.getContainer().classList).not.toContain('l7-control--hide');
|
||||
expect(control.getIsShow()).toEqual(true);
|
||||
});
|
||||
|
||||
it('options', () => {
|
||||
const className = 'gunala';
|
||||
const color = 'rgb(255, 0, 0)';
|
||||
const control = new TestControl({});
|
||||
scene.addControl(control);
|
||||
control.setOptions({
|
||||
position: 'leftbottom',
|
||||
className,
|
||||
style: `color: ${color};`,
|
||||
});
|
||||
const container = control.getContainer();
|
||||
const corner = container.parentElement!;
|
||||
expect(corner.classList).toContain('l7-left');
|
||||
expect(corner.classList).toContain('l7-bottom');
|
||||
expect(container.classList).toContain(className);
|
||||
expect(container.style.color).toEqual(color);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import ExportImage from '../src/control/exportImage';
|
||||
|
||||
describe('exportImage', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new ExportImage({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('image', () => {
|
||||
const control = new ExportImage({
|
||||
onExport: (base64) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
// console.log(base64);
|
||||
},
|
||||
});
|
||||
scene.addControl(control);
|
||||
|
||||
const button = control.getContainer() as HTMLDivElement;
|
||||
button.click();
|
||||
|
||||
expect(button.parentElement).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Fullscreen from '../src/control/fullscreen';
|
||||
|
||||
describe('fullscreen', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new Fullscreen({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('fullscreen', () => {
|
||||
const control = new Fullscreen({});
|
||||
scene.addControl(control);
|
||||
|
||||
const button = control.getContainer() as HTMLDivElement;
|
||||
button.click();
|
||||
|
||||
expect(button.parentElement).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import LayerControl from '../src/control/layerControl';
|
||||
|
||||
describe('layerControl', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const layerControl = new LayerControl();
|
||||
scene.addControl(layerControl);
|
||||
|
||||
const container = layerControl.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
expect(layerControl.getLayerVisible()).toEqual([]);
|
||||
|
||||
scene.removeControl(layerControl);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import { PointLayer } from '@antv/l7-layers';
|
||||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import LayerPopup from '../src/popup/layerPopup';
|
||||
|
||||
describe('popup', () => {
|
||||
const scene = TestScene();
|
||||
const testClassName = 'l7-layer-popup-test';
|
||||
|
||||
it('life cycle', () => {
|
||||
const pointLayer = new PointLayer();
|
||||
pointLayer.source([{ lng: 120, lat: 30 }], {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
const layerPopup = new LayerPopup({
|
||||
className: testClassName,
|
||||
config: [
|
||||
{
|
||||
layer: pointLayer,
|
||||
fields: [
|
||||
{
|
||||
field: 'lng',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
scene.addPopup(layerPopup);
|
||||
|
||||
expect(layerPopup.isOpen()).toEqual(true);
|
||||
|
||||
layerPopup.setOptions({
|
||||
trigger: 'click',
|
||||
});
|
||||
|
||||
scene.removePopup(layerPopup);
|
||||
|
||||
expect(layerPopup.isOpen()).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import MapTheme from '../src/control/mapTheme';
|
||||
|
||||
describe('mapTheme', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new MapTheme({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('mapTheme', () => {
|
||||
const control = new MapTheme({
|
||||
defaultValue: 'normal',
|
||||
});
|
||||
scene.addControl(control);
|
||||
|
||||
const options = control.getOptions().options;
|
||||
expect(options.length).toBeGreaterThan(0);
|
||||
expect(control.getSelectValue()).toEqual(
|
||||
'mapbox://styles/mapbox/streets-v11',
|
||||
);
|
||||
|
||||
const optionList = ((control
|
||||
.getPopper()
|
||||
.getContent() as HTMLDivElement).querySelectorAll(
|
||||
'.l7-select-control-item',
|
||||
) as unknown) as HTMLDivElement[];
|
||||
optionList[1].click();
|
||||
|
||||
// expect(control.getSelectValue()).toEqual(
|
||||
// 'mapbox://styles/zcxduo/ck2ypyb1r3q9o1co1766dex29',
|
||||
// );
|
||||
});
|
||||
});
|
|
@ -1,15 +1,14 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Marker from '../src/marker';
|
||||
import Popup from '../src/popup';
|
||||
import { TestScene } from '@antv/l7-test-utils'
|
||||
import Popup from '../src/popup/popup';
|
||||
|
||||
const popup = new Popup({ offsets: [0, 20] })
|
||||
.setHTML('<h1 onclick= alert("123")>111</h1>');
|
||||
const popup = new Popup({ offsets: [0, 20] }).setHTML(
|
||||
'<h1 onclick= alert("123")>111</h1>',
|
||||
);
|
||||
|
||||
const marker = new Marker()
|
||||
.setLnglat({ lng: 120, lat: 30 })
|
||||
.setPopup(popup);
|
||||
const marker = new Marker().setLnglat({ lng: 120, lat: 30 }).setPopup(popup);
|
||||
|
||||
TestScene().addMarker(marker)
|
||||
TestScene().addMarker(marker);
|
||||
|
||||
describe('Marker', () => {
|
||||
it('render and remove correctly', () => {
|
||||
|
@ -18,7 +17,7 @@ describe('Marker', () => {
|
|||
expect(marker.getDefault().color).toEqual('#5B8FF9');
|
||||
expect(marker.getOffset()).toEqual([0, 0]);
|
||||
expect(marker.isDraggable()).toEqual(false);
|
||||
marker.remove()
|
||||
marker.remove();
|
||||
expect(document.querySelector('.l7-marker')).toBeFalsy();
|
||||
});
|
||||
|
||||
|
@ -34,13 +33,13 @@ describe('Marker', () => {
|
|||
|
||||
marker.closePopup();
|
||||
expect(marker.getPopup().isOpen()).toBeFalsy();
|
||||
})
|
||||
});
|
||||
|
||||
it('longitude and latitude', () => {
|
||||
const { lng, lat } = marker.getLnglat();
|
||||
expect(lng).toEqual(120);
|
||||
expect(lat).toEqual(30);
|
||||
marker.setLnglat({ lng: 121, lat: 31 })
|
||||
marker.setLnglat({ lng: 121, lat: 31 });
|
||||
const { lng: newLng, lat: newLat } = marker.getLnglat();
|
||||
expect(newLng).toEqual(121);
|
||||
expect(newLat).toEqual(31);
|
||||
|
@ -55,7 +54,7 @@ describe('Marker', () => {
|
|||
});
|
||||
|
||||
it('extData', () => {
|
||||
marker.setExtData({ test: 1 })
|
||||
expect(marker.getExtData()).toEqual({ test: 1 })
|
||||
marker.setExtData({ test: 1 });
|
||||
expect(marker.getExtData()).toEqual({ test: 1 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import MouseLocation from '../src/control/mouseLocation';
|
||||
|
||||
describe('buttonControl', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new MouseLocation({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new MouseLocation();
|
||||
scene.addControl(control);
|
||||
|
||||
(scene.getMapService().map as any).emit('mousemove', {
|
||||
lngLat: {
|
||||
lng: 120,
|
||||
lat: 30,
|
||||
},
|
||||
});
|
||||
|
||||
expect(control.getLocation()).toEqual([120, 30]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Navigation from '../src/control/geoLocate';
|
||||
|
||||
describe('navigation', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('navigation', () => {
|
||||
const control = new Navigation({});
|
||||
scene.addControl(control);
|
||||
|
||||
const button = control.getContainer() as HTMLDivElement;
|
||||
button.click();
|
||||
|
||||
expect(button.parentElement).toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import PopperControl from '../src/control/baseControl/popperControl';
|
||||
|
||||
class TestControl extends PopperControl {}
|
||||
|
||||
describe('popperControl', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new TestControl({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('popper', () => {
|
||||
const control = new TestControl({
|
||||
popperTrigger: 'click',
|
||||
});
|
||||
scene.addControl(control);
|
||||
});
|
||||
|
||||
it('options', () => {
|
||||
const control = new TestControl({});
|
||||
scene.addControl(control);
|
||||
const testClassName = 'testPopper';
|
||||
control.setOptions({
|
||||
popperClassName: testClassName,
|
||||
});
|
||||
expect(control.getPopper().getPopperDOM().classList).toContain(
|
||||
testClassName,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Popup from '../src/popup/popup';
|
||||
|
||||
describe('popup', () => {
|
||||
const scene = TestScene();
|
||||
const className = 'text-class-popup';
|
||||
|
||||
it('life cycle', () => {
|
||||
const popup = new Popup({
|
||||
html: '123456',
|
||||
className: className,
|
||||
lngLat: {
|
||||
lng: 120,
|
||||
lat: 30,
|
||||
},
|
||||
});
|
||||
|
||||
popup.setOptions({
|
||||
lngLat: { lng: 130, lat: 40 },
|
||||
});
|
||||
|
||||
scene.addPopup(popup);
|
||||
|
||||
const targetPopup = document.querySelector(`.${className}`) as HTMLElement;
|
||||
|
||||
expect(targetPopup).not.toBeFalsy();
|
||||
expect(popup.getLnglat()).toEqual({
|
||||
lng: 130,
|
||||
lat: 40,
|
||||
});
|
||||
expect(/123456/.test(targetPopup.innerHTML)).toEqual(true);
|
||||
|
||||
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(false);
|
||||
|
||||
popup.hide();
|
||||
|
||||
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(true);
|
||||
|
||||
popup.show();
|
||||
|
||||
expect(targetPopup.classList.contains('l7-popup-hide')).toEqual(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Scale from '../src/control/scale';
|
||||
|
||||
describe('scale', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const scale = new Scale();
|
||||
scene.addControl(scale);
|
||||
|
||||
const container = scale.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
expect(
|
||||
/\d+\s?km/i.test(
|
||||
container
|
||||
.querySelector('.l7-control-scale-line')
|
||||
?.innerHTML.toLowerCase() ?? '',
|
||||
),
|
||||
).toEqual(true);
|
||||
|
||||
scale.setOptions({
|
||||
metric: false,
|
||||
imperial: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
/\d+\s?mi/i.test(
|
||||
container
|
||||
.querySelector('.l7-control-scale-line')
|
||||
?.innerHTML.toLowerCase() ?? '',
|
||||
),
|
||||
).toEqual(true);
|
||||
|
||||
scene.removeControl(scale);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import SelectControl from '../src/control/baseControl/selectControl';
|
||||
import { createL7Icon } from '../src/utils/icon';
|
||||
|
||||
class SingleControl extends SelectControl {
|
||||
public getDefault(option: any): any {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
options: [
|
||||
{
|
||||
icon: createL7Icon('icon-1'),
|
||||
label: '1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
icon: createL7Icon('icon-2'),
|
||||
label: '2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
defaultValue: '2',
|
||||
};
|
||||
}
|
||||
|
||||
protected getIsMultiple(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class MultiControl extends SelectControl {
|
||||
public getDefault(option: any): any {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
options: [
|
||||
{
|
||||
img: '1',
|
||||
label: '1',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
img: '1',
|
||||
label: '2',
|
||||
value: '2',
|
||||
},
|
||||
],
|
||||
defaultValue: ['2'],
|
||||
};
|
||||
}
|
||||
protected getIsMultiple(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
describe('selectControl', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const control = new SingleControl({});
|
||||
scene.addControl(control);
|
||||
|
||||
const container = control.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(control);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('normal single select', () => {
|
||||
const control = new SingleControl({});
|
||||
scene.addControl(control);
|
||||
|
||||
expect(control.getSelectValue()).toEqual('2');
|
||||
const popperContainer = control.getPopper().getContent() as HTMLDivElement;
|
||||
const optionDomList = Array.from(
|
||||
popperContainer.querySelectorAll('.l7-select-control-item'),
|
||||
) as HTMLDivElement[];
|
||||
expect(optionDomList).toHaveLength(2);
|
||||
expect(optionDomList[0].getAttribute('data-option-value')).toEqual('1');
|
||||
expect(optionDomList[0].getAttribute('data-option-index')).toEqual('0');
|
||||
|
||||
optionDomList[0].click();
|
||||
|
||||
expect(control.getSelectValue()).toEqual('1');
|
||||
});
|
||||
|
||||
it('img multiple select', () => {
|
||||
const control = new MultiControl({});
|
||||
scene.addControl(control);
|
||||
expect(control.getSelectValue()).toEqual(['2']);
|
||||
const popperContainer = control.getPopper().getContent() as HTMLDivElement;
|
||||
const optionDomList = Array.from(
|
||||
popperContainer.querySelectorAll('.l7-select-control-item'),
|
||||
) as HTMLDivElement[];
|
||||
expect(optionDomList).toHaveLength(2);
|
||||
expect(optionDomList[0].getAttribute('data-option-value')).toEqual('1');
|
||||
expect(optionDomList[0].getAttribute('data-option-index')).toEqual('0');
|
||||
expect(popperContainer.querySelectorAll('img')).toHaveLength(2);
|
||||
|
||||
optionDomList[0].click();
|
||||
expect(control.getSelectValue()).toEqual(['2', '1']);
|
||||
optionDomList[0].click();
|
||||
optionDomList[1].click();
|
||||
expect(control.getSelectValue()).toEqual([]);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import { createL7Icon } from '../src/utils/icon';
|
||||
import { Popper } from '../src/utils/popper';
|
||||
|
||||
describe('util', () => {
|
||||
it('icon', () => {
|
||||
const testClassName = 'l7-test-icon';
|
||||
const testIcon = createL7Icon(testClassName);
|
||||
expect(testIcon).toBeInstanceOf(SVGElement);
|
||||
expect(testIcon.tagName.toLowerCase()).toEqual('svg');
|
||||
const classList = testIcon.classList;
|
||||
expect(classList).toContain('l7-iconfont');
|
||||
});
|
||||
|
||||
it('popper', () => {
|
||||
const button = DOM.create('button') as HTMLButtonElement;
|
||||
button.innerText = 'Test';
|
||||
document.body.append(button);
|
||||
|
||||
const testContent = '123456';
|
||||
const popper1 = new Popper(button, {
|
||||
placement: 'left-start',
|
||||
trigger: 'click',
|
||||
content: testContent,
|
||||
className: 'test-popper-class',
|
||||
container: document.body,
|
||||
unique: true,
|
||||
});
|
||||
const getPopperClassList = (popper: Popper) => {
|
||||
return popper.popperDOM.classList;
|
||||
};
|
||||
popper1.show();
|
||||
|
||||
expect(popper1.getContent()).toEqual(testContent);
|
||||
expect(getPopperClassList(popper1)).toContain('l7-popper');
|
||||
expect(getPopperClassList(popper1)).toContain('test-popper-class');
|
||||
expect(getPopperClassList(popper1)).toContain('l7-popper-left');
|
||||
expect(getPopperClassList(popper1)).toContain('l7-popper-start');
|
||||
expect(getPopperClassList(popper1)).not.toContain('l7-popper-hide');
|
||||
popper1.hide();
|
||||
|
||||
button.click();
|
||||
expect(getPopperClassList(popper1)).not.toContain('l7-popper-hide');
|
||||
button.click();
|
||||
expect(getPopperClassList(popper1)).toContain('l7-popper-hide');
|
||||
|
||||
const newTestContent = DOM.create('div') as HTMLDivElement;
|
||||
newTestContent.innerText = '789456';
|
||||
popper1.setContent(newTestContent);
|
||||
expect(popper1.contentDOM.firstChild).toEqual(newTestContent);
|
||||
popper1.show();
|
||||
|
||||
const popper2 = new Popper(button, {
|
||||
placement: 'right-end',
|
||||
container: document.body,
|
||||
trigger: 'click',
|
||||
content: 'hover',
|
||||
}).show();
|
||||
expect(getPopperClassList(popper2)).toContain('l7-popper-end');
|
||||
expect(getPopperClassList(popper2)).toContain('l7-popper-right');
|
||||
|
||||
const popper3 = new Popper(button, {
|
||||
placement: 'top-start',
|
||||
container: document.body,
|
||||
trigger: 'click',
|
||||
content: 'hover',
|
||||
}).show();
|
||||
expect(getPopperClassList(popper3)).toContain('l7-popper-top');
|
||||
expect(getPopperClassList(popper3)).toContain('l7-popper-start');
|
||||
|
||||
const popper4 = new Popper(button, {
|
||||
placement: 'bottom-end',
|
||||
container: document.body,
|
||||
trigger: 'click',
|
||||
content: 'hover',
|
||||
}).show();
|
||||
expect(getPopperClassList(popper4)).toContain('l7-popper-bottom');
|
||||
expect(getPopperClassList(popper4)).toContain('l7-popper-end');
|
||||
|
||||
const popper5 = new Popper(button, {
|
||||
placement: 'left',
|
||||
container: document.body,
|
||||
trigger: 'click',
|
||||
content: 'hover',
|
||||
}).show();
|
||||
expect(getPopperClassList(popper5)).toContain('l7-popper-left');
|
||||
|
||||
const popper6 = new Popper(button, {
|
||||
placement: 'top',
|
||||
container: document.body,
|
||||
trigger: 'click',
|
||||
content: 'hover',
|
||||
}).show();
|
||||
expect(getPopperClassList(popper6)).toContain('l7-popper-top');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
import { TestScene } from '@antv/l7-test-utils';
|
||||
import Zoom from '../src/control/zoom';
|
||||
|
||||
describe('zoom', () => {
|
||||
const scene = TestScene();
|
||||
|
||||
it('life cycle', () => {
|
||||
const zoom = new Zoom();
|
||||
scene.addControl(zoom);
|
||||
|
||||
const container = zoom.getContainer();
|
||||
expect(container.parentElement).toBeInstanceOf(HTMLElement);
|
||||
|
||||
scene.removeControl(zoom);
|
||||
expect(container.parentElement).not.toBeInstanceOf(HTMLElement);
|
||||
});
|
||||
|
||||
it('zoom getDefault', () => {
|
||||
const zoom = new Zoom();
|
||||
scene.addControl(zoom);
|
||||
|
||||
zoom.disable();
|
||||
const btnList = Array.from(zoom.getContainer().querySelectorAll('button'));
|
||||
expect(btnList.map((item) => item.getAttribute('disabled'))).toEqual([
|
||||
'true',
|
||||
'true',
|
||||
]);
|
||||
zoom.enable();
|
||||
expect(btnList.map((item) => item.getAttribute('disabled'))).toEqual([
|
||||
null,
|
||||
null,
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -13,6 +13,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"tsc": "tsc --project tsconfig.build.json",
|
||||
"less": "lessc src/css/index.less src/css/index.css",
|
||||
"clean": "rimraf dist; rimraf es; rimraf lib;",
|
||||
"build": "father build",
|
||||
"build:cjs": "BABEL_ENV=cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
|
||||
|
@ -35,7 +36,9 @@
|
|||
"supercluster": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antv/l7-test-utils": "2.9.37"
|
||||
"@antv/l7-test-utils": "2.9.37",
|
||||
"gcoord": "^0.3.2",
|
||||
"less": "^4.1.3"
|
||||
},
|
||||
"gitHead": "684ba4eb806a798713496d3fc0b4d1e17517dc31",
|
||||
"publishConfig": {
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,75 @@
|
|||
export const GaodeMapStyleConfig = {
|
||||
normal: {
|
||||
text: '标准',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*-nqiT6Vu948AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
light: {
|
||||
text: '月光银',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*J_wYQL_PaUEAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
dark: {
|
||||
text: '幻影黑',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*U7M9QI1yat4AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
fresh: {
|
||||
text: '草色青',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*T-oBT4hB5ucAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
grey: {
|
||||
text: '雅士灰',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*OREXQ4vgQRIAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
graffiti: {
|
||||
text: '涂鸦',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*4UApTKmeiy4AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
macaron: {
|
||||
text: '马卡龙',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*0GrCQLtDjNcAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
darkblue: {
|
||||
text: '极夜蓝',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*uWxqSZQlPkkAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
wine: {
|
||||
text: '酱籽',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*OFPrTbg3an0AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
};
|
||||
|
||||
export const MapboxMapStyleConfig = {
|
||||
normal: {
|
||||
text: '标准',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*AnfJTbIBJOkAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
light: {
|
||||
text: '亮',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*gnuiQIok9qIAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
dark: {
|
||||
text: '暗',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*NwG-TbOlBH0AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
satellite: {
|
||||
text: '卫星',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*2X5EQLKul3IAAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
outdoors: {
|
||||
text: '户外',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*gXFLRIaBUI0AAAAAAAAAAAAAARQnAQ',
|
||||
},
|
||||
};
|
|
@ -1,112 +0,0 @@
|
|||
import {
|
||||
IControlOption,
|
||||
IControlService,
|
||||
ILayerService,
|
||||
IMapService,
|
||||
IRendererService,
|
||||
PositionType,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
|
||||
export { PositionType } from '@antv/l7-core';
|
||||
|
||||
let controlId = 0;
|
||||
export default class Control extends EventEmitter {
|
||||
public controlOption: IControlOption;
|
||||
protected container: HTMLElement;
|
||||
protected sceneContainer: Container;
|
||||
protected mapsService: IMapService;
|
||||
protected renderService: IRendererService;
|
||||
protected layerService: ILayerService;
|
||||
protected controlService: IControlService;
|
||||
|
||||
private isShow: boolean;
|
||||
|
||||
constructor(cfg?: Partial<IControlOption>) {
|
||||
super();
|
||||
this.controlOption = {
|
||||
...this.getDefault(),
|
||||
...(cfg || {}),
|
||||
};
|
||||
}
|
||||
|
||||
public getDefault() {
|
||||
return {
|
||||
position: PositionType.TOPRIGHT,
|
||||
name: `${controlId++}`,
|
||||
};
|
||||
}
|
||||
|
||||
public setPosition(position: PositionType = PositionType.BOTTOMRIGHT) {
|
||||
// 考虑组件的自动布局,需要销毁重建
|
||||
const controlService = this.controlService;
|
||||
if (controlService) {
|
||||
controlService.removeControl(this);
|
||||
}
|
||||
this.controlOption.position = position;
|
||||
if (controlService) {
|
||||
controlService.addControl(this, this.sceneContainer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public addTo(sceneContainer: Container) {
|
||||
this.mapsService = sceneContainer.get<IMapService>(TYPES.IMapService);
|
||||
this.renderService = sceneContainer.get<IRendererService>(
|
||||
TYPES.IRendererService,
|
||||
);
|
||||
this.layerService = sceneContainer.get<ILayerService>(TYPES.ILayerService);
|
||||
this.controlService = sceneContainer.get<IControlService>(
|
||||
TYPES.IControlService,
|
||||
);
|
||||
this.sceneContainer = sceneContainer;
|
||||
this.isShow = true;
|
||||
this.container = this.onAdd();
|
||||
const container = this.container;
|
||||
const pos = this.controlOption.position;
|
||||
const corner = this.controlService.controlCorners[pos];
|
||||
DOM.addClass(container, 'l7-control');
|
||||
|
||||
if (pos.indexOf('bottom') !== -1) {
|
||||
corner.insertBefore(container, corner.firstChild);
|
||||
} else {
|
||||
corner.appendChild(container);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public onAdd(): HTMLElement {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public onRemove(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public hide() {
|
||||
const container = this.container;
|
||||
DOM.addClass(container, 'l7-control-hide');
|
||||
this.isShow = false;
|
||||
}
|
||||
public show() {
|
||||
const container = this.container;
|
||||
DOM.removeClass(container, 'l7-control-hide');
|
||||
this.isShow = true;
|
||||
}
|
||||
public remove() {
|
||||
if (!this.mapsService) {
|
||||
return this;
|
||||
}
|
||||
DOM.remove(this.container);
|
||||
this.onRemove();
|
||||
}
|
||||
public _refocusOnMap(e: MouseEvent) {
|
||||
// if map exists and event is not a keyboard event
|
||||
if (this.mapsService && e && e.screenX > 0 && e.screenY > 0) {
|
||||
const container = this.mapsService.getContainer();
|
||||
if (container !== null) {
|
||||
container.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
describe('BaseControl', () => {
|
||||
// const el = document.createElement('div');
|
||||
// el.id = 'test-div-id';
|
||||
// el.style.width = '500px';
|
||||
// el.style.height = '500px';
|
||||
// el.style.position = 'absolute';
|
||||
// document.querySelector('body')?.appendChild(el);
|
||||
// const scene = new Scene({
|
||||
// id: 'test-div-id',
|
||||
// map: new Map({
|
||||
// style: 'dark',
|
||||
// center: [110.19382669582967, 30.258134],
|
||||
// pitch: 0,
|
||||
// zoom: 3,
|
||||
// }),
|
||||
// });
|
||||
it('control', () => {
|
||||
expect(1).toEqual(1)
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import Zoom from '../zoom';
|
||||
import { TestScene } from '@antv/l7-test-utils'
|
||||
|
||||
|
||||
describe('zoom', () => {
|
||||
const zoom = new Zoom()
|
||||
it('zoom getDefault', () => {
|
||||
expect(zoom.getDefault().name).toEqual('zoom');
|
||||
const scene = TestScene();
|
||||
scene.addControl(zoom);
|
||||
zoom.disable();
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import { ELType } from '@antv/l7-utils/src/dom';
|
||||
import Control, { IControlOption } from './control';
|
||||
|
||||
export { ButtonControl };
|
||||
|
||||
export interface IButtonControlOption extends IControlOption {
|
||||
btnIcon?: ELType | DocumentFragment;
|
||||
btnText?: string;
|
||||
title?: string;
|
||||
vertical?: boolean;
|
||||
}
|
||||
|
||||
export default class ButtonControl<
|
||||
O extends IButtonControlOption = IButtonControlOption
|
||||
> extends Control<O> {
|
||||
/**
|
||||
* 当前按钮是否禁用
|
||||
* @protected
|
||||
*/
|
||||
protected isDisable = false;
|
||||
|
||||
/**
|
||||
* 按钮的 DOM
|
||||
* @protected
|
||||
*/
|
||||
protected button?: HTMLElement;
|
||||
|
||||
/**
|
||||
* 按钮中文本对应的 DOM
|
||||
* @protected
|
||||
*/
|
||||
protected buttonText?: HTMLElement;
|
||||
|
||||
/**
|
||||
* 按钮中图标对应的 DOM
|
||||
* @protected
|
||||
*/
|
||||
protected buttonIcon?: ELType | DocumentFragment;
|
||||
|
||||
/**
|
||||
* 设置当前按钮
|
||||
* @param newIsDisable
|
||||
*/
|
||||
public setIsDisable(newIsDisable: boolean) {
|
||||
this.isDisable = newIsDisable;
|
||||
if (newIsDisable) {
|
||||
this.button?.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.button?.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
public createButton(className: string = '') {
|
||||
return DOM.create(
|
||||
'button',
|
||||
`l7-button-control ${className}`,
|
||||
) as HTMLElement;
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
this.button = this.createButton();
|
||||
this.isDisable = false;
|
||||
const { title, btnText, btnIcon } = this.controlOption;
|
||||
this.setBtnTitle(title);
|
||||
this.setBtnText(btnText);
|
||||
this.setBtnIcon(btnIcon);
|
||||
return this.button;
|
||||
}
|
||||
|
||||
public onRemove(): void {
|
||||
this.button = this.buttonIcon = this.buttonText = undefined;
|
||||
this.isDisable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置方法
|
||||
* @param newOptions
|
||||
*/
|
||||
public setOptions(newOptions: Partial<O>) {
|
||||
const { title, btnText, btnIcon } = newOptions;
|
||||
if (this.checkUpdateOption(newOptions, ['title'])) {
|
||||
this.setBtnTitle(title);
|
||||
}
|
||||
if (this.checkUpdateOption(newOptions, ['btnIcon'])) {
|
||||
this.setBtnIcon(btnIcon);
|
||||
}
|
||||
if (this.checkUpdateOption(newOptions, ['btnText'])) {
|
||||
this.setBtnText(btnText);
|
||||
}
|
||||
super.setOptions(newOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮 title
|
||||
* @param title
|
||||
*/
|
||||
public setBtnTitle(title: O['title']) {
|
||||
this.button?.setAttribute('title', title ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮 Icon
|
||||
* @param newIcon
|
||||
*/
|
||||
public setBtnIcon(newIcon: O['btnIcon']) {
|
||||
if (this.buttonIcon) {
|
||||
DOM.remove(this.buttonIcon);
|
||||
}
|
||||
if (newIcon) {
|
||||
const firstChild = this.button?.firstChild;
|
||||
if (firstChild) {
|
||||
this.button?.insertBefore(newIcon, firstChild);
|
||||
} else {
|
||||
this.button?.appendChild(newIcon);
|
||||
}
|
||||
this.buttonIcon = newIcon;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按钮文本
|
||||
* @param newText
|
||||
*/
|
||||
public setBtnText(newText: O['btnText']) {
|
||||
if (!this.button) {
|
||||
return;
|
||||
}
|
||||
DOM.removeClass(this.button, 'l7-button-control--row');
|
||||
DOM.removeClass(this.button, 'l7-button-control--column');
|
||||
if (newText) {
|
||||
let btnText = this.buttonText;
|
||||
if (!btnText) {
|
||||
btnText = DOM.create('div', 'l7-button-control__text') as HTMLElement;
|
||||
this.button?.appendChild(btnText);
|
||||
this.buttonText = btnText;
|
||||
}
|
||||
btnText.innerText = newText;
|
||||
DOM.addClass(
|
||||
this.button,
|
||||
this.controlOption.vertical
|
||||
? 'l7-button-control--column'
|
||||
: 'l7-button-control--row',
|
||||
);
|
||||
} else if (!newText && this.buttonText) {
|
||||
DOM.remove(this.buttonText);
|
||||
this.buttonText = undefined;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
import {
|
||||
IControl,
|
||||
IControlService,
|
||||
IGlobalConfigService,
|
||||
ILayerService,
|
||||
IMapService,
|
||||
IRendererService,
|
||||
ISceneService,
|
||||
PositionName,
|
||||
PositionType,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
import { ControlEvent } from '../../interface';
|
||||
|
||||
export { PositionType } from '@antv/l7-core';
|
||||
|
||||
export { Control };
|
||||
|
||||
export interface IControlOption {
|
||||
name: string;
|
||||
position: PositionName;
|
||||
className?: string;
|
||||
style?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export default class Control<O extends IControlOption = IControlOption>
|
||||
extends EventEmitter<ControlEvent>
|
||||
implements IControl<O> {
|
||||
/**
|
||||
* 当前类型控件实例个数
|
||||
* @protected
|
||||
*/
|
||||
protected static controlCount = 0;
|
||||
|
||||
/**
|
||||
* 当前控件实例配置
|
||||
*/
|
||||
public controlOption: O;
|
||||
|
||||
/**
|
||||
* 控件的 DOM 容器
|
||||
* @protected
|
||||
*/
|
||||
protected container: HTMLElement;
|
||||
|
||||
/**
|
||||
* 当前控件是否显示
|
||||
* @protected
|
||||
*/
|
||||
protected isShow: boolean;
|
||||
|
||||
protected sceneContainer: Container;
|
||||
protected scene: ISceneService;
|
||||
protected mapsService: IMapService;
|
||||
protected renderService: IRendererService;
|
||||
protected layerService: ILayerService;
|
||||
protected controlService: IControlService;
|
||||
protected configService: IGlobalConfigService;
|
||||
|
||||
constructor(option?: Partial<O>) {
|
||||
super();
|
||||
Control.controlCount++;
|
||||
this.controlOption = {
|
||||
...this.getDefault(option),
|
||||
...(option || {}),
|
||||
};
|
||||
}
|
||||
|
||||
public getOptions() {
|
||||
return this.controlOption;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置的方法,子类如果有自己的配置,也需要重写该方法
|
||||
* @param newOptions
|
||||
*/
|
||||
public setOptions(newOptions: Partial<O>): void {
|
||||
const defaultOptions = this.getDefault(newOptions);
|
||||
(Object.entries(newOptions) as Array<[keyof O, any]>).forEach(
|
||||
([key, value]) => {
|
||||
if (value === undefined) {
|
||||
newOptions[key] = defaultOptions[key];
|
||||
}
|
||||
},
|
||||
);
|
||||
if ('position' in newOptions) {
|
||||
this.setPosition(newOptions.position);
|
||||
}
|
||||
if ('className' in newOptions) {
|
||||
this.setClassName(newOptions.className);
|
||||
}
|
||||
if ('style' in newOptions) {
|
||||
this.setStyle(newOptions.style);
|
||||
}
|
||||
this.controlOption = {
|
||||
...this.controlOption,
|
||||
...newOptions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 Control 被添加至 Scene 中,被 controlService 调用的方法
|
||||
* @param sceneContainer
|
||||
*/
|
||||
public addTo(sceneContainer: Container) {
|
||||
// 初始化各个 Service 实例
|
||||
this.mapsService = sceneContainer.get<IMapService>(TYPES.IMapService);
|
||||
this.renderService = sceneContainer.get<IRendererService>(
|
||||
TYPES.IRendererService,
|
||||
);
|
||||
this.layerService = sceneContainer.get<ILayerService>(TYPES.ILayerService);
|
||||
this.controlService = sceneContainer.get<IControlService>(
|
||||
TYPES.IControlService,
|
||||
);
|
||||
this.configService = sceneContainer.get<IGlobalConfigService>(
|
||||
TYPES.IGlobalConfigService,
|
||||
);
|
||||
this.scene = sceneContainer.get<ISceneService>(TYPES.ISceneService);
|
||||
this.sceneContainer = sceneContainer;
|
||||
this.isShow = true;
|
||||
|
||||
// 初始化 container
|
||||
this.container = this.onAdd();
|
||||
DOM.addClass(this.container, 'l7-control');
|
||||
|
||||
const { className, style } = this.controlOption;
|
||||
if (className) {
|
||||
this.setClassName(className);
|
||||
}
|
||||
if (style) {
|
||||
this.setStyle(style);
|
||||
}
|
||||
// 将 container 插入容器中
|
||||
this.insertContainer();
|
||||
this.emit('add', this);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将控件移除时触发
|
||||
*/
|
||||
public remove() {
|
||||
if (!this.mapsService) {
|
||||
return this;
|
||||
}
|
||||
DOM.remove(this.container);
|
||||
this.onRemove();
|
||||
this.emit('remove', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Control 被添加的时候被调用,返回 Control 对应的 DOM 容器
|
||||
*/
|
||||
public onAdd(): HTMLElement {
|
||||
return DOM.create('div');
|
||||
}
|
||||
|
||||
/**
|
||||
* Control 被移除时调用
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
public onRemove() {}
|
||||
|
||||
/**
|
||||
* 显示控件时触发
|
||||
*/
|
||||
public show() {
|
||||
const container = this.container;
|
||||
DOM.removeClass(container, 'l7-control--hide');
|
||||
this.isShow = true;
|
||||
this.emit('show', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏控件时触发
|
||||
*/
|
||||
public hide() {
|
||||
const container = this.container;
|
||||
DOM.addClass(container, 'l7-control--hide');
|
||||
this.isShow = false;
|
||||
this.emit('hide', this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认构造器参数
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
public getDefault(option?: Partial<O>): O {
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
return {
|
||||
position: PositionType.TOPRIGHT,
|
||||
name: `${Control.controlCount}`,
|
||||
} as O;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前控件对应的 DOM 容器
|
||||
*/
|
||||
public getContainer() {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Control 是否展示
|
||||
*/
|
||||
public getIsShow() {
|
||||
return this.isShow;
|
||||
}
|
||||
|
||||
public _refocusOnMap(e: MouseEvent) {
|
||||
// if map exists and event is not a keyboard event
|
||||
if (this.mapsService && e && e.screenX > 0 && e.screenY > 0) {
|
||||
const container = this.mapsService.getContainer();
|
||||
if (container !== null) {
|
||||
container.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前控件位置
|
||||
* @param position
|
||||
*/
|
||||
public setPosition(
|
||||
position: PositionType | PositionName = PositionType.TOPLEFT,
|
||||
) {
|
||||
// 考虑组件的自动布局,需要销毁重建
|
||||
const controlService = this.controlService;
|
||||
if (controlService) {
|
||||
controlService.removeControl(this);
|
||||
}
|
||||
this.controlOption.position = position;
|
||||
if (controlService) {
|
||||
controlService.addControl(this, this.sceneContainer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置容器 container 的样式相关位置,包含 className
|
||||
* @param className
|
||||
*/
|
||||
public setClassName(className?: string | null) {
|
||||
const container = this.container;
|
||||
const { className: oldClassName } = this.controlOption;
|
||||
if (oldClassName) {
|
||||
DOM.removeClass(container, oldClassName);
|
||||
}
|
||||
if (className) {
|
||||
DOM.addClass(container, className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置容器 container 的样式相关位置,包含 style
|
||||
* @param style
|
||||
*/
|
||||
public setStyle(style?: string | null) {
|
||||
const container = this.container;
|
||||
if (style) {
|
||||
container.setAttribute('style', style);
|
||||
} else {
|
||||
container.removeAttribute('style');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将控件 DOM 插入到对应 position 的容器中
|
||||
* @protected
|
||||
*/
|
||||
protected insertContainer() {
|
||||
const container = this.container;
|
||||
const position = this.controlOption.position;
|
||||
const corner = this.controlService.controlCorners[position];
|
||||
if (position.indexOf('bottom') !== -1) {
|
||||
corner.insertBefore(container, corner.firstChild);
|
||||
} else {
|
||||
corner.appendChild(container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前传入 option 是否包含 keys 字段
|
||||
* @param option
|
||||
* @param keys
|
||||
* @protected
|
||||
*/
|
||||
protected checkUpdateOption(option: Partial<O>, keys: Array<keyof O>) {
|
||||
return keys.some((key) => key in option);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './control';
|
||||
export * from './buttonControl';
|
||||
export * from './popperControl';
|
||||
export * from './selectControl';
|
|
@ -0,0 +1,111 @@
|
|||
import { PositionName } from '@antv/l7-core';
|
||||
import { Popper, PopperPlacement, PopperTrigger } from '../../utils/popper';
|
||||
import ButtonControl, { IButtonControlOption } from './buttonControl';
|
||||
|
||||
export { PopperControl };
|
||||
|
||||
export interface IPopperControlOption extends IButtonControlOption {
|
||||
popperPlacement: PopperPlacement;
|
||||
popperClassName?: string;
|
||||
popperTrigger: PopperTrigger;
|
||||
}
|
||||
|
||||
const PopperPlacementMap: Record<PositionName, PopperPlacement> = {
|
||||
topleft: 'right-start',
|
||||
topcenter: 'bottom',
|
||||
topright: 'left-start',
|
||||
bottomleft: 'right-end',
|
||||
bottomcenter: 'top',
|
||||
bottomright: 'left-end',
|
||||
lefttop: 'bottom-start',
|
||||
leftcenter: 'right',
|
||||
leftbottom: 'top-start',
|
||||
righttop: 'bottom-end',
|
||||
rightcenter: 'left',
|
||||
rightbottom: 'top-end',
|
||||
};
|
||||
|
||||
export default class PopperControl<
|
||||
O extends IPopperControlOption = IPopperControlOption
|
||||
> extends ButtonControl<O> {
|
||||
/**
|
||||
* 气泡实例
|
||||
* @protected
|
||||
*/
|
||||
protected popper!: Popper;
|
||||
|
||||
public getPopper() {
|
||||
return this.popper;
|
||||
}
|
||||
|
||||
public hide() {
|
||||
this.popper.hide();
|
||||
super.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认配置
|
||||
* @param option
|
||||
*/
|
||||
public getDefault(option?: Partial<O>): O {
|
||||
const defaultOption = super.getDefault(option);
|
||||
const position = option?.position ?? defaultOption.position!;
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
popperPlacement: PopperPlacementMap[position],
|
||||
popperTrigger: 'click',
|
||||
};
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
const button = super.onAdd();
|
||||
this.initPopper();
|
||||
return button;
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
this.popper.destroy();
|
||||
}
|
||||
|
||||
public initPopper() {
|
||||
const {
|
||||
popperClassName,
|
||||
popperPlacement,
|
||||
popperTrigger,
|
||||
} = this.controlOption;
|
||||
const popperContainer = this.mapsService.getMapContainer()!;
|
||||
|
||||
this.popper = new Popper(this.button!, {
|
||||
className: popperClassName,
|
||||
placement: popperPlacement,
|
||||
trigger: popperTrigger,
|
||||
container: popperContainer,
|
||||
unique: true,
|
||||
});
|
||||
this.popper
|
||||
.on('show', () => {
|
||||
this.emit('popperShow', this);
|
||||
})
|
||||
.on('hide', () => {
|
||||
this.emit('popperHide', this);
|
||||
});
|
||||
return this.popper;
|
||||
}
|
||||
|
||||
public setOptions(option: Partial<O>) {
|
||||
super.setOptions(option);
|
||||
|
||||
if (
|
||||
this.checkUpdateOption(option, [
|
||||
'popperPlacement',
|
||||
'popperTrigger',
|
||||
'popperClassName',
|
||||
])
|
||||
) {
|
||||
const content = this.popper.getContent();
|
||||
this.popper.destroy();
|
||||
this.initPopper();
|
||||
this.popper.setContent(content);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import { IPopperControlOption, PopperControl } from './popperControl';
|
||||
|
||||
type BaseOptionItem = {
|
||||
value: string;
|
||||
text: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type NormalOptionItem = BaseOptionItem & {
|
||||
icon?: HTMLElement;
|
||||
};
|
||||
|
||||
type ImageOptionItem = BaseOptionItem & {
|
||||
img: string;
|
||||
};
|
||||
|
||||
export type ControlOptionItem = ImageOptionItem | NormalOptionItem;
|
||||
|
||||
export interface ISelectControlOption extends IPopperControlOption {
|
||||
options: ControlOptionItem[];
|
||||
defaultValue?: string | string[];
|
||||
}
|
||||
|
||||
export { SelectControl };
|
||||
|
||||
enum SelectControlConstant {
|
||||
ActiveOptionClassName = 'l7-select-control-item-active',
|
||||
OptionValueAttrKey = 'data-option-value',
|
||||
OptionIndexAttrKey = 'data-option-index',
|
||||
}
|
||||
|
||||
export default class SelectControl<
|
||||
O extends ISelectControlOption = ISelectControlOption
|
||||
> extends PopperControl<O> {
|
||||
/**
|
||||
* 当前选中的值
|
||||
* @protected
|
||||
*/
|
||||
protected selectValue: string[] = [];
|
||||
|
||||
/**
|
||||
* 选项对应的 DOM 列表
|
||||
* @protected
|
||||
*/
|
||||
protected optionDOMList: HTMLElement[];
|
||||
|
||||
public setOptions(option: Partial<O>) {
|
||||
super.setOptions(option);
|
||||
const { options } = option;
|
||||
if (options) {
|
||||
this.popper.setContent(this.getPopperContent(options));
|
||||
}
|
||||
}
|
||||
|
||||
public onAdd() {
|
||||
const button = super.onAdd();
|
||||
const { defaultValue } = this.controlOption;
|
||||
if (defaultValue) {
|
||||
this.selectValue = this.transSelectValue(defaultValue);
|
||||
}
|
||||
this.popper.setContent(this.getPopperContent(this.controlOption.options));
|
||||
return button;
|
||||
}
|
||||
|
||||
public getSelectValue() {
|
||||
return this.getIsMultiple() ? this.selectValue : this.selectValue[0];
|
||||
}
|
||||
|
||||
public setSelectValue(value: string | string[], emitEvent = true) {
|
||||
const finalValue = this.transSelectValue(value);
|
||||
this.optionDOMList.forEach((optionDOM) => {
|
||||
const optionValue = optionDOM.getAttribute(
|
||||
SelectControlConstant.OptionValueAttrKey,
|
||||
)!;
|
||||
const checkboxDOM = this.getIsMultiple()
|
||||
? optionDOM.querySelector('input[type=checkbox]')
|
||||
: undefined;
|
||||
if (finalValue.includes(optionValue)) {
|
||||
DOM.addClass(optionDOM, SelectControlConstant.ActiveOptionClassName);
|
||||
if (checkboxDOM) {
|
||||
// @ts-ignore
|
||||
DOM.setChecked(checkboxDOM, true);
|
||||
}
|
||||
} else {
|
||||
DOM.removeClass(optionDOM, SelectControlConstant.ActiveOptionClassName);
|
||||
if (checkboxDOM) {
|
||||
// @ts-ignore
|
||||
DOM.setChecked(checkboxDOM, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.selectValue = finalValue;
|
||||
if (emitEvent) {
|
||||
this.emit(
|
||||
'selectChange',
|
||||
this.getIsMultiple() ? finalValue : finalValue[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为多选
|
||||
* @protected
|
||||
*/
|
||||
protected getIsMultiple() {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected getPopperContent(options: ControlOptionItem[]): HTMLElement {
|
||||
const isImageOptions = this.isImageOptions();
|
||||
const content = DOM.create(
|
||||
'div',
|
||||
isImageOptions ? 'l7-select-control--image' : 'l7-select-control--normal',
|
||||
) as HTMLElement;
|
||||
if (this.getIsMultiple()) {
|
||||
DOM.addClass(content, 'l7-select-control--multiple');
|
||||
}
|
||||
const optionsDOMList = options.map((option, optionIndex) => {
|
||||
const optionDOM = isImageOptions
|
||||
? // @ts-ignore
|
||||
this.createImageOption(option)
|
||||
: this.createNormalOption(option);
|
||||
|
||||
optionDOM.setAttribute(
|
||||
SelectControlConstant.OptionValueAttrKey,
|
||||
option.value,
|
||||
);
|
||||
optionDOM.setAttribute(
|
||||
SelectControlConstant.OptionIndexAttrKey,
|
||||
window.String(optionIndex),
|
||||
);
|
||||
optionDOM.addEventListener('click', this.onItemClick.bind(this, option));
|
||||
return optionDOM;
|
||||
});
|
||||
content.append(...optionsDOMList);
|
||||
this.optionDOMList = optionsDOMList;
|
||||
return content;
|
||||
}
|
||||
|
||||
protected createNormalOption = (option: NormalOptionItem) => {
|
||||
const isSelect = this.selectValue.includes(option.value);
|
||||
const optionDOM = DOM.create(
|
||||
'div',
|
||||
`l7-select-control-item ${
|
||||
isSelect ? SelectControlConstant.ActiveOptionClassName : ''
|
||||
}`,
|
||||
) as HTMLElement;
|
||||
if (this.getIsMultiple()) {
|
||||
optionDOM.appendChild(this.createCheckbox(isSelect));
|
||||
}
|
||||
if (option.icon) {
|
||||
optionDOM.appendChild(option.icon);
|
||||
}
|
||||
const textDOM = DOM.create('span');
|
||||
textDOM.innerText = option.text;
|
||||
optionDOM.appendChild(textDOM);
|
||||
return optionDOM;
|
||||
};
|
||||
|
||||
protected createImageOption(option: ImageOptionItem): HTMLElement {
|
||||
const isSelect = this.selectValue.includes(option.value);
|
||||
const optionDOM = DOM.create(
|
||||
'div',
|
||||
`l7-select-control-item ${
|
||||
isSelect ? SelectControlConstant.ActiveOptionClassName : ''
|
||||
}`,
|
||||
) as HTMLElement;
|
||||
const imgDOM = DOM.create('img') as HTMLElement;
|
||||
imgDOM.setAttribute('src', option.img);
|
||||
DOM.setUnDraggable(imgDOM);
|
||||
optionDOM.appendChild(imgDOM);
|
||||
const rowDOM = DOM.create(
|
||||
'div',
|
||||
'l7-select-control-item-row',
|
||||
) as HTMLElement;
|
||||
if (this.getIsMultiple()) {
|
||||
optionDOM.appendChild(this.createCheckbox(isSelect));
|
||||
}
|
||||
const textDOM = DOM.create('span');
|
||||
textDOM.innerText = option.text;
|
||||
rowDOM.appendChild(textDOM);
|
||||
optionDOM.appendChild(rowDOM);
|
||||
return optionDOM;
|
||||
}
|
||||
|
||||
protected createCheckbox(isSelect: boolean) {
|
||||
const checkboxDOM = DOM.create('input') as HTMLElement;
|
||||
checkboxDOM.setAttribute('type', 'checkbox');
|
||||
if (isSelect) {
|
||||
DOM.setChecked(checkboxDOM, true);
|
||||
}
|
||||
return checkboxDOM;
|
||||
}
|
||||
|
||||
protected onItemClick = (item: ControlOptionItem) => {
|
||||
if (this.getIsMultiple()) {
|
||||
const targetIndex = this.selectValue.findIndex(
|
||||
(value) => value === item.value,
|
||||
);
|
||||
if (targetIndex > -1) {
|
||||
this.selectValue.splice(targetIndex, 1);
|
||||
} else {
|
||||
this.selectValue = [...this.selectValue, item.value];
|
||||
}
|
||||
} else {
|
||||
this.selectValue = [item.value];
|
||||
}
|
||||
this.setSelectValue(this.selectValue);
|
||||
};
|
||||
|
||||
protected isImageOptions() {
|
||||
// @ts-ignore
|
||||
return !!this.controlOption.options.find((item) => item.img);
|
||||
}
|
||||
|
||||
protected transSelectValue(value: string | string[]) {
|
||||
return Array.isArray(value) ? value : [value];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import { createL7Icon } from '../utils/icon';
|
||||
import ButtonControl, {
|
||||
IButtonControlOption,
|
||||
} from './baseControl/buttonControl';
|
||||
|
||||
export interface IExportImageControlOption extends IButtonControlOption {
|
||||
imageType: 'png' | 'jpeg';
|
||||
onExport: (base64: string) => void;
|
||||
}
|
||||
|
||||
export { ExportImage };
|
||||
|
||||
export default class ExportImage extends ButtonControl<
|
||||
IExportImageControlOption
|
||||
> {
|
||||
public onAdd(): HTMLElement {
|
||||
const button = super.onAdd();
|
||||
button.addEventListener('click', this.onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
public getDefault(
|
||||
option?: Partial<IExportImageControlOption>,
|
||||
): IExportImageControlOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
title: '导出图片',
|
||||
btnIcon: createL7Icon('l7-icon-export-picture'),
|
||||
imageType: 'png',
|
||||
};
|
||||
}
|
||||
|
||||
public getImage() {
|
||||
const mapImage = this.mapsService.exportMap('png');
|
||||
const layerImage = this.scene.exportPng('png');
|
||||
return this.mergeImage(mapImage, layerImage);
|
||||
}
|
||||
|
||||
protected onClick = async () => {
|
||||
const { onExport } = this.controlOption;
|
||||
onExport?.(await this.getImage());
|
||||
};
|
||||
|
||||
/**
|
||||
* 将多张图片合并为一张图片
|
||||
* @protected
|
||||
* @param base64List
|
||||
*/
|
||||
protected mergeImage = async (...base64List: string[]) => {
|
||||
const { imageType } = this.controlOption;
|
||||
const { width = 0, height = 0 } =
|
||||
this.mapsService.getContainer()?.getBoundingClientRect() ?? {};
|
||||
const canvas = document.createElement('canvas') as HTMLCanvasElement;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const context = canvas.getContext('2d');
|
||||
const imgList = await Promise.all(
|
||||
base64List.map((base64) => {
|
||||
return new Promise<HTMLImageElement>((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
resolve(img);
|
||||
};
|
||||
img.src = base64;
|
||||
});
|
||||
}),
|
||||
);
|
||||
imgList.forEach((img) => {
|
||||
context?.drawImage(img, 0, 0, width, height);
|
||||
});
|
||||
return canvas.toDataURL(`image/${imageType}`) as string;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
import ScreenFull from '../utils/screenfull';
|
||||
import ButtonControl, {
|
||||
IButtonControlOption,
|
||||
} from './baseControl/buttonControl';
|
||||
|
||||
export interface IFullscreenControlOption extends IButtonControlOption {
|
||||
exitBtnText: IButtonControlOption['btnText'];
|
||||
exitBtnIcon: IButtonControlOption['btnIcon'];
|
||||
exitTitle: IButtonControlOption['title'];
|
||||
}
|
||||
|
||||
export { Fullscreen };
|
||||
|
||||
export default class Fullscreen extends ButtonControl<
|
||||
IFullscreenControlOption
|
||||
> {
|
||||
protected isFullscreen = false;
|
||||
|
||||
protected mapContainer: HTMLElement;
|
||||
|
||||
constructor(option?: Partial<IFullscreenControlOption>) {
|
||||
super(option);
|
||||
|
||||
if (!ScreenFull.isEnabled) {
|
||||
console.warn('当前浏览器环境不支持对地图全屏化');
|
||||
}
|
||||
}
|
||||
|
||||
public setOptions(newOptions: Partial<IFullscreenControlOption>) {
|
||||
const { exitBtnText, exitBtnIcon, exitTitle } = newOptions;
|
||||
if (this.isFullscreen) {
|
||||
if (this.checkUpdateOption(newOptions, ['exitBtnIcon'])) {
|
||||
this.setBtnIcon(exitBtnIcon);
|
||||
}
|
||||
if (this.checkUpdateOption(newOptions, ['exitBtnText'])) {
|
||||
this.setBtnText(exitBtnText);
|
||||
}
|
||||
if (this.checkUpdateOption(newOptions, ['exitTitle'])) {
|
||||
this.setBtnTitle(exitTitle);
|
||||
}
|
||||
}
|
||||
super.setOptions(newOptions);
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
const button = super.onAdd();
|
||||
button.addEventListener('click', this.onClick);
|
||||
this.mapContainer = DOM.getContainer(this.scene.getSceneConfig().id!);
|
||||
this.mapContainer.addEventListener(
|
||||
'fullscreenchange',
|
||||
this.onFullscreenChange,
|
||||
);
|
||||
return button;
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
super.onRemove();
|
||||
this.mapContainer.removeEventListener(
|
||||
'fullscreenchange',
|
||||
this.onFullscreenChange,
|
||||
);
|
||||
}
|
||||
|
||||
public getDefault(
|
||||
option?: Partial<IFullscreenControlOption>,
|
||||
): IFullscreenControlOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
title: '全屏',
|
||||
btnIcon: createL7Icon('l7-icon-fullscreen'),
|
||||
exitTitle: '退出全屏',
|
||||
exitBtnIcon: createL7Icon('l7-icon-exit-fullscreen'),
|
||||
};
|
||||
}
|
||||
|
||||
public toggleFullscreen = async () => {
|
||||
if (ScreenFull.isEnabled) {
|
||||
await ScreenFull.toggle(this.mapContainer);
|
||||
}
|
||||
};
|
||||
|
||||
protected onClick = () => {
|
||||
this.toggleFullscreen();
|
||||
};
|
||||
|
||||
protected onFullscreenChange = () => {
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
|
||||
const {
|
||||
btnText,
|
||||
btnIcon,
|
||||
title,
|
||||
exitBtnText,
|
||||
exitBtnIcon,
|
||||
exitTitle,
|
||||
} = this.controlOption;
|
||||
if (this.isFullscreen) {
|
||||
this.setBtnTitle(exitTitle);
|
||||
this.setBtnText(exitBtnText);
|
||||
this.setBtnIcon(exitBtnIcon);
|
||||
} else {
|
||||
this.setBtnTitle(title);
|
||||
this.setBtnText(btnText);
|
||||
this.setBtnIcon(btnIcon);
|
||||
}
|
||||
|
||||
this.emit('fullscreenChange', this.isFullscreen);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { Point } from '@antv/l7-core';
|
||||
import { isNaN } from 'lodash';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
import ButtonControl, {
|
||||
IButtonControlOption,
|
||||
} from './baseControl/buttonControl';
|
||||
|
||||
export interface IGeoLocateOption extends IButtonControlOption {
|
||||
transform: (position: Point) => Point | Promise<Point>;
|
||||
}
|
||||
|
||||
export { GeoLocate };
|
||||
|
||||
export default class GeoLocate extends ButtonControl<IGeoLocateOption> {
|
||||
constructor(option?: Partial<IGeoLocateOption>) {
|
||||
super(option);
|
||||
|
||||
if (!window.navigator.geolocation) {
|
||||
console.warn('当前浏览器环境不支持获取地理定位');
|
||||
}
|
||||
}
|
||||
|
||||
public getDefault(option?: Partial<IGeoLocateOption>): IGeoLocateOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
title: '定位',
|
||||
btnIcon: createL7Icon('l7-icon-reposition'),
|
||||
};
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
const button = super.onAdd();
|
||||
button.addEventListener('click', this.onClick);
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过浏览器 API 获取当前所在经纬度
|
||||
*/
|
||||
public getGeoLocation = () => {
|
||||
return new Promise<Point>((resolve, reject) => {
|
||||
window.navigator.geolocation.getCurrentPosition(
|
||||
({ coords }) => {
|
||||
const { longitude, latitude } = coords ?? {};
|
||||
if (!isNaN(longitude) && !isNaN(latitude)) {
|
||||
resolve([longitude, latitude]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
},
|
||||
(e) => {
|
||||
reject(e);
|
||||
},
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
public onClick = async () => {
|
||||
if (!window.navigator.geolocation) {
|
||||
return;
|
||||
}
|
||||
const { transform } = this.controlOption;
|
||||
const position = await this.getGeoLocation();
|
||||
const currentZoom = this.mapsService.getZoom();
|
||||
this.mapsService.setZoomAndCenter(
|
||||
currentZoom > 15 ? currentZoom : 15,
|
||||
transform ? await transform(position) : position,
|
||||
);
|
||||
};
|
||||
}
|
|
@ -1,317 +0,0 @@
|
|||
import { PositionType } from '@antv/l7-core';
|
||||
import { bindAll, DOM } from '@antv/l7-utils';
|
||||
import { ILayerControlOption } from '../interface';
|
||||
import Control from './BaseControl';
|
||||
|
||||
interface IInputItem extends HTMLInputElement {
|
||||
layerId: string;
|
||||
}
|
||||
|
||||
export default class Layers extends Control {
|
||||
private layerControlInputs: any[];
|
||||
private layers: any[];
|
||||
private lastZIndex: number;
|
||||
private handlingClick: boolean;
|
||||
private layersLink: HTMLElement;
|
||||
private baseLayersList: HTMLElement;
|
||||
private separator: HTMLElement;
|
||||
private overlaysList: HTMLElement;
|
||||
private form: HTMLElement;
|
||||
|
||||
constructor(cfg: Partial<ILayerControlOption>) {
|
||||
super(cfg);
|
||||
this.layerControlInputs = [];
|
||||
this.layers = [];
|
||||
this.lastZIndex = 0;
|
||||
this.handlingClick = false;
|
||||
this.initLayers();
|
||||
|
||||
bindAll(
|
||||
[
|
||||
'checkDisabledLayers',
|
||||
'onLayerChange',
|
||||
'collapse',
|
||||
'extend',
|
||||
'expand',
|
||||
'onInputClick',
|
||||
],
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
public getDefault() {
|
||||
return {
|
||||
collapsed: true,
|
||||
position: PositionType.TOPRIGHT,
|
||||
autoZIndex: true,
|
||||
hideSingleBase: false,
|
||||
sortLayers: false,
|
||||
name: 'layers',
|
||||
};
|
||||
}
|
||||
public onAdd() {
|
||||
this.initLayout();
|
||||
this.update();
|
||||
this.mapsService.on('zoomend', this.checkDisabledLayers);
|
||||
this.layers.forEach((layerItem) => {
|
||||
layerItem.layer.on('remove', this.onLayerChange);
|
||||
layerItem.layer.on('add', this.onLayerChange);
|
||||
});
|
||||
return this.container;
|
||||
}
|
||||
|
||||
public addVisualLayer(layer: any, name: string | number) {
|
||||
this.addLayer(layer, name, true);
|
||||
return this.mapsService ? this.update() : this;
|
||||
}
|
||||
public expand() {
|
||||
const { height } = this.renderService.getViewportSize();
|
||||
DOM.addClass(this.container, 'l7-control-layers-expanded');
|
||||
this.form.style.height = 'null';
|
||||
const acceptableHeight = height - (this.container.offsetTop + 50);
|
||||
if (acceptableHeight < this.form.clientHeight) {
|
||||
DOM.addClass(this.form, 'l7-control-layers-scrollbar');
|
||||
this.form.style.height = acceptableHeight + 'px';
|
||||
} else {
|
||||
DOM.removeClass(this.form, 'l7-control-layers-scrollbar');
|
||||
}
|
||||
this.checkDisabledLayers();
|
||||
return this;
|
||||
}
|
||||
|
||||
public collapse() {
|
||||
DOM.removeClass(this.container, 'l7-control-layers-expanded');
|
||||
return this;
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
if (!this.mapsService) {
|
||||
return;
|
||||
}
|
||||
this.mapsService.off('click', this.collapse);
|
||||
this.layers.forEach((layerItem) => {
|
||||
layerItem.layer.off('remove', this.onLayerChange);
|
||||
layerItem.layer.off('add', this.onLayerChange);
|
||||
});
|
||||
}
|
||||
private initLayout() {
|
||||
const className = 'l7-control-layers';
|
||||
const container = (this.container = DOM.create('div', className));
|
||||
const { collapsed } = this.controlOption;
|
||||
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
|
||||
container.setAttribute('aria-haspopup', 'true');
|
||||
const form = (this.form = DOM.create(
|
||||
'form',
|
||||
className + '-list',
|
||||
) as HTMLElement);
|
||||
|
||||
if (collapsed) {
|
||||
this.mapsService.on('click', this.collapse);
|
||||
container.addEventListener('mouseenter', this.expand);
|
||||
container.addEventListener('mouseleave', this.collapse);
|
||||
}
|
||||
|
||||
this.layersLink = DOM.create('a', className + '-toggle', container);
|
||||
const link = this.layersLink;
|
||||
// link.href = '#';
|
||||
link.title = 'Layers';
|
||||
if (!collapsed) {
|
||||
this.expand();
|
||||
}
|
||||
this.baseLayersList = DOM.create('div', className + '-base', form);
|
||||
this.separator = DOM.create('div', className + '-separator', form);
|
||||
this.overlaysList = DOM.create('div', className + '-overlays', form);
|
||||
container.appendChild(form);
|
||||
}
|
||||
private initLayers() {
|
||||
const { baseLayers = {}, overlayers = {} } = this.controlOption;
|
||||
Object.keys(baseLayers).forEach((name: string) => {
|
||||
// baseLayers[name].once('inited', this.update);
|
||||
this.addLayer(baseLayers[name], name, false);
|
||||
});
|
||||
Object.keys(overlayers).forEach((name: any) => {
|
||||
// overlayers[name].once('inited', this.update);
|
||||
this.addLayer(overlayers[name], name, true);
|
||||
});
|
||||
}
|
||||
|
||||
private update() {
|
||||
if (!this.container) {
|
||||
return this;
|
||||
}
|
||||
|
||||
DOM.empty(this.baseLayersList);
|
||||
DOM.empty(this.overlaysList);
|
||||
|
||||
this.layerControlInputs = [];
|
||||
let baseLayersPresent;
|
||||
let overlaysPresent;
|
||||
let i;
|
||||
let obj;
|
||||
let baseLayersCount = 0;
|
||||
|
||||
for (i = 0; i < this.layers.length; i++) {
|
||||
obj = this.layers[i];
|
||||
this.addItem(obj);
|
||||
overlaysPresent = overlaysPresent || obj.overlay;
|
||||
baseLayersPresent = baseLayersPresent || !obj.overlay;
|
||||
baseLayersCount += !obj.overlay ? 1 : 0;
|
||||
}
|
||||
|
||||
// Hide base layers section if there's only one layer.
|
||||
if (this.controlOption.hideSingleBase) {
|
||||
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
|
||||
this.baseLayersList.style.display = baseLayersPresent ? '' : 'none';
|
||||
}
|
||||
|
||||
this.separator.style.display =
|
||||
overlaysPresent && baseLayersPresent ? '' : 'none';
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private checkDisabledLayers() {
|
||||
const inputs = this.layerControlInputs;
|
||||
let input: IInputItem;
|
||||
let layer;
|
||||
const zoom = this.mapsService.getZoom();
|
||||
|
||||
for (let i = inputs.length - 1; i >= 0; i--) {
|
||||
input = inputs[i];
|
||||
layer = this.layerService.getLayer(input.layerId);
|
||||
|
||||
if (layer && layer.inited) {
|
||||
const minZoom = layer.getMinZoom();
|
||||
const maxZoom = layer.getMaxZoom();
|
||||
|
||||
input.disabled = zoom < minZoom || zoom > maxZoom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addLayer(layer: any, name: string | number, overlay: boolean) {
|
||||
if (this.mapsService) {
|
||||
layer.on('add', this.onLayerChange);
|
||||
layer.on('remove', this.onLayerChange);
|
||||
}
|
||||
this.layers.push({
|
||||
layer,
|
||||
name,
|
||||
overlay,
|
||||
});
|
||||
const { sortLayers, sortFunction, autoZIndex } = this.controlOption;
|
||||
if (sortLayers) {
|
||||
this.layers.sort((a, b) => {
|
||||
return sortFunction(a.layer, b.layer, a.name, b.name);
|
||||
});
|
||||
}
|
||||
|
||||
if (autoZIndex && layer.setZIndex) {
|
||||
this.lastZIndex++;
|
||||
layer.setZIndex(this.lastZIndex);
|
||||
}
|
||||
|
||||
this.expandIfNotCollapsed();
|
||||
}
|
||||
|
||||
private expandIfNotCollapsed() {
|
||||
if (this.mapsService && !this.controlOption.collapsed) {
|
||||
this.expand();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private onLayerChange(e: any) {
|
||||
if (!this.handlingClick) {
|
||||
this.update();
|
||||
}
|
||||
|
||||
const obj = this.layerService.getLayer(e.target.layerId);
|
||||
|
||||
// @ts-ignore
|
||||
const type = obj?.overlay
|
||||
? e.type === 'add'
|
||||
? 'overlayadd'
|
||||
: 'overlayremove'
|
||||
: e.type === 'add'
|
||||
? 'baselayerchange'
|
||||
: null;
|
||||
|
||||
if (type) {
|
||||
this.emit(type, obj);
|
||||
}
|
||||
}
|
||||
|
||||
private createRadioElement(name: string, checked: boolean): ChildNode {
|
||||
const radioHtml =
|
||||
'<input type="radio" class="l7-control-layers-selector" name="' +
|
||||
name +
|
||||
'"' +
|
||||
(checked ? ' checked="checked"' : '') +
|
||||
'/>';
|
||||
|
||||
const radioFragment = document.createElement('div');
|
||||
radioFragment.innerHTML = radioHtml;
|
||||
|
||||
return radioFragment.firstChild as ChildNode;
|
||||
}
|
||||
|
||||
private addItem(obj: any) {
|
||||
const label = document.createElement('label');
|
||||
const layer = this.layerService.getLayer(obj.layer.id);
|
||||
const checked = layer && layer.inited && obj.layer.isVisible();
|
||||
let input: IInputItem;
|
||||
if (obj.overlay) {
|
||||
input = document.createElement('input') as IInputItem;
|
||||
input.type = 'checkbox';
|
||||
input.className = 'l7-control-layers-selector';
|
||||
input.defaultChecked = checked;
|
||||
} else {
|
||||
input = this.createRadioElement('l7-base-layers', checked) as IInputItem;
|
||||
}
|
||||
this.layerControlInputs.push(input);
|
||||
input.layerId = obj.layer.id;
|
||||
input.addEventListener('click', this.onInputClick);
|
||||
|
||||
const name = document.createElement('span');
|
||||
name.innerHTML = ' ' + obj.name;
|
||||
|
||||
const holder = document.createElement('div');
|
||||
|
||||
label.appendChild(holder);
|
||||
holder.appendChild(input);
|
||||
holder.appendChild(name);
|
||||
|
||||
const container = obj.overlay ? this.overlaysList : this.baseLayersList;
|
||||
container.appendChild(label);
|
||||
|
||||
this.checkDisabledLayers();
|
||||
return label;
|
||||
}
|
||||
|
||||
private onInputClick() {
|
||||
const inputs = this.layerControlInputs;
|
||||
let input;
|
||||
let layer;
|
||||
const addedLayers = [];
|
||||
const removedLayers = [];
|
||||
this.handlingClick = true;
|
||||
for (let i = inputs.length - 1; i >= 0; i--) {
|
||||
input = inputs[i];
|
||||
layer = this.layerService.getLayer(input.layerId);
|
||||
if (input.checked) {
|
||||
addedLayers.push(layer);
|
||||
} else if (!input.checked) {
|
||||
removedLayers.push(layer);
|
||||
}
|
||||
}
|
||||
removedLayers.forEach((l: any) => {
|
||||
l.hide();
|
||||
});
|
||||
addedLayers.forEach((l: any) => {
|
||||
l.show();
|
||||
});
|
||||
|
||||
this.handlingClick = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import { ILayer } from '@antv/l7-core';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
import SelectControl, {
|
||||
ISelectControlOption,
|
||||
ControlOptionItem,
|
||||
} from './baseControl/selectControl';
|
||||
|
||||
export interface ILayerControlOption extends ISelectControlOption {
|
||||
layers: ILayer[];
|
||||
}
|
||||
|
||||
export { LayerControl };
|
||||
|
||||
export default class LayerControl extends SelectControl<ILayerControlOption> {
|
||||
protected get layers() {
|
||||
return this.controlOption.layers || this.layerService.getLayers() || [];
|
||||
}
|
||||
|
||||
public getDefault(
|
||||
option?: Partial<ILayerControlOption>,
|
||||
): ILayerControlOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
title: '图层控制',
|
||||
btnIcon: createL7Icon('l7-icon-layer'),
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
public getLayerVisible() {
|
||||
return this.layers
|
||||
.filter((layer) => {
|
||||
return layer.isVisible();
|
||||
})
|
||||
.map((layer) => {
|
||||
return layer.name;
|
||||
});
|
||||
}
|
||||
|
||||
public getLayerOptions(): ControlOptionItem[] {
|
||||
return this.layers.map((layer: ILayer) => {
|
||||
return {
|
||||
text: layer.name,
|
||||
value: layer.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public setOptions(option: Partial<ILayerControlOption>) {
|
||||
const isLayerChange = this.checkUpdateOption(option, ['layers']);
|
||||
if (isLayerChange) {
|
||||
this.unbindLayerVisibleCallback();
|
||||
}
|
||||
super.setOptions(option);
|
||||
if (isLayerChange) {
|
||||
this.bindLayerVisibleCallback();
|
||||
this.selectValue = this.getLayerVisible();
|
||||
this.controlOption.options = this.getLayerOptions();
|
||||
this.popper.setContent(this.getPopperContent(this.controlOption.options));
|
||||
}
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
if (!this.controlOption.options?.length) {
|
||||
this.controlOption.options = this.getLayerOptions();
|
||||
}
|
||||
if (!this.controlOption.defaultValue) {
|
||||
this.controlOption.defaultValue = this.getLayerVisible();
|
||||
}
|
||||
this.on('selectChange', this.onSelectChange);
|
||||
this.layerService.on('layerChange', this.onLayerChange);
|
||||
this.bindLayerVisibleCallback();
|
||||
return super.onAdd();
|
||||
}
|
||||
|
||||
public bindLayerVisibleCallback = () => {
|
||||
this.layers.forEach((layer) => {
|
||||
layer.on('show', this.onLayerVisibleChane);
|
||||
layer.on('hide', this.onLayerVisibleChane);
|
||||
});
|
||||
};
|
||||
|
||||
public unbindLayerVisibleCallback = () => {
|
||||
this.layers.forEach((layer) => {
|
||||
layer.off('show', this.onLayerVisibleChane);
|
||||
layer.off('hide', this.onLayerVisibleChane);
|
||||
});
|
||||
};
|
||||
|
||||
public onRemove() {
|
||||
this.off('selectChange', this.onSelectChange);
|
||||
this.layerService.off('layerChange', this.onLayerChange);
|
||||
this.unbindLayerVisibleCallback();
|
||||
}
|
||||
|
||||
protected onLayerChange = () => {
|
||||
if (this.controlOption.layers?.length) {
|
||||
return;
|
||||
}
|
||||
this.selectValue = this.getLayerVisible();
|
||||
this.setOptions({
|
||||
options: this.getLayerOptions(),
|
||||
});
|
||||
};
|
||||
|
||||
protected onLayerVisibleChane = () => {
|
||||
this.setSelectValue(this.getLayerVisible());
|
||||
};
|
||||
|
||||
protected onSelectChange = () => {
|
||||
this.layers.forEach((layer) => {
|
||||
const needShow = this.selectValue.includes(layer.name);
|
||||
const isShow = layer.isVisible();
|
||||
if (needShow && !isShow) {
|
||||
layer.show();
|
||||
}
|
||||
if (!needShow && isShow) {
|
||||
layer.hide();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
protected getIsMultiple(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,30 +1,63 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import Control, { PositionType } from './BaseControl';
|
||||
import { Control, IControlOption, PositionType } from './baseControl';
|
||||
|
||||
export default class Logo extends Control {
|
||||
public getDefault() {
|
||||
export interface ILogoControlOption extends IControlOption {
|
||||
// Logo 展示的图片 url
|
||||
img: string;
|
||||
// 点击 Logo 跳转的超链接,不传或传 '' | null 则纯展示 Logo,点击不跳转
|
||||
href?: string | null;
|
||||
}
|
||||
|
||||
export { Logo };
|
||||
|
||||
export default class Logo extends Control<ILogoControlOption> {
|
||||
public getDefault(): ILogoControlOption {
|
||||
return {
|
||||
position: PositionType.BOTTOMLEFT,
|
||||
name: 'logo',
|
||||
href: 'https://l7.antv.vision/',
|
||||
img:
|
||||
'https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*GRb1TKp4HcMAAAAAAAAAAAAAARQnAQ',
|
||||
};
|
||||
}
|
||||
|
||||
public onAdd() {
|
||||
const className = 'l7-control-logo';
|
||||
const container = DOM.create('div', className);
|
||||
const anchor: HTMLLinkElement = DOM.create(
|
||||
'a',
|
||||
'l7-ctrl-logo',
|
||||
) as HTMLLinkElement;
|
||||
anchor.target = '_blank';
|
||||
anchor.rel = 'noopener nofollow';
|
||||
anchor.href = 'https://antv.alipay.com/l7';
|
||||
anchor.setAttribute('aria-label', 'AntV logo');
|
||||
anchor.setAttribute('rel', 'noopener nofollow');
|
||||
container.appendChild(anchor);
|
||||
const container = DOM.create('div', 'l7-control-logo');
|
||||
this.setLogoContent(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public setOptions(option: Partial<ILogoControlOption>) {
|
||||
super.setOptions(option);
|
||||
if (this.checkUpdateOption(option, ['img', 'href'])) {
|
||||
DOM.clearChildren(this.container);
|
||||
this.setLogoContent(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
protected setLogoContent(container: HTMLElement) {
|
||||
const { href, img } = this.controlOption;
|
||||
const imgDOM = DOM.create('img') as HTMLElement;
|
||||
imgDOM.setAttribute('src', img);
|
||||
imgDOM.setAttribute('aria-label', 'AntV logo');
|
||||
DOM.setUnDraggable(imgDOM);
|
||||
if (href) {
|
||||
const anchorDOM = DOM.create(
|
||||
'a',
|
||||
'l7-control-logo-link',
|
||||
) as HTMLLinkElement;
|
||||
anchorDOM.target = '_blank';
|
||||
anchorDOM.href = href;
|
||||
anchorDOM.rel = 'noopener nofollow';
|
||||
anchorDOM.setAttribute('rel', 'noopener nofollow');
|
||||
anchorDOM.appendChild(imgDOM);
|
||||
container.appendChild(anchorDOM);
|
||||
} else {
|
||||
container.appendChild(imgDOM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import { GaodeMapStyleConfig, MapboxMapStyleConfig } from '../constants';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
import SelectControl, {
|
||||
ControlOptionItem,
|
||||
ISelectControlOption,
|
||||
} from './baseControl/selectControl';
|
||||
|
||||
export { MapTheme };
|
||||
|
||||
export default class MapTheme extends SelectControl<ISelectControlOption> {
|
||||
public getDefault(
|
||||
option?: Partial<ISelectControlOption>,
|
||||
): ISelectControlOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
title: '地图样式',
|
||||
btnIcon: createL7Icon('l7-icon-color'),
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
|
||||
public getStyleOptions(): ControlOptionItem[] {
|
||||
const mapStyleConfig =
|
||||
this.mapsService.getType() === 'mapbox'
|
||||
? MapboxMapStyleConfig
|
||||
: GaodeMapStyleConfig;
|
||||
return Object.entries(this.mapsService.getMapStyleConfig())
|
||||
.filter(([key, value]) => typeof value === 'string' && key !== 'blank')
|
||||
.map(([key, value]) => {
|
||||
// @ts-ignore
|
||||
const { text, img } = mapStyleConfig[key] ?? {};
|
||||
return {
|
||||
text: text ?? key,
|
||||
value,
|
||||
img,
|
||||
key,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public getMapStyle() {
|
||||
return this.mapsService.getMapStyle();
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
if (!this.controlOption.options?.length) {
|
||||
this.controlOption.options = this.getStyleOptions();
|
||||
}
|
||||
if (this.controlOption.defaultValue) {
|
||||
const defaultValue = this.controlOption.defaultValue as string;
|
||||
this.controlOption.defaultValue =
|
||||
this.controlOption.options.find((item) => item.key === defaultValue)
|
||||
?.value ?? defaultValue;
|
||||
} else {
|
||||
const defaultStyle = this.getMapStyle();
|
||||
if (defaultStyle) {
|
||||
this.controlOption.defaultValue = defaultStyle;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
this.mapsService.map.once('styledata', () => {
|
||||
const mapboxStyle = this.mapsService.getMapStyle();
|
||||
this.controlOption.defaultValue = mapboxStyle;
|
||||
this.setSelectValue(mapboxStyle, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.on('selectChange', this.onMapThemeChange);
|
||||
return super.onAdd();
|
||||
}
|
||||
|
||||
protected onMapThemeChange = () => {
|
||||
this.mapsService.setMapStyle(this.selectValue[0]);
|
||||
};
|
||||
|
||||
protected getIsMultiple(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
import { ILngLat, Position, PositionType } from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import Control, { IControlOption } from './baseControl/control';
|
||||
|
||||
export interface IMouseLocationControlOption extends IControlOption {
|
||||
transform: (position: Position) => Position;
|
||||
}
|
||||
|
||||
export { MouseLocation };
|
||||
|
||||
export default class MouseLocation extends Control<
|
||||
IMouseLocationControlOption
|
||||
> {
|
||||
protected location: Position = [0, 0];
|
||||
|
||||
public getLocation() {
|
||||
return this.location;
|
||||
}
|
||||
|
||||
public getDefault(
|
||||
option?: Partial<IMouseLocationControlOption>,
|
||||
): IMouseLocationControlOption {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
position: PositionType.BOTTOMLEFT,
|
||||
transform: ([lng, lat]) => {
|
||||
return [+(+lng).toFixed(6), +(+lat).toFixed(6)];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
const container = DOM.create('div', 'l7-control-mouse-location');
|
||||
container.innerHTML = ' ';
|
||||
this.mapsService.on('mousemove', this.onMouseMove);
|
||||
return container;
|
||||
}
|
||||
|
||||
public onRemove(): void {
|
||||
this.mapsService.off('mousemove', this.onMouseMove);
|
||||
}
|
||||
protected onMouseMove = (e: any) => {
|
||||
let position: Position = this.location;
|
||||
const lngLat: ILngLat | undefined = e.lngLat || e.lnglat;
|
||||
const { transform } = this.controlOption;
|
||||
if (lngLat) {
|
||||
position = [lngLat.lng, lngLat.lat];
|
||||
}
|
||||
this.location = position;
|
||||
if (transform) {
|
||||
position = transform(position);
|
||||
}
|
||||
this.insertLocation2HTML(position);
|
||||
this.emit('locationChange', position);
|
||||
};
|
||||
|
||||
protected insertLocation2HTML(position: Position) {
|
||||
this.container.innerText = position.join(', ');
|
||||
}
|
||||
}
|
|
@ -1,39 +1,43 @@
|
|||
import { bindAll, DOM, lnglatDistance } from '@antv/l7-utils';
|
||||
import { IScaleControlOption } from '../interface';
|
||||
import Control, { PositionType } from './BaseControl';
|
||||
import { DOM, lnglatDistance } from '@antv/l7-utils';
|
||||
import { Control, IControlOption, PositionType } from './baseControl';
|
||||
|
||||
export default class Scale extends Control {
|
||||
export interface IScaleControlOption extends IControlOption {
|
||||
lockWidth: boolean;
|
||||
maxWidth: number;
|
||||
metric: boolean;
|
||||
updateWhenIdle: boolean;
|
||||
imperial: boolean;
|
||||
}
|
||||
|
||||
export { Scale };
|
||||
|
||||
export default class Scale extends Control<IScaleControlOption> {
|
||||
private mScale: HTMLElement;
|
||||
private iScale: HTMLElement;
|
||||
constructor(cfg?: Partial<IScaleControlOption>) {
|
||||
super(cfg);
|
||||
bindAll(['update'], this);
|
||||
}
|
||||
|
||||
public getDefault() {
|
||||
public getDefault(option: Partial<IScaleControlOption>) {
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
name: 'scale',
|
||||
position: PositionType.BOTTOMLEFT,
|
||||
maxWidth: 100,
|
||||
metric: true,
|
||||
updateWhenIdle: false,
|
||||
imperial: false,
|
||||
name: 'scale',
|
||||
lockWidth: true,
|
||||
};
|
||||
}
|
||||
|
||||
public onAdd() {
|
||||
const className = 'l7-control-scale';
|
||||
const container = DOM.create('div', className);
|
||||
this.addScales(className + '-line', container);
|
||||
this.resetScaleLines(container);
|
||||
const { updateWhenIdle } = this.controlOption;
|
||||
// TODO: 高德地图和MapBox地图事件不一致问题
|
||||
// 高德zoomchange
|
||||
this.mapsService.on(updateWhenIdle ? 'moveend' : 'mapmove', this.update);
|
||||
this.mapsService.on(updateWhenIdle ? 'zoomend' : 'zoomchange', this.update);
|
||||
this.update();
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
const { updateWhenIdle } = this.controlOption;
|
||||
this.mapsService.off(
|
||||
|
@ -42,7 +46,23 @@ export default class Scale extends Control {
|
|||
);
|
||||
this.mapsService.off(updateWhenIdle ? 'moveend' : 'mapmove', this.update);
|
||||
}
|
||||
public update() {
|
||||
|
||||
public setOptions(newOption: Partial<IScaleControlOption>) {
|
||||
super.setOptions(newOption);
|
||||
if (
|
||||
this.checkUpdateOption(newOption, [
|
||||
'lockWidth',
|
||||
'maxWidth',
|
||||
'metric',
|
||||
'updateWhenIdle',
|
||||
'imperial',
|
||||
])
|
||||
) {
|
||||
this.resetScaleLines(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
public update = () => {
|
||||
const mapsService = this.mapsService;
|
||||
const { maxWidth } = this.controlOption;
|
||||
const y = mapsService.getSize()[1] / 2;
|
||||
|
@ -51,7 +71,8 @@ export default class Scale extends Control {
|
|||
const p2 = mapsService.containerToLngLat([maxWidth, y]);
|
||||
const maxMeters = lnglatDistance([p1.lng, p1.lat], [p2.lng, p2.lat]);
|
||||
this.updateScales(maxMeters);
|
||||
}
|
||||
};
|
||||
|
||||
public updateScales(maxMeters: number) {
|
||||
const { metric, imperial } = this.controlOption;
|
||||
if (metric && maxMeters) {
|
||||
|
@ -61,11 +82,42 @@ export default class Scale extends Control {
|
|||
this.updateImperial(maxMeters);
|
||||
}
|
||||
}
|
||||
|
||||
private resetScaleLines(container: HTMLElement) {
|
||||
DOM.clearChildren(container);
|
||||
const { metric, imperial, maxWidth, lockWidth } = this.controlOption;
|
||||
if (lockWidth) {
|
||||
DOM.addStyle(container, `width: ${maxWidth}px`);
|
||||
}
|
||||
if (metric) {
|
||||
this.mScale = DOM.create('div', 'l7-control-scale-line', container);
|
||||
}
|
||||
if (imperial) {
|
||||
this.iScale = DOM.create('div', 'l7-control-scale-line', container);
|
||||
}
|
||||
this.update();
|
||||
}
|
||||
|
||||
private updateScale(scale: HTMLElement, text: string, ratio: number) {
|
||||
const { maxWidth } = this.controlOption;
|
||||
scale.style.width = Math.round(maxWidth * ratio) + 'px';
|
||||
scale.innerHTML = text;
|
||||
}
|
||||
private getRoundNum(num: number) {
|
||||
const pow10 = Math.pow(10, (Math.floor(num) + '').length - 1);
|
||||
let d = num / pow10;
|
||||
|
||||
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
|
||||
|
||||
return pow10 * d;
|
||||
}
|
||||
|
||||
private updateMetric(maxMeters: number) {
|
||||
const meters = this.getRoundNum(maxMeters);
|
||||
const label = meters < 1000 ? meters + ' m' : meters / 1000 + ' km';
|
||||
this.updateScale(this.mScale, label, meters / maxMeters);
|
||||
}
|
||||
|
||||
private updateImperial(maxMeters: number) {
|
||||
const maxFeet = maxMeters * 3.2808399;
|
||||
let maxMiles: number;
|
||||
|
@ -81,26 +133,4 @@ export default class Scale extends Control {
|
|||
this.updateScale(this.iScale, feet + ' ft', feet / maxFeet);
|
||||
}
|
||||
}
|
||||
private updateScale(scale: HTMLElement, text: string, ratio: number) {
|
||||
const { maxWidth } = this.controlOption;
|
||||
scale.style.width = Math.round(maxWidth * ratio) + 'px';
|
||||
scale.innerHTML = text;
|
||||
}
|
||||
private getRoundNum(num: number) {
|
||||
const pow10 = Math.pow(10, (Math.floor(num) + '').length - 1);
|
||||
let d = num / pow10;
|
||||
|
||||
d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
|
||||
|
||||
return pow10 * d;
|
||||
}
|
||||
private addScales(className: string, container: HTMLElement) {
|
||||
const { metric, imperial } = this.controlOption;
|
||||
if (metric) {
|
||||
this.mScale = DOM.create('div', className, container);
|
||||
}
|
||||
if (imperial) {
|
||||
this.iScale = DOM.create('div', className, container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,53 @@
|
|||
import { bindAll, DOM } from '@antv/l7-utils';
|
||||
import { IZoomControlOption } from '../interface';
|
||||
import Control, { PositionType } from './BaseControl';
|
||||
import { PositionType } from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { ELType } from '@antv/l7-utils/src/dom';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
import { Control, IControlOption } from './baseControl';
|
||||
|
||||
export default class Zoom extends Control {
|
||||
export interface IZoomControlOption extends IControlOption {
|
||||
zoomInText: ELType | string;
|
||||
zoomInTitle: string;
|
||||
zoomOutText: ELType | string;
|
||||
zoomOutTitle: string;
|
||||
}
|
||||
|
||||
export { Zoom };
|
||||
|
||||
export default class Zoom extends Control<IZoomControlOption> {
|
||||
private disabled: boolean;
|
||||
private zoomInButton: HTMLElement;
|
||||
private zoomOutButton: HTMLElement;
|
||||
|
||||
constructor(cfg?: Partial<IZoomControlOption>) {
|
||||
super(cfg);
|
||||
bindAll(['updateDisabled', 'zoomIn', 'zoomOut'], this);
|
||||
}
|
||||
public getDefault() {
|
||||
public getDefault(option: Partial<IZoomControlOption>) {
|
||||
return {
|
||||
position: PositionType.TOPLEFT,
|
||||
zoomInText: '+',
|
||||
zoomInTitle: 'Zoom in',
|
||||
zoomOutText: '−',
|
||||
zoomOutTitle: 'Zoom out',
|
||||
...super.getDefault(option),
|
||||
position: PositionType.BOTTOMRIGHT,
|
||||
name: 'zoom',
|
||||
zoomInText: createL7Icon('l7-icon-enlarge'),
|
||||
zoomInTitle: 'Zoom in',
|
||||
zoomOutText: createL7Icon('l7-icon-narrow'),
|
||||
zoomOutTitle: 'Zoom out',
|
||||
};
|
||||
}
|
||||
public setOptions(newOptions: Partial<IZoomControlOption>) {
|
||||
super.setOptions(newOptions);
|
||||
if (
|
||||
this.checkUpdateOption(newOptions, [
|
||||
'zoomInText',
|
||||
'zoomInTitle',
|
||||
'zoomOutText',
|
||||
'zoomOutTitle',
|
||||
])
|
||||
) {
|
||||
this.resetButtonGroup(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
public onAdd(): HTMLElement {
|
||||
const zoomName = 'l7-control-zoom';
|
||||
const container = DOM.create('div', zoomName + ' l7-bar');
|
||||
|
||||
this.zoomInButton = this.createButton(
|
||||
this.controlOption.zoomInText,
|
||||
this.controlOption.zoomInTitle,
|
||||
zoomName + '-in',
|
||||
container,
|
||||
this.zoomIn,
|
||||
);
|
||||
this.zoomOutButton = this.createButton(
|
||||
this.controlOption.zoomOutText,
|
||||
this.controlOption.zoomOutTitle,
|
||||
zoomName + '-out',
|
||||
container,
|
||||
this.zoomOut,
|
||||
);
|
||||
const container = DOM.create('div', 'l7-control-zoom');
|
||||
this.resetButtonGroup(container);
|
||||
this.mapsService.on('zoomend', this.updateDisabled);
|
||||
this.mapsService.on('zoomchange', this.updateDisabled);
|
||||
this.updateDisabled();
|
||||
return container;
|
||||
}
|
||||
|
||||
|
@ -63,46 +68,70 @@ export default class Zoom extends Control {
|
|||
return this;
|
||||
}
|
||||
|
||||
private zoomIn() {
|
||||
public zoomIn = () => {
|
||||
if (
|
||||
!this.disabled &&
|
||||
this.mapsService.getZoom() < this.mapsService.getMaxZoom()
|
||||
) {
|
||||
this.mapsService.zoomIn();
|
||||
}
|
||||
}
|
||||
private zoomOut() {
|
||||
};
|
||||
|
||||
public zoomOut = () => {
|
||||
if (
|
||||
!this.disabled &&
|
||||
this.mapsService.getZoom() > this.mapsService.getMinZoom()
|
||||
) {
|
||||
this.mapsService.zoomOut();
|
||||
}
|
||||
};
|
||||
|
||||
private resetButtonGroup(container: HTMLElement) {
|
||||
DOM.clearChildren(container);
|
||||
this.zoomInButton = this.createButton(
|
||||
this.controlOption.zoomInText,
|
||||
this.controlOption.zoomInTitle,
|
||||
'l7-button-control',
|
||||
container,
|
||||
this.zoomIn,
|
||||
);
|
||||
this.zoomOutButton = this.createButton(
|
||||
this.controlOption.zoomOutText,
|
||||
this.controlOption.zoomOutTitle,
|
||||
'l7-button-control',
|
||||
container,
|
||||
this.zoomOut,
|
||||
);
|
||||
this.updateDisabled();
|
||||
}
|
||||
|
||||
private createButton(
|
||||
html: string,
|
||||
html: ELType | string,
|
||||
tile: string,
|
||||
className: string,
|
||||
container: HTMLElement,
|
||||
fn: (...arg: any[]) => any,
|
||||
) {
|
||||
const link = DOM.create('a', className, container) as HTMLLinkElement;
|
||||
link.innerHTML = html;
|
||||
const link = DOM.create('button', className, container) as HTMLLinkElement;
|
||||
if (typeof html === 'string') {
|
||||
link.innerHTML = html;
|
||||
} else {
|
||||
link.append(html);
|
||||
}
|
||||
link.title = tile;
|
||||
link.href = 'javascript:void(0)';
|
||||
link.addEventListener('click', fn);
|
||||
return link;
|
||||
}
|
||||
private updateDisabled() {
|
||||
|
||||
private updateDisabled = () => {
|
||||
const mapsService = this.mapsService;
|
||||
const className = 'l7-disabled';
|
||||
DOM.removeClass(this.zoomInButton, className);
|
||||
DOM.removeClass(this.zoomOutButton, className);
|
||||
this.zoomInButton.removeAttribute('disabled');
|
||||
this.zoomOutButton.removeAttribute('disabled');
|
||||
if (this.disabled || mapsService.getZoom() <= mapsService.getMinZoom()) {
|
||||
DOM.addClass(this.zoomOutButton, className);
|
||||
this.zoomOutButton.setAttribute('disabled', 'true');
|
||||
}
|
||||
if (this.disabled || mapsService.getZoom() >= mapsService.getMaxZoom()) {
|
||||
DOM.addClass(this.zoomInButton, className);
|
||||
this.zoomInButton.setAttribute('disabled', 'true');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
@import 'variables.less';
|
||||
|
||||
.l7-button-control {
|
||||
min-width: @l7-btn-control-size;
|
||||
height: @l7-btn-control-size;
|
||||
background-color: @l7-control-bg-color;
|
||||
border-width: 0;
|
||||
border-radius: @l7-btn-control-border-radius;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 ((@l7-btn-control-size - @l7-btn-icon-size) / 2);
|
||||
box-shadow: @l7-control-shadow;
|
||||
line-height: 16px;
|
||||
|
||||
.l7-iconfont {
|
||||
fill: @l7-control-font-color;
|
||||
color: @l7-control-font-color;
|
||||
width: @l7-btn-icon-size;
|
||||
height: @l7-btn-icon-size;
|
||||
}
|
||||
|
||||
&.l7-button-control--row {
|
||||
padding: 0 16px 0 13px;
|
||||
|
||||
* + .l7-button-control__text {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&.l7-button-control--column {
|
||||
height: @l7-btn-column-height;
|
||||
flex-direction: column;
|
||||
.l7-iconfont {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.l7-button-control__text {
|
||||
margin-top: 3px;
|
||||
font-size: 10px;
|
||||
transform: scale(0.83333);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:disabled) {
|
||||
&:hover {
|
||||
background-color: @l7-btn-control-bg-hover-color;
|
||||
}
|
||||
&:active {
|
||||
background-color: @l7-btn-control-bg-active-color;
|
||||
}
|
||||
}
|
||||
&:disabled {
|
||||
background-color: @l7-btn-control-disabled-bg-color;
|
||||
color: @l7-btn-control-disabled-font-color;
|
||||
cursor: not-allowed;
|
||||
.l7-iconfont {
|
||||
fill: @l7-btn-control-disabled-font-color;
|
||||
color: @l7-btn-control-disabled-font-color;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @l7-btn-control-disabled-bg-color;
|
||||
}
|
||||
&:active {
|
||||
background-color: @l7-btn-control-disabled-bg-color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
@import 'variables.less';
|
||||
|
||||
.l7-control-container {
|
||||
font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
|
||||
.l7-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
float: left;
|
||||
clear: both;
|
||||
color: @l7-control-font-color;
|
||||
font-size: @l7-control-font-size;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
|
||||
&.l7-control--hide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
each(@position-list,{
|
||||
.l7-@{value} {
|
||||
@{value}: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
.l7-control:not(.l7-control--hide) {
|
||||
margin-@{value}: @l7-control-space;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
.l7-center {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&.l7-top,
|
||||
&.l7-bottom {
|
||||
width: 100%;
|
||||
}
|
||||
&.l7-left,
|
||||
&.l7-right {
|
||||
height: 100%;
|
||||
}
|
||||
.l7-control {
|
||||
margin-right: @l7-control-space;
|
||||
margin-bottom: @l7-control-space;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-row {
|
||||
flex-direction: row;
|
||||
&.l7-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
&.l7-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-column {
|
||||
flex-direction: column;
|
||||
&.l7-left {
|
||||
align-items: flex-start;
|
||||
}
|
||||
&.l7-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,567 @@
|
|||
.l7-marker-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.l7-marker {
|
||||
position: absolute !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.l7-marker-cluster {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgba(181, 226, 140, 0.6);
|
||||
background-clip: padding-box;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.l7-marker-cluster div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-top: 5px;
|
||||
margin-left: 5px;
|
||||
font: 12px 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
background-color: rgba(110, 204, 57, 0.6);
|
||||
border-radius: 15px;
|
||||
}
|
||||
.l7-marker-cluster span {
|
||||
line-height: 30px;
|
||||
}
|
||||
.l7-touch .l7-control-attribution,
|
||||
.l7-touch .l7-control-layers,
|
||||
.l7-touch .l7-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.l7-touch .l7-control-layers,
|
||||
.l7-touch .l7-bar {
|
||||
background-clip: padding-box;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.mapboxgl-ctrl-logo,
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
.l7-select-box {
|
||||
border: 3px dashed gray;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.l7-control-container {
|
||||
font: 12px/1.5 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
}
|
||||
.l7-control-container .l7-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
float: left;
|
||||
clear: both;
|
||||
color: #595959;
|
||||
font-size: 12px;
|
||||
pointer-events: visiblePainted;
|
||||
/* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.l7-control-container .l7-control.l7-control--hide {
|
||||
display: none;
|
||||
}
|
||||
.l7-control-container .l7-top {
|
||||
top: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l7-control-container .l7-top .l7-control:not(.l7-control--hide) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.l7-control-container .l7-right {
|
||||
right: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l7-control-container .l7-right .l7-control:not(.l7-control--hide) {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.l7-control-container .l7-bottom {
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l7-control-container .l7-bottom .l7-control:not(.l7-control--hide) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.l7-control-container .l7-left {
|
||||
left: 0;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l7-control-container .l7-left .l7-control:not(.l7-control--hide) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.l7-control-container .l7-center {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.l7-control-container .l7-center.l7-top,
|
||||
.l7-control-container .l7-center.l7-bottom {
|
||||
width: 100%;
|
||||
}
|
||||
.l7-control-container .l7-center.l7-left,
|
||||
.l7-control-container .l7-center.l7-right {
|
||||
height: 100%;
|
||||
}
|
||||
.l7-control-container .l7-center .l7-control {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.l7-control-container .l7-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
.l7-control-container .l7-row.l7-top {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.l7-control-container .l7-row.l7-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.l7-control-container .l7-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
.l7-control-container .l7-column.l7-left {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.l7-control-container .l7-column.l7-right {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.l7-button-control {
|
||||
min-width: 28px;
|
||||
height: 28px;
|
||||
background-color: #fff;
|
||||
border-width: 0;
|
||||
border-radius: 2px;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
line-height: 16px;
|
||||
}
|
||||
.l7-button-control .l7-iconfont {
|
||||
fill: #595959;
|
||||
color: #595959;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.l7-button-control.l7-button-control--row {
|
||||
padding: 0 16px 0 13px;
|
||||
}
|
||||
.l7-button-control.l7-button-control--row * + .l7-button-control__text {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.l7-button-control.l7-button-control--column {
|
||||
height: 44px;
|
||||
flex-direction: column;
|
||||
}
|
||||
.l7-button-control.l7-button-control--column .l7-iconfont {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.l7-button-control.l7-button-control--column .l7-button-control__text {
|
||||
margin-top: 3px;
|
||||
font-size: 10px;
|
||||
transform: scale(0.83333);
|
||||
}
|
||||
.l7-button-control:not(:disabled):hover {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
.l7-button-control:not(:disabled):active {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
.l7-button-control:disabled {
|
||||
background-color: #fafafa;
|
||||
color: #bdbdbd;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.l7-button-control:disabled .l7-iconfont {
|
||||
fill: #bdbdbd;
|
||||
color: #bdbdbd;
|
||||
}
|
||||
.l7-button-control:disabled:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
.l7-button-control:disabled:active {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
.l7-popper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
color: #595959;
|
||||
}
|
||||
.l7-popper.l7-popper-hide {
|
||||
display: none;
|
||||
}
|
||||
.l7-popper .l7-popper-content {
|
||||
min-height: 28px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.l7-popper .l7-popper-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 4px;
|
||||
border-style: solid;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.l7-popper.l7-popper-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
.l7-popper.l7-popper-left .l7-popper-arrow {
|
||||
border-left-color: #fff;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.l7-popper.l7-popper-right {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.l7-popper.l7-popper-right .l7-popper-arrow {
|
||||
border-right-color: #fff;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.l7-popper.l7-popper-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
.l7-popper.l7-popper-top .l7-popper-arrow {
|
||||
border-top-color: #fff;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.l7-popper.l7-popper-bottom {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.l7-popper.l7-popper-bottom .l7-popper-arrow {
|
||||
border-bottom-color: #fff;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.l7-popper.l7-popper-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
.l7-popper.l7-popper-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
.l7-select-control--normal {
|
||||
padding: 4px 0;
|
||||
}
|
||||
.l7-select-control--normal .l7-select-control-item {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.l7-select-control--normal .l7-select-control-item > * + * {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.l7-select-control--normal .l7-select-control-item input[type='checkbox'] {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.l7-select-control--normal .l7-select-control-item:hover {
|
||||
background-color: #f3f3f3;
|
||||
}
|
||||
.l7-select-control--image {
|
||||
padding: 12px 12px 0 12px;
|
||||
width: 474px;
|
||||
height: 320px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item {
|
||||
margin-right: 12px;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
border: 1px solid #fff;
|
||||
box-sizing: content-box;
|
||||
width: calc((100% - 36px) / 3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item img {
|
||||
width: 142px;
|
||||
height: 80px;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item input[type='checkbox'] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item .l7-select-control-item-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 26px;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item .l7-select-control-item-row > * + * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item.l7-select-control-item-active {
|
||||
border-color: #0370fe;
|
||||
}
|
||||
.l7-select-control--image .l7-select-control-item:nth-child(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
.l7-select-control-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.l7-select-control-item input[type='checkbox'] {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.l7-select-control--multiple .l7-select-control-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
.l7-control-logo {
|
||||
width: 89px;
|
||||
height: 16px;
|
||||
user-select: none;
|
||||
}
|
||||
.l7-control-logo img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.l7-control-logo .l7-control-logo-link {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.l7-control-logo .l7-control-logo-link img {
|
||||
cursor: pointer;
|
||||
}
|
||||
.l7-control-mouse-location {
|
||||
background-color: #fff;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
padding: 2px 4px;
|
||||
min-width: 130px;
|
||||
}
|
||||
.l7-control-zoom {
|
||||
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.l7-control-zoom .l7-button-control {
|
||||
box-shadow: 0 0 0;
|
||||
border-radius: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.l7-control-zoom .l7-button-control .l7-iconfont {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
.l7-control-zoom .l7-button-control:first-child {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.l7-control-scale {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.l7-control-scale .l7-control-scale-line {
|
||||
box-sizing: border-box;
|
||||
padding: 2px 5px 1px;
|
||||
overflow: hidden;
|
||||
color: #595959;
|
||||
font-size: 10px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
background: #fff;
|
||||
border: 2px solid #000;
|
||||
border-top: 0;
|
||||
transition: width 0.1s;
|
||||
}
|
||||
.l7-control-scale .l7-control-scale-line + .l7-control-scale .l7-control-scale-line {
|
||||
margin-top: -2px;
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
}
|
||||
.l7-right .l7-control-scale {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.l7-right .l7-control-scale .l7-control-scale-line {
|
||||
text-align: right;
|
||||
}
|
||||
.l7-popup {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
}
|
||||
.l7-popup.l7-popup-hide {
|
||||
display: none;
|
||||
}
|
||||
.l7-popup .l7-popup-content {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
pointer-events: auto;
|
||||
font-size: 14px;
|
||||
}
|
||||
.l7-popup .l7-popup-content .l7-popup-content__title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.l7-popup .l7-popup-content .l7-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 0 3px 0 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.l7-popup .l7-popup-tip {
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 10px solid transparent;
|
||||
}
|
||||
.l7-popup.l7-popup-anchor-bottom,
|
||||
.l7-popup.l7-popup-anchor-bottom-left,
|
||||
.l7-popup.l7-popup-anchor-bottom-right {
|
||||
-webkit-flex-direction: column-reverse;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
.l7-popup.l7-popup-anchor-top,
|
||||
.l7-popup.l7-popup-anchor-top-left,
|
||||
.l7-popup.l7-popup-anchor-top-right {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
.l7-popup.l7-popup-anchor-left {
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
.l7-popup.l7-popup-anchor-right {
|
||||
-webkit-flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
.l7-popup-anchor-top .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-top: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.l7-popup-anchor-top-left .l7-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-top: none;
|
||||
border-bottom-color: #fff;
|
||||
border-left: none;
|
||||
}
|
||||
.l7-popup-anchor-top-right .l7-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.l7-popup-anchor-bottom .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-top-color: #fff;
|
||||
border-bottom: none;
|
||||
}
|
||||
.l7-popup-anchor-bottom-left .l7-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-top-color: #fff;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
}
|
||||
.l7-popup-anchor-bottom-right .l7-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-top-color: #fff;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
.l7-popup-anchor-left .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-right-color: #fff;
|
||||
border-left: none;
|
||||
}
|
||||
.l7-popup-anchor-right .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-right: none;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.l7-popup-anchor-top-left .l7-popup-content {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
.l7-popup-anchor-top-right .l7-popup-content {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.l7-popup-anchor-bottom-left .l7-popup-content {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.l7-popup-anchor-bottom-right .l7-popup-content {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.l7-popup-track-pointer {
|
||||
display: none;
|
||||
}
|
||||
.l7-popup-track-pointer * {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
.l7-map:hover .l7-popup-track-pointer {
|
||||
display: flex;
|
||||
}
|
||||
.l7-map:active .l7-popup-track-pointer {
|
||||
display: none;
|
||||
}
|
||||
.l7-layer-popup__row {
|
||||
font-size: 12px;
|
||||
}
|
||||
.l7-layer-popup__row + .l7-layer-popup__row {
|
||||
margin-top: 4px;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
@import 'variables';
|
||||
@import 'l7';
|
||||
@import 'control';
|
||||
@import 'button';
|
||||
@import 'popper';
|
||||
@import 'select';
|
||||
@import 'logo';
|
||||
@import 'mouseLocation';
|
||||
@import 'zoom';
|
||||
@import 'scale';
|
||||
@import 'popup';
|
||||
@import 'layerPopup';
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,60 @@
|
|||
.l7-marker-container {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.l7-marker {
|
||||
position: absolute !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.l7-marker-cluster {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgba(181, 226, 140, 0.6);
|
||||
background-clip: padding-box;
|
||||
border-radius: 20px;
|
||||
}
|
||||
.l7-marker-cluster div {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
margin-top: 5px;
|
||||
margin-left: 5px;
|
||||
font: 12px 'Helvetica Neue', Arial, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
background-color: rgba(110, 204, 57, 0.6);
|
||||
border-radius: 15px;
|
||||
}
|
||||
.l7-marker-cluster span {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.l7-touch .l7-control-attribution,
|
||||
.l7-touch .l7-control-layers,
|
||||
.l7-touch .l7-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.l7-touch .l7-control-layers,
|
||||
.l7-touch .l7-bar {
|
||||
background-clip: padding-box;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
// 隐藏底图 Logo
|
||||
.mapboxgl-ctrl-logo,
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.l7-select-box {
|
||||
border: 3px dashed gray;
|
||||
border-radius: 2px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
box-sizing: border-box;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-layer-popup__row {
|
||||
font-size: 12px;
|
||||
& + & {
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-control-logo {
|
||||
width: 89px;
|
||||
height: 16px;
|
||||
user-select: none;
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.l7-control-logo-link {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
img {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-control-mouse-location {
|
||||
background-color: @l7-control-bg-color;
|
||||
border-radius: @l7-btn-control-border-radius;
|
||||
box-shadow: @l7-control-shadow;
|
||||
padding: 2px 4px;
|
||||
min-width: 130px;
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-popper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
color: @l7-control-font-color;
|
||||
&.l7-popper-hide {
|
||||
display: none;
|
||||
}
|
||||
.l7-popper-content {
|
||||
min-height: @l7-btn-control-size;
|
||||
background: @l7-popper-control-bg-color;
|
||||
border-radius: @l7-btn-control-border-radius;
|
||||
box-shadow: @l7-control-shadow;
|
||||
}
|
||||
.l7-popper-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: @l7-popper-control-arrow-size;
|
||||
border-style: solid;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
box-shadow: @l7-control-shadow;
|
||||
}
|
||||
&.l7-popper-left {
|
||||
flex-direction: row;
|
||||
.l7-popper-arrow {
|
||||
border-left-color: @l7-popper-control-bg-color;
|
||||
margin: (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size) 0;
|
||||
}
|
||||
}
|
||||
&.l7-popper-right {
|
||||
flex-direction: row-reverse;
|
||||
.l7-popper-arrow {
|
||||
border-right-color: @l7-popper-control-bg-color;
|
||||
margin: (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size) 0;
|
||||
}
|
||||
}
|
||||
&.l7-popper-top {
|
||||
flex-direction: column;
|
||||
.l7-popper-arrow {
|
||||
border-top-color: @l7-popper-control-bg-color;
|
||||
margin: 0 (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size);
|
||||
}
|
||||
}
|
||||
&.l7-popper-bottom {
|
||||
flex-direction: column-reverse;
|
||||
.l7-popper-arrow {
|
||||
border-bottom-color: @l7-popper-control-bg-color;
|
||||
margin: 0 (@l7-btn-control-size / 2 - @l7-popper-control-arrow-size);
|
||||
}
|
||||
}
|
||||
&.l7-popper-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
&.l7-popper-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-popup {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
&.l7-popup-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.l7-popup-content {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
pointer-events: auto;
|
||||
font-size: 14px;
|
||||
|
||||
.l7-popup-content__title {
|
||||
font-weight: bold;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.l7-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-radius: 0 3px 0 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-popup-tip {
|
||||
z-index: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 10px solid transparent;
|
||||
}
|
||||
|
||||
&.l7-popup-anchor-bottom,
|
||||
&.l7-popup-anchor-bottom-left,
|
||||
&.l7-popup-anchor-bottom-right {
|
||||
-webkit-flex-direction: column-reverse;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
&.l7-popup-anchor-top,
|
||||
&.l7-popup-anchor-top-left,
|
||||
&.l7-popup-anchor-top-right {
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.l7-popup-anchor-left {
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&.l7-popup-anchor-right {
|
||||
-webkit-flex-direction: row-reverse;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-popup-anchor-top .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-top: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-top-left .l7-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-top: none;
|
||||
border-bottom-color: #fff;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-top-right .l7-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-bottom .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-top-color: #fff;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-bottom-left .l7-popup-tip {
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
border-top-color: #fff;
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-bottom-right .l7-popup-tip {
|
||||
-webkit-align-self: flex-end;
|
||||
align-self: flex-end;
|
||||
border-top-color: #fff;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-left .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-right-color: #fff;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-right .l7-popup-tip {
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
border-right: none;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-top-left .l7-popup-content {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-top-right .l7-popup-content {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-bottom-left .l7-popup-content {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.l7-popup-anchor-bottom-right .l7-popup-content {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.l7-popup-track-pointer {
|
||||
display: none;
|
||||
* {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-map:hover .l7-popup-track-pointer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.l7-map:active .l7-popup-track-pointer {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-control-scale {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.l7-control-scale-line {
|
||||
box-sizing: border-box;
|
||||
padding: 2px 5px 1px;
|
||||
overflow: hidden;
|
||||
color: @l7-control-font-color;
|
||||
font-size: 10px;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
background: @l7-control-bg-color;
|
||||
border: 2px solid #000;
|
||||
border-top: 0;
|
||||
transition: width 0.1s;
|
||||
& + & {
|
||||
margin-top: -2px;
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l7-right {
|
||||
.l7-control-scale {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
.l7-control-scale-line {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
@import 'variables';
|
||||
|
||||
.l7-select-control--normal {
|
||||
padding: 4px 0;
|
||||
.l7-select-control-item {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
font-size: 12px;
|
||||
> * + * {
|
||||
margin-left: 6px;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: @l7-btn-control-bg-hover-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l7-select-control--image {
|
||||
padding: 12px 12px 0 12px;
|
||||
width: @l7-select-control-image-popper-width;
|
||||
height: 320px;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
box-sizing: border-box;
|
||||
align-items: flex-start;
|
||||
.l7-select-control-item {
|
||||
margin-right: 12px;
|
||||
border-radius: @l7-btn-control-border-radius;
|
||||
overflow: hidden;
|
||||
border: 1px solid @l7-popper-control-bg-color;
|
||||
box-sizing: content-box;
|
||||
width: calc((100% - 36px) / 3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
img {
|
||||
width: 142px;
|
||||
height: 80px;
|
||||
}
|
||||
input[type='checkbox'] {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.l7-select-control-item-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
line-height: 26px;
|
||||
> * + * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
&.l7-select-control-item-active {
|
||||
border-color: @l7-select-control-active-color;
|
||||
}
|
||||
&:nth-child(3n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.l7-select-control-item {
|
||||
cursor: pointer;
|
||||
input[type='checkbox'] {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.l7-select-control--multiple {
|
||||
.l7-select-control-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Control
|
||||
@l7-control-space: 8px;
|
||||
@l7-control-font-size: 12px;
|
||||
@l7-control-font-color: #595959;
|
||||
@l7-control-bg-color: #fff;
|
||||
@l7-control-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.15);
|
||||
|
||||
// ButtonControl
|
||||
@l7-btn-control-bg-color: @l7-control-bg-color;
|
||||
@l7-btn-control-bg-hover-color: #f3f3f3;
|
||||
@l7-btn-control-bg-active-color: @l7-btn-control-bg-hover-color;
|
||||
@l7-btn-control-size: 28px;
|
||||
@l7-btn-icon-size: 16px;
|
||||
@l7-btn-control-border-radius: 2px;
|
||||
@l7-btn-control-disabled-bg-color: #fafafa;
|
||||
@l7-btn-control-disabled-font-color: #bdbdbd;
|
||||
@l7-btn-border-color: #f0f0f0;
|
||||
@l7-btn-column-height: 44px;
|
||||
|
||||
// PopperControl
|
||||
@l7-popper-control-bg-color: @l7-btn-control-bg-color;
|
||||
@l7-popper-control-arrow-size: 4px;
|
||||
|
||||
// SelectControl
|
||||
@l7-select-control-active-color: #0370fe;
|
||||
@l7-select-control-image-popper-width: 474px;
|
||||
|
||||
@position-list: top, right, bottom, left;
|
|
@ -0,0 +1,21 @@
|
|||
@import 'variables';
|
||||
|
||||
@zoom-icon-size: 14px;
|
||||
|
||||
.l7-control-zoom {
|
||||
box-shadow: @l7-control-shadow;
|
||||
border-radius: @l7-btn-control-border-radius;
|
||||
overflow: hidden;
|
||||
.l7-button-control {
|
||||
box-shadow: 0 0 0;
|
||||
border-radius: 0;
|
||||
font-size: @l7-btn-icon-size;
|
||||
.l7-iconfont {
|
||||
width: @zoom-icon-size;
|
||||
height: @zoom-icon-size;
|
||||
}
|
||||
&:first-child {
|
||||
border-bottom: 1px solid @l7-btn-border-color;
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 8.2 KiB |
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Created with Vectornator (http://vectornator.io/) -->
|
||||
<svg height="100%" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 682.67 682.67" width="100%" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:vectornator="http://vectornator.io" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs/>
|
||||
<clipPath id="ArtboardFrame">
|
||||
<rect height="682.67" width="682.67" x="0" y="0"/>
|
||||
</clipPath>
|
||||
<g clip-path="url(#ArtboardFrame)" id="Untitled" vectornator:layerName="Untitled">
|
||||
<path d="M0 477.87L0 580.27C0.00552233 636.822 45.8483 682.664 102.4 682.67L204.8 682.67C223.424 682.351 238.356 667.162 238.356 648.535C238.356 629.908 223.424 614.719 204.8 614.4L102.4 614.4C83.5505 614.4 68.27 599.119 68.27 580.27L68.27 477.87C68.4812 465.535 62.0216 454.046 51.3734 447.817C40.7252 441.588 27.5448 441.588 16.8966 447.817C6.24842 454.046-0.211108 465.535 0 477.87ZM477.87 682.67L580.27 682.67C636.822 682.664 682.664 636.822 682.67 580.27L682.67 477.87C682.881 465.535 676.422 454.046 665.773 447.817C655.125 441.588 641.945 441.588 631.297 447.817C620.648 454.046 614.189 465.535 614.4 477.87L614.4 580.27C614.4 599.119 599.119 614.4 580.27 614.4L477.87 614.4C465.535 614.189 454.046 620.648 447.817 631.297C441.588 641.945 441.588 655.125 447.817 665.773C454.046 676.422 465.535 682.881 477.87 682.67ZM682.67 204.8L682.67 102.4C682.664 45.8483 636.822 0.00553344 580.27 1.15748e-06L477.87 1.15748e-06C465.535-0.211139 454.046 6.24838 447.817 16.8966C441.588 27.5448 441.588 40.7252 447.817 51.3734C454.046 62.0216 465.535 68.4811 477.87 68.27L580.27 68.27C599.119 68.27 614.4 83.5505 614.4 102.4L614.4 204.8C614.719 223.424 629.908 238.356 648.535 238.356C667.162 238.356 682.351 223.424 682.67 204.8ZM204.8 0L102.4 0C45.8483 0.00551165 0.00552271 45.8483-3.66902e-07 102.4L-3.66902e-07 204.8C0.318807 223.424 15.5078 238.356 34.135 238.356C52.7622 238.356 67.9512 223.424 68.27 204.8L68.27 102.4C68.27 83.5505 83.5505 68.27 102.4 68.27L204.8 68.27C223.424 67.9512 238.356 52.7622 238.356 34.135C238.356 15.5078 223.424 0.318804 204.8 0Z" fill="#333333" fill-rule="nonzero" opacity="1" stroke="none"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -1,16 +1,23 @@
|
|||
import Control from './control/BaseControl';
|
||||
import Layers from './control/layer';
|
||||
import Logo from './control/logo';
|
||||
import Scale from './control/scale';
|
||||
import Zoom from './control/zoom';
|
||||
import Marker from './marker';
|
||||
import MarkerLayer from './marker-layer';
|
||||
import Popup from './popup';
|
||||
|
||||
import './assets/iconfont/iconfont.js';
|
||||
// 引入样式
|
||||
// TODO: 使用 Less 或者 Sass,每个组件单独引用自身样式
|
||||
import './css/l7.css';
|
||||
import './css/index.css';
|
||||
|
||||
export { Control, Logo, Scale, Zoom, Layers, Marker, Popup, MarkerLayer };
|
||||
export * from './control/baseControl';
|
||||
export * from './control/logo';
|
||||
export * from './control/fullscreen';
|
||||
export * from './control/exportImage';
|
||||
export * from './control/geoLocate';
|
||||
export * from './control/mapTheme';
|
||||
export * from './control/layerControl';
|
||||
export * from './control/mouseLocation';
|
||||
export * from './control/zoom';
|
||||
export * from './control/scale';
|
||||
export * from './popup/popup';
|
||||
export * from './popup/layerPopup';
|
||||
|
||||
export { Marker, MarkerLayer };
|
||||
|
||||
export * from './interface';
|
||||
|
|
|
@ -1,27 +1,4 @@
|
|||
import { IControlOption } from '@antv/l7-core';
|
||||
|
||||
export interface ILayerControlOption extends IControlOption {
|
||||
collapsed: boolean;
|
||||
autoZIndex: boolean;
|
||||
hideSingleBase: boolean;
|
||||
sortLayers: boolean;
|
||||
|
||||
sortFunction: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
export interface IScaleControlOption extends IControlOption {
|
||||
maxWidth: number;
|
||||
metric: boolean;
|
||||
updateWhenIdle: boolean;
|
||||
imperial: boolean;
|
||||
}
|
||||
|
||||
export interface IZoomControlOption extends IControlOption {
|
||||
zoomInText: string;
|
||||
zoomInTitle: string;
|
||||
zoomOutText: string;
|
||||
zoomOutTitle: string;
|
||||
}
|
||||
export type ControlEvent = 'show' | 'hide' | 'add' | 'remove' | string;
|
||||
|
||||
export interface IMarkerStyleOption {
|
||||
element?: (...args: any[]) => any;
|
||||
|
|
|
@ -1,262 +0,0 @@
|
|||
import {
|
||||
ILngLat,
|
||||
IMapService,
|
||||
IPopup,
|
||||
IPopupOption,
|
||||
ISceneService,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import {
|
||||
anchorTranslate,
|
||||
anchorType,
|
||||
applyAnchorClass,
|
||||
bindAll,
|
||||
DOM,
|
||||
} from '@antv/l7-utils';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
|
||||
/** colse event */
|
||||
|
||||
export default class Popup extends EventEmitter implements IPopup {
|
||||
private popupOption: IPopupOption;
|
||||
private mapsService: IMapService<unknown>;
|
||||
private sceneSerive: ISceneService;
|
||||
private lngLat: ILngLat;
|
||||
private content: HTMLElement;
|
||||
private closeButton: HTMLElement;
|
||||
private timeoutInstance: any;
|
||||
private container: HTMLElement;
|
||||
private tip: HTMLElement;
|
||||
private scene: Container;
|
||||
|
||||
constructor(cfg?: Partial<IPopupOption>) {
|
||||
super();
|
||||
this.popupOption = {
|
||||
...this.getdefault(),
|
||||
...cfg,
|
||||
};
|
||||
bindAll(['update', 'onClickClose', 'remove'], this);
|
||||
}
|
||||
|
||||
public addTo(scene: Container) {
|
||||
this.mapsService = scene.get<IMapService>(TYPES.IMapService);
|
||||
this.sceneSerive = scene.get<ISceneService>(TYPES.ISceneService);
|
||||
this.mapsService.on('camerachange', this.update);
|
||||
this.mapsService.on('viewchange', this.update);
|
||||
this.scene = scene;
|
||||
this.update();
|
||||
if (this.popupOption.closeOnClick) {
|
||||
this.timeoutInstance = setTimeout(() => {
|
||||
this.mapsService.on('click', this.onClickClose);
|
||||
}, 30);
|
||||
}
|
||||
this.emit('open');
|
||||
return this;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.remove();
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.addTo(this.scene);
|
||||
}
|
||||
|
||||
public setHTML(html: string) {
|
||||
const frag = window.document.createDocumentFragment();
|
||||
const temp = window.document.createElement('body');
|
||||
let child: ChildNode | null;
|
||||
temp.innerHTML = html;
|
||||
while (true) {
|
||||
child = temp.firstChild;
|
||||
if (!child) {
|
||||
break;
|
||||
}
|
||||
frag.appendChild(child);
|
||||
}
|
||||
|
||||
return this.setDOMContent(frag);
|
||||
}
|
||||
|
||||
public setLnglat(lngLat: ILngLat | number[]): this {
|
||||
this.lngLat = lngLat as ILngLat;
|
||||
if (Array.isArray(lngLat)) {
|
||||
this.lngLat = {
|
||||
lng: lngLat[0],
|
||||
lat: lngLat[1],
|
||||
};
|
||||
}
|
||||
if (this.mapsService) {
|
||||
this.mapsService.on('camerachange', this.update);
|
||||
this.mapsService.on('viewchange', this.update);
|
||||
}
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
public getLnglat(): ILngLat {
|
||||
return this.lngLat;
|
||||
}
|
||||
public setText(text: string) {
|
||||
return this.setDOMContent(window.document.createTextNode(text));
|
||||
}
|
||||
|
||||
public setMaxWidth(maxWidth: string): this {
|
||||
this.popupOption.maxWidth = maxWidth;
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
|
||||
public setDOMContent(htmlNode: ChildNode | DocumentFragment) {
|
||||
this.createContent();
|
||||
this.content.appendChild(htmlNode);
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
|
||||
// 移除popup
|
||||
public remove() {
|
||||
if (this.content) {
|
||||
this.removeDom(this.content);
|
||||
}
|
||||
|
||||
if (this.container) {
|
||||
this.removeDom(this.container);
|
||||
// @ts-ignore
|
||||
delete this.container;
|
||||
}
|
||||
if (this.mapsService) {
|
||||
// TODO: mapbox AMap 事件同步
|
||||
this.mapsService.off('camerachange', this.update);
|
||||
this.mapsService.off('viewchange', this.update);
|
||||
this.mapsService.off('click', this.onClickClose);
|
||||
// @ts-ignore
|
||||
delete this.mapsService;
|
||||
}
|
||||
clearTimeout(this.timeoutInstance);
|
||||
this.emit('close');
|
||||
return this;
|
||||
}
|
||||
public isOpen() {
|
||||
return !!this.mapsService;
|
||||
}
|
||||
|
||||
private createContent() {
|
||||
if (this.content) {
|
||||
DOM.remove(this.content);
|
||||
}
|
||||
this.content = DOM.create('div', 'l7-popup-content', this.container);
|
||||
if (this.popupOption.closeButton) {
|
||||
this.closeButton = DOM.create(
|
||||
'button',
|
||||
'l7-popup-close-button',
|
||||
this.content,
|
||||
);
|
||||
|
||||
if (this.popupOption.closeButtonOffsets) {
|
||||
// 关闭按钮的偏移
|
||||
this.closeButton.style.right =
|
||||
this.popupOption.closeButtonOffsets[0] + 'px';
|
||||
this.closeButton.style.top =
|
||||
this.popupOption.closeButtonOffsets[1] + 'px';
|
||||
}
|
||||
|
||||
// this.closeButton.type = 'button';
|
||||
this.closeButton.setAttribute('aria-label', 'Close popup');
|
||||
this.closeButton.innerHTML = '×';
|
||||
this.closeButton.addEventListener('click', this.onClickClose);
|
||||
}
|
||||
}
|
||||
|
||||
private creatDom(tagName: string, className: string, container: HTMLElement) {
|
||||
const el = window.document.createElement(tagName);
|
||||
if (className !== undefined) {
|
||||
el.className = className;
|
||||
}
|
||||
if (container) {
|
||||
container.appendChild(el);
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
private removeDom(node: ChildNode) {
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
private getdefault() {
|
||||
return {
|
||||
closeButton: true,
|
||||
closeOnClick: true,
|
||||
maxWidth: '240px',
|
||||
offsets: [0, 0],
|
||||
anchor: anchorType.BOTTOM,
|
||||
className: '',
|
||||
stopPropagation: true,
|
||||
};
|
||||
}
|
||||
|
||||
private onClickClose(e: Event) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.remove();
|
||||
}
|
||||
|
||||
private update() {
|
||||
const hasPosition = this.lngLat;
|
||||
const { className, maxWidth, anchor } = this.popupOption;
|
||||
if (!this.mapsService || !hasPosition || !this.content) {
|
||||
return;
|
||||
}
|
||||
const popupContainer = this.mapsService.getMarkerContainer();
|
||||
if (!this.container && popupContainer) {
|
||||
this.container = this.creatDom(
|
||||
'div',
|
||||
'l7-popup',
|
||||
popupContainer as HTMLElement,
|
||||
);
|
||||
|
||||
this.tip = this.creatDom('div', 'l7-popup-tip', this.container);
|
||||
this.container.appendChild(this.content);
|
||||
if (className) {
|
||||
className
|
||||
.split(' ')
|
||||
.forEach((name) => this.container.classList.add(name));
|
||||
}
|
||||
|
||||
// 高德地图需要阻止事件冒泡 // 测试mapbox 地图不需要添加
|
||||
const { stopPropagation } = this.popupOption;
|
||||
if (stopPropagation) {
|
||||
['mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'].forEach(
|
||||
(type) => {
|
||||
this.container.addEventListener(type, (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.container.style.whiteSpace = 'nowrap';
|
||||
}
|
||||
if (maxWidth && this.container.style.maxWidth !== maxWidth) {
|
||||
this.container.style.maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
DOM.setTransform(this.container, `${anchorTranslate[anchor]}`);
|
||||
applyAnchorClass(this.container, anchor, 'popup');
|
||||
}
|
||||
|
||||
private updatePosition() {
|
||||
if (!this.mapsService) {
|
||||
return;
|
||||
}
|
||||
const { lng, lat } = this.lngLat;
|
||||
const { offsets } = this.popupOption;
|
||||
const pos = this.mapsService.lngLatToContainer([lng, lat]);
|
||||
this.container.style.left = pos.x + offsets[0] + 'px';
|
||||
this.container.style.top = pos.y - offsets[1] + 'px';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
import { ILayer, IPopupOption } from '@antv/l7-core';
|
||||
// @ts-ignore
|
||||
// tslint:disable-next-line:no-implicit-dependencies
|
||||
import { BaseLayer } from '@antv/l7-layers';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { Container } from 'inversify';
|
||||
import { get } from 'lodash';
|
||||
// import { Container } from 'inversify';
|
||||
import Popup from './popup';
|
||||
|
||||
export type LayerField = {
|
||||
field: string;
|
||||
formatField?: (field: string) => string;
|
||||
formatValue?: (value: any) => any;
|
||||
getValue?: (feature: any) => any;
|
||||
};
|
||||
|
||||
export type LayerPopupConfigItem = {
|
||||
layer: BaseLayer | string;
|
||||
fields: Array<LayerField | string>;
|
||||
};
|
||||
|
||||
export interface ILayerPopupOption extends IPopupOption {
|
||||
config: LayerPopupConfigItem[];
|
||||
trigger: 'hover' | 'click';
|
||||
}
|
||||
|
||||
type LayerMapInfo = {
|
||||
onMouseMove?: (layer: BaseLayer, e: any) => void;
|
||||
onMouseOut?: (layer: BaseLayer, e: any) => void;
|
||||
onClick?: (layer: BaseLayer, e: any) => void;
|
||||
onSourceUpdate?: (layer: BaseLayer) => void;
|
||||
} & Partial<LayerPopupConfigItem>;
|
||||
|
||||
export { LayerPopup };
|
||||
|
||||
export default class LayerPopup extends Popup<ILayerPopupOption> {
|
||||
/**
|
||||
* 用于保存图层对应的事件回调以及配置信息
|
||||
* @protected
|
||||
*/
|
||||
protected layerConfigMap: WeakMap<ILayer, LayerMapInfo> = new WeakMap();
|
||||
|
||||
/**
|
||||
* 当期正在展示的图层以及对应元素 id 的信息
|
||||
* @protected
|
||||
*/
|
||||
protected displayFeatureInfo?: {
|
||||
layer: ILayer;
|
||||
featureId: number;
|
||||
};
|
||||
|
||||
public addTo(scene: Container) {
|
||||
super.addTo(scene);
|
||||
this.bindLayerEvent();
|
||||
this.hide();
|
||||
return this;
|
||||
}
|
||||
|
||||
public remove() {
|
||||
super.remove();
|
||||
this.unbindLayerEvent();
|
||||
return this;
|
||||
}
|
||||
|
||||
public setOptions(option: Partial<ILayerPopupOption>) {
|
||||
this.unbindLayerEvent();
|
||||
super.setOptions(option);
|
||||
this.bindLayerEvent();
|
||||
return this;
|
||||
}
|
||||
|
||||
protected getDefault(option: Partial<ILayerPopupOption>): ILayerPopupOption {
|
||||
const isClickTrigger = option.trigger === 'click';
|
||||
|
||||
return {
|
||||
...super.getDefault(option),
|
||||
trigger: 'hover',
|
||||
followCursor: !isClickTrigger,
|
||||
lngLat: {
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
},
|
||||
offsets: [0, 10],
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
autoClose: false,
|
||||
closeOnEsc: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定对应的图层事件
|
||||
* @protected
|
||||
*/
|
||||
protected bindLayerEvent() {
|
||||
const { config, trigger } = this.popupOption;
|
||||
config.forEach((configItem) => {
|
||||
const layer = this.getLayerByConfig(configItem);
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
const layerInfo: LayerMapInfo = {
|
||||
...configItem,
|
||||
};
|
||||
|
||||
if (trigger === 'hover') {
|
||||
const onMouseMove = this.onLayerMouseMove.bind(this, layer);
|
||||
const onMouseOut = this.onLayerMouseOut.bind(this, layer);
|
||||
layerInfo.onMouseMove = onMouseMove;
|
||||
layerInfo.onMouseOut = onMouseOut;
|
||||
|
||||
layer?.on('mousemove', onMouseMove);
|
||||
layer?.on('mouseout', onMouseOut);
|
||||
} else {
|
||||
const onClick = this.onLayerClick.bind(this, layer);
|
||||
layerInfo.onClick = onClick;
|
||||
|
||||
layer?.on('click', onClick);
|
||||
}
|
||||
const source = layer.getSource();
|
||||
const onSourceUpdate = this.onSourceUpdate.bind(this, layer);
|
||||
source?.on('update', onSourceUpdate);
|
||||
layerInfo.onSourceUpdate = onSourceUpdate;
|
||||
|
||||
this.layerConfigMap.set(layer, layerInfo);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑对应的图层事件
|
||||
* @protected
|
||||
*/
|
||||
protected unbindLayerEvent() {
|
||||
const { config } = this.popupOption;
|
||||
config.forEach((configItem) => {
|
||||
const layer = this.getLayerByConfig(configItem);
|
||||
const layerInfo = layer && this.layerConfigMap.get(layer);
|
||||
if (!layerInfo) {
|
||||
return;
|
||||
}
|
||||
const { onMouseMove, onMouseOut, onClick, onSourceUpdate } = layerInfo;
|
||||
if (onMouseMove) {
|
||||
layer.off('mousemove', onMouseMove);
|
||||
}
|
||||
if (onMouseOut) {
|
||||
layer.off('mouseout', onMouseOut);
|
||||
}
|
||||
if (onClick) {
|
||||
layer.off('click', onClick);
|
||||
}
|
||||
if (onSourceUpdate) {
|
||||
layer?.getSource()?.off('update', onSourceUpdate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected onLayerMouseMove(layer: ILayer, e: any) {
|
||||
if (!this.isSameFeature(layer, e.featureId)) {
|
||||
const frag = this.getLayerInfoFrag(layer, e);
|
||||
this.setDOMContent(frag);
|
||||
this.displayFeatureInfo = {
|
||||
layer,
|
||||
featureId: e.featureId,
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isShow) {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
protected onLayerMouseOut(layer: ILayer, e: any) {
|
||||
this.displayFeatureInfo = undefined;
|
||||
if (this.isShow) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
protected onLayerClick(layer: ILayer, e: any) {
|
||||
if (this.isShow && this.isSameFeature(layer, e.featureId)) {
|
||||
this.hide();
|
||||
} else {
|
||||
const frag = this.getLayerInfoFrag(layer, e);
|
||||
this.setDOMContent(frag);
|
||||
this.setLnglat(e.lngLat);
|
||||
this.show();
|
||||
this.displayFeatureInfo = {
|
||||
layer,
|
||||
featureId: e.featureId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected onSourceUpdate(layer: ILayer) {
|
||||
if (this.displayFeatureInfo?.layer === layer) {
|
||||
this.hide();
|
||||
this.displayFeatureInfo = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过当前图层和对应选中的元素获取气泡展示的 HTML 内容
|
||||
* @param layer
|
||||
* @param e
|
||||
* @protected
|
||||
*/
|
||||
protected getLayerInfoFrag(layer: ILayer, e: any): DocumentFragment {
|
||||
const layerInfo = this.layerConfigMap.get(layer);
|
||||
const frag = document.createDocumentFragment();
|
||||
if (layerInfo) {
|
||||
let feature = e.feature;
|
||||
if (
|
||||
feature.type === 'Feature' &&
|
||||
'properties' in feature &&
|
||||
'geometry' in feature
|
||||
) {
|
||||
feature = feature.properties;
|
||||
}
|
||||
const { fields } = layerInfo;
|
||||
fields?.forEach((fieldConfig) => {
|
||||
const { field, formatField, formatValue, getValue } =
|
||||
typeof fieldConfig === 'string'
|
||||
? ({ field: fieldConfig } as any)
|
||||
: fieldConfig;
|
||||
const row = DOM.create('div', 'l7-layer-popup__row');
|
||||
const value = getValue ? getValue(e.feature) : get(feature, field);
|
||||
row.innerHTML = `${formatField ? formatField(field) : field}: ${
|
||||
formatValue ? formatValue(value) : value
|
||||
}`;
|
||||
frag.appendChild(row);
|
||||
});
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 Layer 配置访问到真实的 Layer 实例
|
||||
* @param config
|
||||
* @protected
|
||||
*/
|
||||
protected getLayerByConfig(config: LayerPopupConfigItem): ILayer | undefined {
|
||||
const layer = config.layer;
|
||||
if (layer instanceof Object) {
|
||||
return layer;
|
||||
}
|
||||
if (typeof layer === 'string') {
|
||||
return (
|
||||
this.layerService.getLayer(layer) ||
|
||||
this.layerService.getLayerByName(layer)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前展示的 Feature 是否和上一次查看的一致
|
||||
* @param layer
|
||||
* @param featureId
|
||||
* @protected
|
||||
*/
|
||||
protected isSameFeature(layer: ILayer, featureId: number) {
|
||||
const displayFeatureInfo = this.displayFeatureInfo;
|
||||
return (
|
||||
displayFeatureInfo &&
|
||||
layer === displayFeatureInfo.layer &&
|
||||
featureId === displayFeatureInfo.featureId
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,575 @@
|
|||
import {
|
||||
ILayerService,
|
||||
ILngLat,
|
||||
IMapService,
|
||||
IPopup,
|
||||
IPopupOption,
|
||||
ISceneService,
|
||||
PopupHTML,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import {
|
||||
anchorTranslate,
|
||||
anchorType,
|
||||
applyAnchorClass,
|
||||
DOM,
|
||||
} from '@antv/l7-utils';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
import { createL7Icon } from '../utils/icon';
|
||||
|
||||
export { Popup };
|
||||
|
||||
export default class Popup<O extends IPopupOption = IPopupOption>
|
||||
extends EventEmitter
|
||||
implements IPopup {
|
||||
/**
|
||||
* 配置
|
||||
* @protected
|
||||
*/
|
||||
protected popupOption: O;
|
||||
protected mapsService: IMapService;
|
||||
protected sceneService: ISceneService;
|
||||
protected layerService: ILayerService;
|
||||
protected scene: Container;
|
||||
|
||||
/**
|
||||
* 关闭按钮对应的 DOM
|
||||
* @protected
|
||||
*/
|
||||
protected closeButton?: HTMLElement | SVGElement;
|
||||
|
||||
/**
|
||||
* Popup 的总容器 DOM,包含 content 和 tip
|
||||
* @protected
|
||||
*/
|
||||
protected container: HTMLElement;
|
||||
|
||||
/**
|
||||
* popup 气泡容器
|
||||
* @protected
|
||||
*/
|
||||
protected content: HTMLElement;
|
||||
|
||||
/**
|
||||
* popup 气泡标题
|
||||
* @protected
|
||||
*/
|
||||
protected contentTitle?: HTMLElement;
|
||||
|
||||
/**
|
||||
* popup 内容容器
|
||||
* @protected
|
||||
*/
|
||||
protected contentPanel: HTMLElement;
|
||||
|
||||
/**
|
||||
* popup 内容标题
|
||||
* @protected
|
||||
*/
|
||||
protected title: HTMLElement;
|
||||
|
||||
/**
|
||||
* 气泡箭头对应的 DOM
|
||||
* @protected
|
||||
*/
|
||||
protected tip: HTMLElement;
|
||||
|
||||
/**
|
||||
* 当前是否展示
|
||||
* @protected
|
||||
*/
|
||||
protected isShow: boolean = true;
|
||||
|
||||
protected get lngLat() {
|
||||
return (
|
||||
this.popupOption.lngLat ?? {
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected set lngLat(newLngLat: ILngLat) {
|
||||
this.popupOption.lngLat = newLngLat;
|
||||
}
|
||||
|
||||
constructor(cfg?: Partial<O>) {
|
||||
super();
|
||||
this.popupOption = {
|
||||
...this.getDefault(cfg ?? {}),
|
||||
...cfg,
|
||||
};
|
||||
const { lngLat } = this.popupOption;
|
||||
if (lngLat) {
|
||||
this.lngLat = lngLat;
|
||||
}
|
||||
}
|
||||
|
||||
public getIsShow() {
|
||||
return this.isShow;
|
||||
}
|
||||
|
||||
public addTo(scene: Container) {
|
||||
this.mapsService = scene.get<IMapService>(TYPES.IMapService);
|
||||
this.sceneService = scene.get<ISceneService>(TYPES.ISceneService);
|
||||
this.layerService = scene.get<ILayerService>(TYPES.ILayerService);
|
||||
this.mapsService.on('camerachange', this.update);
|
||||
this.mapsService.on('viewchange', this.update);
|
||||
this.scene = scene;
|
||||
this.update();
|
||||
|
||||
this.updateCloseOnClick();
|
||||
this.updateCloseOnEsc();
|
||||
this.updateFollowCursor();
|
||||
|
||||
const { html, text } = this.popupOption;
|
||||
if (html) {
|
||||
this.setHTML(html);
|
||||
} else if (text) {
|
||||
this.setText(text);
|
||||
}
|
||||
this.emit('open');
|
||||
return this;
|
||||
}
|
||||
|
||||
// 移除popup
|
||||
public remove() {
|
||||
if (!this.isOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.content) {
|
||||
DOM.remove(this.content);
|
||||
}
|
||||
|
||||
if (this.container) {
|
||||
DOM.remove(this.container);
|
||||
// @ts-ignore
|
||||
delete this.container;
|
||||
}
|
||||
if (this.mapsService) {
|
||||
// TODO: mapbox AMap 事件同步
|
||||
this.mapsService.off('camerachange', this.update);
|
||||
this.mapsService.off('viewchange', this.update);
|
||||
this.updateCloseOnClick(true);
|
||||
this.updateCloseOnEsc(true);
|
||||
this.updateFollowCursor(true);
|
||||
// @ts-ignore
|
||||
delete this.mapsService;
|
||||
}
|
||||
this.emit('close');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 option 配置
|
||||
*/
|
||||
public getOptions() {
|
||||
return this.popupOption;
|
||||
}
|
||||
|
||||
public setOptions(option: Partial<O>) {
|
||||
this.popupOption = {
|
||||
...this.popupOption,
|
||||
...option,
|
||||
};
|
||||
if (
|
||||
this.checkUpdateOption(option, [
|
||||
'closeButton',
|
||||
'closeButtonOffsets',
|
||||
'maxWidth',
|
||||
'anchor',
|
||||
'stopPropagation',
|
||||
'className',
|
||||
'style',
|
||||
'lngLat',
|
||||
'offsets',
|
||||
'title',
|
||||
])
|
||||
) {
|
||||
if (this.container) {
|
||||
DOM.remove(this.container);
|
||||
// @ts-ignore
|
||||
this.container = undefined;
|
||||
}
|
||||
if (this.popupOption.html) {
|
||||
this.setHTML(this.popupOption.html);
|
||||
} else if (this.popupOption.text) {
|
||||
this.setText(this.popupOption.text);
|
||||
}
|
||||
}
|
||||
if (this.checkUpdateOption(option, ['closeOnEsc'])) {
|
||||
this.updateCloseOnEsc();
|
||||
}
|
||||
if (this.checkUpdateOption(option, ['closeOnClick'])) {
|
||||
this.updateCloseOnClick();
|
||||
}
|
||||
if (this.checkUpdateOption(option, ['followCursor'])) {
|
||||
this.updateFollowCursor();
|
||||
}
|
||||
if (this.checkUpdateOption(option, ['html']) && option.html) {
|
||||
this.setHTML(option.html);
|
||||
} else if (this.checkUpdateOption(option, ['text']) && option.text) {
|
||||
this.setText(option.text);
|
||||
}
|
||||
if (this.checkUpdateOption(option, ['lngLat']) && option.lngLat) {
|
||||
this.setLnglat(option.lngLat);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public open() {
|
||||
this.addTo(this.scene);
|
||||
return this;
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.remove();
|
||||
return this;
|
||||
}
|
||||
|
||||
public show() {
|
||||
if (this.isShow) {
|
||||
return;
|
||||
}
|
||||
if (this.container) {
|
||||
DOM.removeClass(this.container, 'l7-popup-hide');
|
||||
}
|
||||
this.isShow = true;
|
||||
this.emit('show');
|
||||
return this;
|
||||
}
|
||||
|
||||
public hide() {
|
||||
if (!this.isShow) {
|
||||
return;
|
||||
}
|
||||
if (this.container) {
|
||||
DOM.addClass(this.container, 'l7-popup-hide');
|
||||
}
|
||||
this.isShow = false;
|
||||
this.emit('hide');
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 HTML 内容
|
||||
* @param html
|
||||
*/
|
||||
public setHTML(html: PopupHTML) {
|
||||
this.popupOption.html = html;
|
||||
return this.setDOMContent(this.getPopupHTMLFragment(html));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Popup 展示文本
|
||||
* @param text
|
||||
*/
|
||||
public setText(text: string) {
|
||||
this.popupOption.text = text;
|
||||
return this.setDOMContent(window.document.createTextNode(text));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将地图自动平移到气泡位置
|
||||
*/
|
||||
public panToPopup() {
|
||||
const { lng, lat } = this.lngLat;
|
||||
if (this.popupOption.autoPan) {
|
||||
this.mapsService.panTo([lng, lat]);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public setLngLat(lngLat: ILngLat | [number, number]): this {
|
||||
return this.setLnglat(lngLat);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Popup 所在经纬度
|
||||
* @param lngLat
|
||||
*/
|
||||
public setLnglat(lngLat: ILngLat | [number, number]): this {
|
||||
this.lngLat = lngLat as ILngLat;
|
||||
if (Array.isArray(lngLat)) {
|
||||
this.lngLat = {
|
||||
lng: lngLat[0],
|
||||
lat: lngLat[1],
|
||||
};
|
||||
}
|
||||
if (this.mapsService) {
|
||||
// 防止事件重复监听
|
||||
this.mapsService.off('camerachange', this.update);
|
||||
this.mapsService.off('viewchange', this.update);
|
||||
|
||||
this.mapsService.on('camerachange', this.update);
|
||||
this.mapsService.on('viewchange', this.update);
|
||||
}
|
||||
this.update();
|
||||
if (this.popupOption.autoPan) {
|
||||
setTimeout(() => {
|
||||
this.panToPopup();
|
||||
}, 0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Popup 所在经纬度
|
||||
*/
|
||||
public getLnglat(): ILngLat {
|
||||
return this.lngLat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Popup 最大宽度
|
||||
* @param maxWidth
|
||||
*/
|
||||
public setMaxWidth(maxWidth: string): this {
|
||||
this.popupOption.maxWidth = maxWidth;
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
|
||||
public isOpen() {
|
||||
return !!this.mapsService;
|
||||
}
|
||||
|
||||
protected onMouseMove = (e: MouseEvent) => {
|
||||
const container = this.mapsService.getMapContainer();
|
||||
const { left = 0, top = 0 } = container?.getBoundingClientRect() ?? {};
|
||||
this.setPopupPosition(e.clientX - left, e.clientY - top);
|
||||
};
|
||||
|
||||
/**
|
||||
* 将经纬度转换成对应的像素偏移位置
|
||||
* @protected
|
||||
*/
|
||||
protected updateLngLatPosition = () => {
|
||||
if (!this.mapsService || this.popupOption.followCursor) {
|
||||
return;
|
||||
}
|
||||
const { lng, lat } = this.lngLat;
|
||||
const { x, y } = this.mapsService.lngLatToContainer([lng, lat]);
|
||||
this.setPopupPosition(x, y);
|
||||
};
|
||||
|
||||
protected getDefault(option: Partial<O>): O {
|
||||
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||
return {
|
||||
closeButton: true,
|
||||
closeOnClick: true,
|
||||
maxWidth: '240px',
|
||||
offsets: [0, 0],
|
||||
anchor: anchorType.BOTTOM,
|
||||
stopPropagation: true,
|
||||
autoPan: false,
|
||||
autoClose: true,
|
||||
closeOnEsc: false,
|
||||
followCursor: false,
|
||||
} as O;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Popup 内容 HTML
|
||||
* @param htmlNode
|
||||
*/
|
||||
protected setDOMContent(htmlNode: ChildNode | DocumentFragment) {
|
||||
this.createContent();
|
||||
this.contentPanel.appendChild(htmlNode);
|
||||
this.update();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定地图点击事件触发销毁 Popup
|
||||
* @protected
|
||||
*/
|
||||
protected updateCloseOnClick(onlyClear?: boolean) {
|
||||
this.mapsService.off('click', this.onCloseButtonClick);
|
||||
if (this.popupOption.closeOnClick && !onlyClear) {
|
||||
this.mapsService.on('click', this.onCloseButtonClick);
|
||||
}
|
||||
}
|
||||
|
||||
protected updateCloseOnEsc(onlyClear?: boolean) {
|
||||
window.removeEventListener('keydown', this.onKeyDown);
|
||||
if (this.popupOption.closeOnEsc && !onlyClear) {
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
}
|
||||
}
|
||||
|
||||
protected updateFollowCursor(onlyClear?: boolean) {
|
||||
const container = this.mapsService.getContainer()!;
|
||||
container.removeEventListener('mousemove', this.onMouseMove);
|
||||
if (this.popupOption.followCursor && !onlyClear) {
|
||||
container.addEventListener('mousemove', this.onMouseMove);
|
||||
}
|
||||
}
|
||||
|
||||
protected onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.keyCode === 27) {
|
||||
this.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建 Popup 内容容器的 DOM (在每次 setHTML 或 setText 时都会被调用)
|
||||
* @protected
|
||||
*/
|
||||
protected createContent() {
|
||||
if (this.content) {
|
||||
DOM.remove(this.content);
|
||||
}
|
||||
this.content = DOM.create('div', 'l7-popup-content', this.container);
|
||||
|
||||
if (this.popupOption.title) {
|
||||
this.contentTitle = DOM.create(
|
||||
'div',
|
||||
'l7-popup-content__title',
|
||||
this.content,
|
||||
);
|
||||
this.contentTitle?.append(
|
||||
this.getPopupHTMLFragment(this.popupOption.title),
|
||||
);
|
||||
} else {
|
||||
this.contentTitle = undefined;
|
||||
}
|
||||
|
||||
if (this.popupOption.closeButton) {
|
||||
const closeButton = createL7Icon('l7-icon-guanbi');
|
||||
DOM.addClass(closeButton, 'l7-popup-close-button');
|
||||
this.content.appendChild(closeButton);
|
||||
|
||||
if (this.popupOption.closeButtonOffsets) {
|
||||
// 关闭按钮的偏移
|
||||
closeButton.style.right = this.popupOption.closeButtonOffsets[0] + 'px';
|
||||
closeButton.style.top = this.popupOption.closeButtonOffsets[1] + 'px';
|
||||
}
|
||||
|
||||
// this.closeButton.type = 'button';
|
||||
closeButton.setAttribute('aria-label', 'Close popup');
|
||||
closeButton.addEventListener('click', this.onCloseButtonClick);
|
||||
|
||||
this.closeButton = closeButton;
|
||||
} else {
|
||||
this.closeButton = undefined;
|
||||
}
|
||||
|
||||
this.contentPanel = DOM.create(
|
||||
'div',
|
||||
'l7-popup-content__panel',
|
||||
this.content,
|
||||
);
|
||||
}
|
||||
|
||||
protected onCloseButtonClick = (e: Event) => {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
this.remove();
|
||||
};
|
||||
|
||||
protected update = () => {
|
||||
const hasPosition = !!this.lngLat;
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
maxWidth,
|
||||
anchor,
|
||||
stopPropagation,
|
||||
} = this.popupOption;
|
||||
if (!this.mapsService || !hasPosition || !this.content) {
|
||||
return;
|
||||
}
|
||||
const popupContainer = this.mapsService.getMarkerContainer();
|
||||
// 如果当前没有创建 Popup 容器则创建
|
||||
if (!this.container && popupContainer) {
|
||||
this.container = DOM.create(
|
||||
'div',
|
||||
`l7-popup ${className ?? ''} ${!this.isShow ? 'l7-popup-hide' : ''}`,
|
||||
popupContainer as HTMLElement,
|
||||
);
|
||||
|
||||
if (style) {
|
||||
this.container.setAttribute('style', style);
|
||||
}
|
||||
|
||||
this.tip = DOM.create('div', 'l7-popup-tip', this.container);
|
||||
this.container.appendChild(this.content);
|
||||
|
||||
// 高德地图需要阻止事件冒泡 // 测试mapbox 地图不需要添加
|
||||
if (stopPropagation) {
|
||||
['mousemove', 'mousedown', 'mouseup', 'click', 'dblclick'].forEach(
|
||||
(type) => {
|
||||
this.container.addEventListener(type, (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
this.container.style.whiteSpace = 'nowrap';
|
||||
}
|
||||
|
||||
// 设置 Popup 的最大宽度
|
||||
if (maxWidth && this.container.style.maxWidth !== maxWidth) {
|
||||
this.container.style.maxWidth = maxWidth;
|
||||
}
|
||||
|
||||
this.updateLngLatPosition();
|
||||
DOM.setTransform(this.container, `${anchorTranslate[anchor]}`);
|
||||
applyAnchorClass(this.container, anchor, 'popup');
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置 Popup 相对于地图容器的 Position
|
||||
* @param left
|
||||
* @param top
|
||||
* @protected
|
||||
*/
|
||||
protected setPopupPosition(left: number, top: number) {
|
||||
if (this.container) {
|
||||
const { offsets } = this.popupOption;
|
||||
this.container.style.left = left + offsets[0] + 'px';
|
||||
this.container.style.top = top - offsets[1] + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前传入 option 是否包含 keys 字段
|
||||
* @param option
|
||||
* @param keys
|
||||
* @protected
|
||||
*/
|
||||
protected checkUpdateOption(option: Partial<O>, keys: Array<keyof O>) {
|
||||
return keys.some((key) => key in option);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据参数 HTML 片段返回对应的 Fragment
|
||||
* @param html
|
||||
* @protected
|
||||
*/
|
||||
protected getPopupHTMLFragment(html: PopupHTML) {
|
||||
const frag = window.document.createDocumentFragment();
|
||||
const temp = window.document.createElement('body');
|
||||
let child: ChildNode | null;
|
||||
if (typeof html === 'string') {
|
||||
temp.innerHTML = html;
|
||||
} else if (Array.isArray(html)) {
|
||||
temp.append(...html);
|
||||
} else if (html instanceof HTMLElement) {
|
||||
temp.append(html);
|
||||
}
|
||||
while (true) {
|
||||
child = temp.firstChild;
|
||||
if (!child) {
|
||||
break;
|
||||
}
|
||||
frag.appendChild(child);
|
||||
}
|
||||
return frag;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
export const createL7Icon = (className: string) => {
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.classList.add('l7-iconfont');
|
||||
svg.setAttribute('aria-hidden', 'true');
|
||||
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||
use.setAttributeNS('http://www.w3.org/1999/xlink', 'href', `#${className}`);
|
||||
svg.appendChild(use);
|
||||
return svg;
|
||||
};
|
|
@ -0,0 +1,286 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
/**
|
||||
* 气泡位置枚举
|
||||
*/
|
||||
export type PopperPlacement =
|
||||
| 'top-start'
|
||||
| 'top'
|
||||
| 'top-end'
|
||||
| 'left-start'
|
||||
| 'left'
|
||||
| 'left-end'
|
||||
| 'bottom-start'
|
||||
| 'bottom'
|
||||
| 'bottom-end'
|
||||
| 'right-start'
|
||||
| 'right'
|
||||
| 'right-end';
|
||||
|
||||
/**
|
||||
* 气泡触发类型,当前支持 click 和 hover 两种类型
|
||||
*/
|
||||
export type PopperTrigger = 'click' | 'hover';
|
||||
|
||||
/**
|
||||
* 气泡内容类型
|
||||
*/
|
||||
export type PopperContent = string | HTMLElement | null;
|
||||
|
||||
export interface IPopperOption {
|
||||
placement: PopperPlacement; // 气泡展示方向
|
||||
trigger: PopperTrigger; // 气泡触发方式
|
||||
content?: PopperContent; // 初始内容
|
||||
offset?: [number, number]; // 气泡偏移
|
||||
className?: string; // 容器自定义 className
|
||||
container: HTMLElement; // 触发气泡的容器
|
||||
unique?: boolean; // 当前气泡展示时,是否关闭其他该配置为 true 的气泡
|
||||
}
|
||||
|
||||
export class Popper extends EventEmitter<'show' | 'hide'> {
|
||||
protected get buttonRect() {
|
||||
return this.button.getBoundingClientRect();
|
||||
}
|
||||
|
||||
protected static conflictPopperList: Popper[] = [];
|
||||
|
||||
// 气泡容器 DOM
|
||||
public popperDOM!: HTMLElement;
|
||||
|
||||
// 气泡中展示的内容容器 DOM
|
||||
public contentDOM!: HTMLElement;
|
||||
/**
|
||||
* 按钮实体
|
||||
* @protected
|
||||
*/
|
||||
protected button: HTMLElement;
|
||||
/**
|
||||
* Popper 配置
|
||||
* @protected
|
||||
*/
|
||||
protected option: IPopperOption;
|
||||
/**
|
||||
* 当前是否展示
|
||||
* @protected
|
||||
*/
|
||||
protected isShow: boolean = false;
|
||||
|
||||
/**
|
||||
* 当前气泡展示的内容
|
||||
* @protected
|
||||
*/
|
||||
protected content: PopperContent;
|
||||
|
||||
/**
|
||||
* 关闭气泡的定时器
|
||||
* @protected
|
||||
*/
|
||||
protected timeout: number | null = null;
|
||||
|
||||
constructor(button: HTMLElement, option: IPopperOption) {
|
||||
super();
|
||||
this.button = button;
|
||||
this.option = option;
|
||||
this.init();
|
||||
if (option.unique) {
|
||||
Popper.conflictPopperList.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
public getPopperDOM() {
|
||||
return this.popperDOM;
|
||||
}
|
||||
|
||||
public getIsShow() {
|
||||
return this.isShow;
|
||||
}
|
||||
|
||||
public getContent() {
|
||||
return this.content;
|
||||
}
|
||||
|
||||
public setContent(content: PopperContent) {
|
||||
if (typeof content === 'string') {
|
||||
this.contentDOM.innerHTML = content;
|
||||
} else if (content instanceof HTMLElement) {
|
||||
DOM.clearChildren(this.contentDOM);
|
||||
this.contentDOM.appendChild(content);
|
||||
}
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public show = () => {
|
||||
if (this.isShow || !this.contentDOM.innerHTML) {
|
||||
return this;
|
||||
}
|
||||
this.resetPopperPosition();
|
||||
DOM.removeClass(this.popperDOM, 'l7-popper-hide');
|
||||
this.isShow = true;
|
||||
|
||||
if (this.option.unique) {
|
||||
// console.log(Popper.conflictPopperList.length);
|
||||
Popper.conflictPopperList.forEach((popper) => {
|
||||
if (popper !== this && popper.isShow) {
|
||||
popper.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
this.emit('show');
|
||||
return this;
|
||||
};
|
||||
|
||||
public hide = () => {
|
||||
if (!this.isShow) {
|
||||
return this;
|
||||
}
|
||||
DOM.addClass(this.popperDOM, 'l7-popper-hide');
|
||||
this.isShow = false;
|
||||
this.emit('hide');
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置隐藏气泡的定时器
|
||||
*/
|
||||
public setHideTimeout = () => {
|
||||
if (this.timeout) {
|
||||
return;
|
||||
}
|
||||
this.timeout = window.setTimeout(() => {
|
||||
if (!this.isShow) {
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
this.timeout = null;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
/**
|
||||
* 销毁隐藏气泡的定时器
|
||||
*/
|
||||
public clearHideTimeout = () => {
|
||||
if (this.timeout) {
|
||||
window.clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
};
|
||||
|
||||
public init() {
|
||||
const { trigger } = this.option;
|
||||
this.popperDOM = this.createPopper();
|
||||
if (trigger === 'click') {
|
||||
this.button.addEventListener('click', this.onBtnClick);
|
||||
} else {
|
||||
this.button.addEventListener('mousemove', this.onBtnMouseMove);
|
||||
this.button.addEventListener('mouseleave', this.onBtnMouseLeave);
|
||||
this.popperDOM.addEventListener('mousemove', this.onBtnMouseMove);
|
||||
this.popperDOM.addEventListener('mouseleave', this.onBtnMouseLeave);
|
||||
}
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.button.removeEventListener('click', this.onBtnClick);
|
||||
this.button.removeEventListener('mousemove', this.onBtnMouseMove);
|
||||
this.button.removeEventListener('mousemove', this.onBtnMouseLeave);
|
||||
this.popperDOM.removeEventListener('mousemove', this.onBtnMouseMove);
|
||||
this.popperDOM.removeEventListener('mouseleave', this.onBtnMouseLeave);
|
||||
DOM.remove(this.popperDOM);
|
||||
}
|
||||
|
||||
public resetPopperPosition() {
|
||||
const popperStyleObj: any = {};
|
||||
const { container, offset = [0, 0], placement } = this.option;
|
||||
const [offsetX, offsetY] = offset;
|
||||
const buttonRect = this.button.getBoundingClientRect();
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const { left, right, top, bottom } = DOM.getDiffRect(
|
||||
buttonRect,
|
||||
containerRect,
|
||||
);
|
||||
let isTransformX = false;
|
||||
let isTransformY = false;
|
||||
if (/^(left|right)/.test(placement)) {
|
||||
if (placement.includes('left')) {
|
||||
popperStyleObj.right = `${buttonRect.width + right}px`;
|
||||
} else if (placement.includes('right')) {
|
||||
popperStyleObj.left = `${buttonRect.width + left}px`;
|
||||
}
|
||||
if (placement.includes('start')) {
|
||||
popperStyleObj.top = `${top}px`;
|
||||
} else if (placement.includes('end')) {
|
||||
popperStyleObj.bottom = `${bottom}px`;
|
||||
} else {
|
||||
popperStyleObj.top = `${top + buttonRect.height / 2}px`;
|
||||
isTransformY = true;
|
||||
popperStyleObj.transform = `translate(${offsetX}px, calc(${offsetY}px - 50%))`;
|
||||
}
|
||||
} else if (/^(top|bottom)/.test(placement)) {
|
||||
if (placement.includes('top')) {
|
||||
popperStyleObj.bottom = `${buttonRect.height + bottom}px`;
|
||||
} else if (placement.includes('bottom')) {
|
||||
popperStyleObj.top = `${buttonRect.height + top}px`;
|
||||
}
|
||||
if (placement.includes('start')) {
|
||||
popperStyleObj.left = `${left}px`;
|
||||
} else if (placement.includes('end')) {
|
||||
popperStyleObj.right = `${right}px`;
|
||||
} else {
|
||||
popperStyleObj.left = `${left + buttonRect.width / 2}px`;
|
||||
isTransformX = true;
|
||||
popperStyleObj.transform = `translate(calc(${offsetX}px - 50%), ${offsetY}px)`;
|
||||
}
|
||||
}
|
||||
popperStyleObj.transform = `translate(calc(${offsetX}px - ${
|
||||
isTransformX ? '50%' : '0%'
|
||||
}), calc(${offsetY}px - ${isTransformY ? '50%' : '0%'})`;
|
||||
|
||||
const posList = placement.split('-');
|
||||
if (posList.length) {
|
||||
DOM.addClass(
|
||||
this.popperDOM,
|
||||
posList.map((pos) => `l7-popper-${pos}`).join(' '),
|
||||
);
|
||||
}
|
||||
DOM.addStyle(this.popperDOM, DOM.css2Style(popperStyleObj));
|
||||
}
|
||||
|
||||
protected createPopper(): HTMLElement {
|
||||
const { container, className = '', content } = this.option;
|
||||
const popper = DOM.create(
|
||||
'div',
|
||||
`l7-popper l7-popper-hide ${className}`,
|
||||
) as HTMLElement;
|
||||
const popperContent = DOM.create('div', 'l7-popper-content') as HTMLElement;
|
||||
const popperArrow = DOM.create('div', 'l7-popper-arrow') as HTMLElement;
|
||||
popper.appendChild(popperContent);
|
||||
popper.appendChild(popperArrow);
|
||||
container.appendChild(popper);
|
||||
this.popperDOM = popper;
|
||||
this.contentDOM = popperContent;
|
||||
if (content) {
|
||||
this.setContent(content);
|
||||
}
|
||||
return popper;
|
||||
}
|
||||
|
||||
protected onBtnClick = () => {
|
||||
if (this.isShow) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
};
|
||||
|
||||
protected onBtnMouseLeave = () => {
|
||||
this.setHideTimeout();
|
||||
};
|
||||
|
||||
protected onBtnMouseMove = () => {
|
||||
this.clearHideTimeout();
|
||||
if (this.isShow) {
|
||||
return;
|
||||
}
|
||||
this.show();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
// @ts-nocheck
|
||||
|
||||
const methodMap = [
|
||||
[
|
||||
'requestFullscreen',
|
||||
'exitFullscreen',
|
||||
'fullscreenElement',
|
||||
'fullscreenEnabled',
|
||||
'fullscreenchange',
|
||||
'fullscreenerror',
|
||||
],
|
||||
// New WebKit
|
||||
[
|
||||
'webkitRequestFullscreen',
|
||||
'webkitExitFullscreen',
|
||||
'webkitFullscreenElement',
|
||||
'webkitFullscreenEnabled',
|
||||
'webkitfullscreenchange',
|
||||
'webkitfullscreenerror',
|
||||
],
|
||||
// Old WebKit
|
||||
[
|
||||
'webkitRequestFullScreen',
|
||||
'webkitCancelFullScreen',
|
||||
'webkitCurrentFullScreenElement',
|
||||
'webkitCancelFullScreen',
|
||||
'webkitfullscreenchange',
|
||||
'webkitfullscreenerror',
|
||||
],
|
||||
[
|
||||
'mozRequestFullScreen',
|
||||
'mozCancelFullScreen',
|
||||
'mozFullScreenElement',
|
||||
'mozFullScreenEnabled',
|
||||
'mozfullscreenchange',
|
||||
'mozfullscreenerror',
|
||||
],
|
||||
[
|
||||
'msRequestFullscreen',
|
||||
'msExitFullscreen',
|
||||
'msFullscreenElement',
|
||||
'msFullscreenEnabled',
|
||||
'MSFullscreenChange',
|
||||
'MSFullscreenError',
|
||||
],
|
||||
];
|
||||
|
||||
const nativeAPI = (() => {
|
||||
if (typeof document === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const unprefixedMethods = methodMap[0];
|
||||
const returnValue = {};
|
||||
|
||||
for (const methodList of methodMap) {
|
||||
const exitFullscreenMethod = methodList?.[1];
|
||||
if (exitFullscreenMethod in document) {
|
||||
for (const [index, method] of methodList.entries()) {
|
||||
returnValue[unprefixedMethods[index]] = method;
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})();
|
||||
|
||||
const eventNameMap = {
|
||||
change: nativeAPI.fullscreenchange,
|
||||
error: nativeAPI.fullscreenerror,
|
||||
};
|
||||
|
||||
let screenfull: any = {
|
||||
// eslint-disable-next-line default-param-last
|
||||
request(element = document.documentElement, options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const onFullScreenEntered = () => {
|
||||
screenfull.off('change', onFullScreenEntered);
|
||||
resolve();
|
||||
};
|
||||
|
||||
screenfull.on('change', onFullScreenEntered);
|
||||
|
||||
const returnPromise = element[nativeAPI.requestFullscreen](options);
|
||||
|
||||
if (returnPromise instanceof Promise) {
|
||||
returnPromise.then(onFullScreenEntered).catch(reject);
|
||||
}
|
||||
});
|
||||
},
|
||||
exit() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!screenfull.isFullscreen) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const onFullScreenExit = () => {
|
||||
screenfull.off('change', onFullScreenExit);
|
||||
resolve();
|
||||
};
|
||||
|
||||
screenfull.on('change', onFullScreenExit);
|
||||
|
||||
const returnPromise = document[nativeAPI.exitFullscreen]();
|
||||
|
||||
if (returnPromise instanceof Promise) {
|
||||
returnPromise.then(onFullScreenExit).catch(reject);
|
||||
}
|
||||
});
|
||||
},
|
||||
toggle(element, options) {
|
||||
return screenfull.isFullscreen
|
||||
? screenfull.exit()
|
||||
: screenfull.request(element, options);
|
||||
},
|
||||
onchange(callback) {
|
||||
screenfull.on('change', callback);
|
||||
},
|
||||
onerror(callback) {
|
||||
screenfull.on('error', callback);
|
||||
},
|
||||
on(event, callback) {
|
||||
const eventName = eventNameMap[event];
|
||||
if (eventName) {
|
||||
document.addEventListener(eventName, callback, false);
|
||||
}
|
||||
},
|
||||
off(event, callback) {
|
||||
const eventName = eventNameMap[event];
|
||||
if (eventName) {
|
||||
document.removeEventListener(eventName, callback, false);
|
||||
}
|
||||
},
|
||||
raw: nativeAPI,
|
||||
};
|
||||
|
||||
Object.defineProperties(screenfull, {
|
||||
isFullscreen: {
|
||||
get: () => Boolean(document[nativeAPI.fullscreenElement]),
|
||||
},
|
||||
element: {
|
||||
enumerable: true,
|
||||
get: () => document[nativeAPI.fullscreenElement] ?? undefined,
|
||||
},
|
||||
isEnabled: {
|
||||
enumerable: true,
|
||||
// Coerce to boolean in case of old WebKit.
|
||||
get: () => Boolean(document[nativeAPI.fullscreenEnabled]),
|
||||
},
|
||||
});
|
||||
|
||||
if (!nativeAPI) {
|
||||
screenfull = { isEnabled: false };
|
||||
}
|
||||
|
||||
export default screenfull;
|
|
@ -8,7 +8,25 @@ import {
|
|||
IControlCorners,
|
||||
IControlService,
|
||||
IControlServiceCfg,
|
||||
PositionName,
|
||||
PositionType,
|
||||
} from './IControlService';
|
||||
|
||||
const ControlDirectionConfig: Record<PositionName, 'column' | 'row'> = {
|
||||
topleft: 'column',
|
||||
topright: 'column',
|
||||
bottomright: 'column',
|
||||
bottomleft: 'column',
|
||||
leftcenter: 'column',
|
||||
rightcenter: 'column',
|
||||
topcenter: 'row',
|
||||
bottomcenter: 'row',
|
||||
lefttop: 'row',
|
||||
righttop: 'row',
|
||||
leftbottom: 'row',
|
||||
rightbottom: 'row',
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export default class ControlService implements IControlService {
|
||||
public container: HTMLElement;
|
||||
|
@ -74,18 +92,21 @@ export default class ControlService implements IControlService {
|
|||
|
||||
function createCorner(vSideList: string[] = []) {
|
||||
const className = vSideList.map((item) => l + item).join(' ');
|
||||
corners[vSideList.join('')] = DOM.create('div', className, container);
|
||||
corners[
|
||||
vSideList.filter((item) => !['row', 'column'].includes(item)).join('')
|
||||
] = DOM.create('div', className, container);
|
||||
}
|
||||
|
||||
createCorner(['top', 'left']);
|
||||
createCorner(['top', 'right']);
|
||||
createCorner(['bottom', 'left']);
|
||||
createCorner(['bottom', 'right']);
|
||||
function getCornerClassList(positionName: PositionName) {
|
||||
const positionList = positionName
|
||||
.replace(/^(top|bottom|left|right|center)/, '$1-')
|
||||
.split('-');
|
||||
return [...positionList, ControlDirectionConfig[positionName]];
|
||||
}
|
||||
|
||||
createCorner(['top', 'center']);
|
||||
createCorner(['right', 'center']);
|
||||
createCorner(['left', 'center']);
|
||||
createCorner(['bottom', 'center']);
|
||||
Object.values(PositionType).forEach((position) => {
|
||||
createCorner(getCornerClassList(position));
|
||||
});
|
||||
}
|
||||
|
||||
private clearControlPos() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Container } from 'inversify';
|
||||
|
||||
export enum PositionType {
|
||||
'TOPRIGHT' = 'topright',
|
||||
'TOPLEFT' = 'topleft',
|
||||
|
@ -8,6 +9,10 @@ export enum PositionType {
|
|||
'BOTTOMCENTER' = 'bottomcenter',
|
||||
'LEFTCENTER' = 'leftcenter',
|
||||
'RIGHTCENTER' = 'rightcenter',
|
||||
'LEFTTOP' = 'lefttop',
|
||||
'RIGHTTOP' = 'righttop',
|
||||
'LEFTBOTTOM' = 'leftbottom',
|
||||
'RIGHTBOTTOM' = 'rightbottom',
|
||||
}
|
||||
|
||||
export type PositionName =
|
||||
|
@ -18,21 +23,21 @@ export type PositionName =
|
|||
| 'topcenter'
|
||||
| 'bottomcenter'
|
||||
| 'leftcenter'
|
||||
| 'rightcenter';
|
||||
export interface IControlOption {
|
||||
name: string;
|
||||
position: PositionName;
|
||||
[key: string]: any;
|
||||
}
|
||||
| 'rightcenter'
|
||||
| 'lefttop'
|
||||
| 'righttop'
|
||||
| 'leftbottom'
|
||||
| 'rightbottom';
|
||||
|
||||
export interface IControlServiceCfg {
|
||||
container: HTMLElement;
|
||||
}
|
||||
export interface IControlCorners {
|
||||
[key: string]: HTMLElement;
|
||||
}
|
||||
export interface IControl {
|
||||
controlOption: IControlOption;
|
||||
setPosition(pos: PositionType): void;
|
||||
export interface IControl<O = any> {
|
||||
controlOption: O;
|
||||
setOptions: (newOption: Partial<O>) => void;
|
||||
addTo(sceneContainer: Container): void;
|
||||
onAdd(): HTMLElement;
|
||||
onRemove(): void;
|
||||
|
|
|
@ -1,29 +1,112 @@
|
|||
import { anchorType } from '@antv/l7-utils';
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
import { ILngLat } from '../map/IMapService';
|
||||
|
||||
export type PopupHTML = string | HTMLElement | HTMLElement[];
|
||||
|
||||
export interface IPopupOption {
|
||||
/**
|
||||
* 是否展示关闭按钮
|
||||
*/
|
||||
closeButton: boolean;
|
||||
|
||||
/**
|
||||
* 关闭按钮距离右上角的偏移
|
||||
*/
|
||||
closeButtonOffsets?: [number, number];
|
||||
|
||||
/**
|
||||
* 点击地图区域是否关闭弹框
|
||||
*/
|
||||
closeOnClick: boolean;
|
||||
|
||||
/**
|
||||
* 按 Esc 键是否关闭弹框
|
||||
*/
|
||||
closeOnEsc: boolean;
|
||||
|
||||
/**
|
||||
* 气泡体的最大宽度
|
||||
*/
|
||||
maxWidth: string;
|
||||
|
||||
/**
|
||||
* 气泡
|
||||
*/
|
||||
anchor: anchorType[any];
|
||||
className: string;
|
||||
offsets: number[];
|
||||
|
||||
/**
|
||||
* 气泡相对偏移
|
||||
*/
|
||||
offsets: [number, number];
|
||||
|
||||
/**
|
||||
* 气泡上的所有鼠标事件是否关闭事件冒泡
|
||||
*/
|
||||
stopPropagation: boolean;
|
||||
|
||||
/**
|
||||
* popup 位置发生变化时地图是否自动平移至气泡位置
|
||||
*/
|
||||
autoPan: boolean;
|
||||
|
||||
/**
|
||||
* 展示其他气泡时,当前气泡是否自动关闭
|
||||
*/
|
||||
autoClose: boolean;
|
||||
|
||||
/**
|
||||
* 当前气泡是否自动跟随光标
|
||||
*/
|
||||
followCursor: boolean;
|
||||
|
||||
/**
|
||||
* 自定义气泡容器的 class
|
||||
*/
|
||||
className?: string;
|
||||
|
||||
/**
|
||||
* 自定义气泡容器的 style
|
||||
*/
|
||||
style?: string;
|
||||
|
||||
/**
|
||||
* Popup 气泡的内置文本
|
||||
*/
|
||||
text?: string;
|
||||
|
||||
/**
|
||||
* Popup 气泡的内置HTML
|
||||
*/
|
||||
html?: PopupHTML;
|
||||
|
||||
/**
|
||||
* Popup 气泡的标题
|
||||
*/
|
||||
title?: PopupHTML;
|
||||
|
||||
/**
|
||||
* 初始的经纬度位置
|
||||
*/
|
||||
lngLat?: ILngLat;
|
||||
}
|
||||
export interface IPopup {
|
||||
|
||||
export interface IPopup extends EventEmitter {
|
||||
addTo(scene: Container): this;
|
||||
remove(): void;
|
||||
setLnglat(lngLat: ILngLat): this;
|
||||
getLnglat(): ILngLat;
|
||||
setHTML(html: string): this;
|
||||
setHTML(html: PopupHTML): this;
|
||||
setText(text: string): this;
|
||||
setMaxWidth(maxWidth: string): this;
|
||||
isOpen(): boolean;
|
||||
open(): void;
|
||||
close(): void;
|
||||
open(): this;
|
||||
close(): this;
|
||||
getOptions(): IPopupOption;
|
||||
setOptions(option: Partial<IPopupOption>): this;
|
||||
}
|
||||
|
||||
export interface IPopupService {
|
||||
addPopup(popup: IPopup): void;
|
||||
removePopup(popup: IPopup): void;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue