// ==UserScript==
// @name 碧蓝 wiki 大型作战成就记录地图增强
// @namespace http://github.com/8qwe24657913
// @match https://wiki.biligame.com/blhx/%E5%A4%A7%E5%9E%8B%E4%BD%9C%E6%88%98%E6%88%90%E5%B0%B1%E8%AE%B0%E5%BD%95%E5%9C%B0%E5%9B%BE
// @grant none
// @run-at document-start
// @version 1.1.1
// @author 8q
// @description 增加侵蚀度、成就奖励、海域id、海域名称、成就种类、档案筛选器,添加存/读档功能,避免弹出框偏移出地图,去除平移与缩放
// ==/UserScript==
/* globals L mapData mapModel filterMouseover filterMouseout mapPoints filterClick updateSection mapSize updateMap saveAchievements normalAchievementKeywords safeAchievementKeywords */
;(function () {
'use strict'
function patch() {
const LEVEL_COUNT = 6
const ALL_REWARDS = [
'深渊6',
'深渊5',
'深渊4',
'宝箱6',
'金菜',
'紫菜',
'金猫箱',
'魔方',
'紫币',
'物资',
'指令书',
]
const MAP_DATA_SUPPLEMENT = {
11: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
12: [5, '指令书', '物资', '魔方', '紫币', '金猫箱'],
13: [5, '指令书', '金菜', '魔方', '紫币', '金猫箱'],
14: [4, '紫币', '物资', '金菜', '紫币', '魔方'],
21: [2, '紫币', '物资', '紫菜', '紫币', '金菜'],
22: [1, '紫币', '指令书', '紫菜', '紫币', '物资'],
23: [2, '指令书', '紫菜', '紫菜', '紫币', '金菜'],
24: [2, '指令书', '紫菜', '物资', '紫币', '物资'],
25: [3, '指令书', '紫菜', '金菜', '紫币', '金菜'],
31: [2, '紫币', '紫菜', '紫菜', '紫币', '物资'],
32: [3, '指令书', '紫菜', '金菜', '宝箱6', '金菜'],
33: [3, '指令书', '紫菜', '物资', '宝箱6', '金菜'],
34: [3, '指令书', '紫菜', '紫菜', '宝箱6', '魔方'],
41: [3, '指令书', '物资', '金菜', '紫币', '魔方'],
42: [4, '指令书', '物资', '金菜', '紫币', '魔方'],
43: [2, '紫币', '紫菜', '物资', '紫币', '物资'],
44: [1, '紫币', '指令书', '紫菜', '紫币', '物资'],
51: [4, '指令书', '物资', '魔方', '深渊4', '魔方'],
52: [4, '紫币', '金菜', '魔方', '紫币', '魔方'],
53: [4, '指令书', '金菜', '魔方', '紫币', '魔方'],
54: [4, '指令书', '金菜', '金菜', '深渊4', '魔方'],
61: [4, '指令书', '物资', '魔方', '深渊4', '魔方'],
62: [3, '指令书', '物资', '金菜', '宝箱6', '紫菜'],
63: [4, '指令书', '物资', '金菜', '紫币', '魔方'],
64: [4, '指令书', '金菜', '金菜', '深渊4', '魔方'],
65: [3, '指令书', '指令书', '金菜', '紫币', '魔方'],
66: [3, '指令书', '指令书', '物资', '紫币', '魔方'],
71: [5, '指令书', '金菜', '魔方', '深渊5', '金猫箱'],
72: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
73: [5, '指令书', '金菜', '魔方', '深渊5', '金猫箱'],
81: [2, '指令书', '指令书', '物资', '紫币', '金菜'],
82: [4, '紫币', '金菜', '魔方', '深渊4', '魔方'],
83: [2, '指令书', '物资', '物资', '紫币', '金菜'],
84: [2, '紫币', '紫菜', '物资', '紫币', '金菜'],
85: [4, '指令书', '金菜', '魔方', '深渊4', '魔方'],
91: [4, '指令书', '金菜', '魔方', '深渊4', '魔方'],
92: [2, '紫币', '指令书', '紫菜', '紫币', '物资'],
93: [2, '指令书', '指令书', '紫菜', '紫币', '物资'],
94: [3, '指令书', '指令书', '物资', '宝箱6', '紫菜'],
95: [3, '指令书', '金菜', '紫菜', '宝箱6', '紫菜'],
101: [5, '指令书', '物资', '魔方', '深渊5', '金猫箱'],
102: [5, '指令书', '物资', '魔方', '深渊5', '金猫箱'],
103: [4, '紫币', '金菜', '魔方', '深渊4', '魔方'],
104: [4, '紫币', '金菜', '魔方', '深渊4', '魔方'],
105: [3, '紫币', '指令书', '紫菜', '宝箱6', '魔方'],
106: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
111: [3, '紫币', '指令书', '金菜', '紫币', '魔方'],
112: [2, '指令书', '指令书', '紫菜', '紫币', '物资'],
113: [3, '紫币', '指令书', '金菜', '紫币', '魔方'],
114: [3, '紫币', '指令书', '物资', '宝箱6', '物资'],
121: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
122: [2, '指令书', '物资', '紫菜', '紫币', '物资'],
123: [3, '紫币', '金菜', '紫菜', '紫币', '物资'],
124: [5, '指令书', '物资', '魔方', '紫币', '金猫箱'],
125: [3, '紫币', '指令书', '紫菜', '紫币', '物资'],
131: [2, '指令书', '指令书', '紫菜', '紫币', '物资'],
132: [2, '指令书', '物资', '紫菜', '紫币', '物资'],
133: [3, '紫币', '金菜', '金菜', '紫币', '物资'],
134: [2, '指令书', '物资', '紫菜', '紫币', '物资'],
135: [3, '紫币', '金菜', '物资', '宝箱6', '魔方'],
141: [3, '紫币', '指令书', '金菜', '紫币', '魔方'],
142: [4, '紫币', '金菜', '魔方', '深渊4', '魔方'],
143: [3, '紫币', '指令书', '金菜', '宝箱6', '魔方'],
144: [5, '指令书', '金菜', '魔方', '紫币', '金猫箱'],
151: [5, '指令书', '金菜', '魔方', '深渊5', '金猫箱'],
152: [5, '指令书', '金菜', '魔方', '紫币', '金猫箱'],
153: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
155: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
156: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
157: [6, '指令书', '金菜', '魔方', '深渊6', '金猫箱'],
158: [5, '指令书', '物资', '魔方', '紫币', '金猫箱'],
159: [5, '指令书', '物资', '魔方', '深渊5', '金猫箱'],
}
const FILES = {
陨石事件: [44, 84, 125, 95, 104, 52],
能源革命: [22, 134, 32, 94, 142, 53],
科技与生活: [83, 122, 135, 143, 63, 54],
生活的变革: [23, 62, 114, 51, 41, 14],
魔方军用化: [21, 31, 66, 113, 65, 85],
魔方军用化II: [43, 112, 34, 133, 61, 71],
'「微光」计划': [81, 132, 123, 105, 91, 64],
军备竞赛: [24, 92, 111, 25, 82, 42],
冷战升级: [93, 131, 141, 33, 103, 13],
}
const NORMAL_FILE = '档案(1/3/5)'
const SAFE_FILE = '档案(2/4/6)'
const SELECTOR_CLASS = 'leaflet-control-layers-selector'
const name2file = {}
for (const [file, ids] of Object.entries(FILES)) {
for (const [i, id] of ids.entries()) {
if (!mapData[id]) continue
const name = mapData[id][0]
name2file[name] = [file, i + 1]
}
}
const name2id = {}
const name2level = {}
const name2rewards = {}
const achievementTypes = {
普通海域: [...normalAchievementKeywords, NORMAL_FILE],
安全海域: [...safeAchievementKeywords, SAFE_FILE],
}
const isSafe = {}
for (const [safety, achievementType] of Object.entries(achievementTypes)) {
isSafe[achievementType] = safety === '安全海域'
}
const name2AchievementTypes = {}
const achievementKeywordsWithoutFile = [...normalAchievementKeywords, ...safeAchievementKeywords]
for (const [id, [level, ...rewards]] of Object.entries(MAP_DATA_SUPPLEMENT)) {
if (!mapData[id]) continue
const name = mapData[id][0]
name2id[name] = Number(id)
name2level[name] = level
name2rewards[name] = rewards
name2AchievementTypes[name] = mapData[id].slice(3).map((achievement) => {
if (achievement.includes('档案')) {
return name2file[name][1] % 2 === 0 ? SAFE_FILE : NORMAL_FILE
} else {
return achievementKeywordsWithoutFile.find((keyword) => achievement.includes(keyword))
}
})
}
// 原来就有的 bug,更新了成就情况后筛选结果不跟着变
const oldUpdateMap = updateMap
window.updateMap = function updateMap() {
oldUpdateMap()
updateSection()
}
// 增加各种筛选器
const safetyFilterList = L.DomUtil.get('filter-safe-todo-box').parentElement
const safetyFilterChildren = [...safetyFilterList.children]
function createFilterList(type, text) {
L.DomUtil.create(
'div',
`filter-title filter-${type}-title`,
safetyFilterList.parentElement,
).innerText = text
return L.DomUtil.create('div', `filter-list filter-${type}-list`, safetyFilterList.parentElement)
}
function createFilterBox(parent, type, text = '') {
const filterBox = L.DomUtil.create('label', null, parent)
filterBox.id = `filter-${type}-box`
filterBox.for = `filter-${type}`
if (text) filterBox.innerText = text
return filterBox
}
function createFilterCheckbox(parent, type, text) {
const filterBox = createFilterBox(parent, type)
filterBox.innerHTML = `<div><input type="checkbox" id="filter-${type}" class="${SELECTOR_CLASS}"><span>${text}</span></div>`
return L.DomUtil.get(`filter-${type}`)
}
function createOption(parent, value, text = value) {
const option = L.DomUtil.create('option', null, parent)
option.value = value
option.innerText = text
return option
}
function createSelect(parent) {
const filterSelect = L.DomUtil.create('select', SELECTOR_CLASS, parent)
createOption(filterSelect, '', '(未选择)')
return filterSelect
}
// 侵蚀度
const levelFilterList = createFilterList('level', '侵蚀度:')
const levelFilterBoxes = new Array(LEVEL_COUNT)
.fill()
.map((_, i) => createFilterCheckbox(levelFilterList, `level-${i + 1}`, `侵蚀${i + 1}`))
// 未获取的奖励
const rewardFilterList = createFilterList('reward', '未获取的奖励:')
const rewardFilterBoxes = ALL_REWARDS.map((reward, i) =>
createFilterCheckbox(rewardFilterList, `reward-${i + 1}`, reward),
)
// 海域搜索
const searchFilterList = createFilterList('name-id', '海域搜索:')
const searchFilterBoxes = [
['name', '海域名称:', null],
['id', '海域编号:', '^\\d+$'],
].map(([type, text, pattern]) => {
const filterBox = createFilterBox(searchFilterList, type, text)
const filterInput = L.DomUtil.create('input', SELECTOR_CLASS, filterBox)
filterInput.type = 'text'
filterInput.id = `filter-${type}`
if (pattern) filterInput.pattern = pattern
return filterInput
})
// 档案
const fileFilterBoxes = [
['file-name', '档案名称:', Object.keys(FILES)],
['file-index', '档案序号:', [1, 2, 3, 4, 5, 6]],
].map(([type, text, options]) => {
const filterBox = createFilterBox(searchFilterList, type, text)
const filterSelect = createSelect(filterBox)
for (const option of options) {
createOption(filterSelect, option)
}
return filterSelect
})
// 未完成成就具体类型
const achievementTypeSelect = (() => {
const filterBox = createFilterBox(safetyFilterList, 'achievement', '具体类型:')
const filterSelect = createSelect(filterBox)
for (const [group, types] of Object.entries(achievementTypes)) {
const optgroup = L.DomUtil.create('optgroup', null, filterSelect)
optgroup.label = group
for (const type of types) {
createOption(optgroup, type)
}
}
return filterSelect
})()
const filters = {
safety: {
checked: false,
// 原来的筛选函数里没有考虑档案,这里修个 bug
filter(section) {
const achievementTypes = name2AchievementTypes[section.name]
for (const [i, isCompleted] of section.completed.entries()) {
if (isCompleted) continue
if (isSafe[achievementTypes[i]] ? mapModel.filters.safeTodo : mapModel.filters.normalTodo)
return true
}
return false
},
update() {
mapModel.filters.safeTodo = L.DomUtil.get('filter-safe-todo').checked
mapModel.filters.normalTodo = L.DomUtil.get('filter-normal-todo').checked
this.checked = mapModel.filters.safeTodo || mapModel.filters.normalTodo
},
},
level: {
checked: false,
filter(section) {
return mapModel.filters.levels[name2level[section.name] - 1]
},
update() {
mapModel.filters.levels = levelFilterBoxes.map((checkbox) => checkbox.checked)
this.checked = mapModel.filters.levels.includes(true)
},
},
reward: {
checked: false,
filter(section) {
const rewards = name2rewards[section.name].slice(section.completed.filter((x) => x).length)
return rewards.some((reward) => mapModel.filters.rewards.has(reward))
},
update() {
mapModel.filters.rewards = new Set(
rewardFilterBoxes.map((checkbox, i) => checkbox.checked && ALL_REWARDS[i]).filter((x) => x),
)
this.checked = mapModel.filters.rewards.size > 0
},
},
name: {
checked: false,
filter(section) {
return section.name.includes(mapModel.filters.name)
},
update() {
mapModel.filters.name = searchFilterBoxes[0].value
this.checked = !!mapModel.filters.name
},
},
id: {
checked: false,
filter(section) {
return name2id[section.name] === mapModel.filters.id
},
update() {
const value = searchFilterBoxes[1].value
mapModel.filters.id = Number(value)
this.checked = !!value && !Number.isNaN(mapModel.filters.id)
},
},
achievement: {
checked: false,
filter(section) {
const achievementTypes = name2AchievementTypes[section.name]
for (const [i, isCompleted] of section.completed.entries()) {
if (isCompleted) continue
if (achievementTypes[i] === mapModel.filters.achievementType) return true
}
return false
},
update() {
mapModel.filters.achievementType = achievementTypeSelect.value
this.checked = !!mapModel.filters.achievementType
},
},
fileType: {
checked: false,
filter(section) {
return name2file[section.name] && name2file[section.name][0] === mapModel.filters.fileType
},
update() {
mapModel.filters.fileType = fileFilterBoxes[0].value
this.checked = !!mapModel.filters.fileType
},
},
fileIndex: {
checked: false,
filter(section) {
return name2file[section.name] && name2file[section.name][1] === mapModel.filters.fileIndex
},
update() {
mapModel.filters.fileIndex = Number(fileFilterBoxes[1].value)
this.checked = !!mapModel.filters.fileIndex
},
},
}
/**
* 筛选规则:
* 1. 同一筛选器的不同选项间为逻辑或关系,不同筛选器间为逻辑与关系
* 2. 若用户未填写某个筛选器,则视为该筛选器不存在
* 3. 不存在任何筛选器时,不选中任何区块
*/
let hasFilter = false
window.filterSection = function filterSection(section) {
if (!hasFilter) return false
for (const { filter, checked } of Object.values(filters)) {
if (checked && !filter(section)) return false
}
return true
}
function filterChange() {
hasFilter = false
for (const filter of Object.values(filters)) {
filter.update()
hasFilter = hasFilter || filter.checked
}
updateSection()
}
filterChange()
function newFilterMouseover(checkbox) {
if (!checkbox.checked) {
checkbox.checked = true
filterChange()
checkbox.checked = false
}
}
L.DomEvent.off(safetyFilterList, 'click', filterClick)
for (const box of safetyFilterList.children) {
L.DomEvent.off(box, {
mouseover: filterMouseover,
mouseout: filterMouseout,
})
}
for (const box of [...safetyFilterChildren, ...levelFilterList.children, ...rewardFilterList.children]) {
L.DomEvent.on(box, {
mouseover: newFilterMouseover.bind(null, L.DomUtil.get(box.id.slice(0, -4))),
mouseout: filterChange,
change: filterChange,
})
}
for (const box of searchFilterBoxes) {
L.DomEvent.on(box, 'input paste change', filterChange)
}
for (const box of [achievementTypeSelect, ...fileFilterBoxes]) {
L.DomEvent.on(box, 'change', filterChange)
}
// 备份 / 读取存档
const collapseList = document.getElementsByClassName('leaflet-control-layers-overlays')[0].parentElement
L.DomUtil.create('div', 'leaflet-control-layers-separator', collapseList)
const saveLoadList = L.DomUtil.create('div', 'achievement-backup-load', collapseList)
const saveLoad = [
{
type: 'backup',
text: '备份',
onclick() {
const achievementList = []
for (const i in mapModel.mapSection) {
const section = mapModel.mapSection[i]
for (const j in section.completed) {
if (section.completed[j]) {
achievementList.push(i + '-' + j)
}
}
}
const userAchievements = achievementList.join(',')
const blob = new Blob([userAchievements], {
type: 'text/plain',
})
const a = document.createElement('a')
a.href = URL.createObjectURL(blob)
a.download = `achievements-${new Date().toISOString()}.txt`
a.click()
},
},
{
type: 'load',
text: '读档',
onclick() {
const input = L.DomUtil.create('input')
input.type = 'file'
input.accept = '.txt,text/plain'
input.onchange = () => {
if (input.files.length === 0) return
const [file] = input.files
const fr = new FileReader()
fr.onload = () => {
updateAchievements(fr.result)
saveAchievements()
}
fr.readAsText(file)
}
input.click()
},
},
]
for (const { type, text, onclick } of saveLoad) {
const button = L.DomUtil.create('button', `achievement-${type}`, saveLoadList)
button.innerText = text
L.DomEvent.on(button, 'click', onclick)
}
// 修改 popup 位置,使其不会超出窗口外
initRrose()
for (const mapPoint of Object.values(mapPoints)) {
const { marker, popup } = mapPoint
marker.bindPopup(
(mapPoint.popup = new L.Rrose({
...popup.options,
autoPan: false,
}).setContent(popup.getContent())),
)
popup.remove()
}
// 防止用户手快事先平移或缩放了,复位一下
const map = mapPoints[11].marker._map
map.fitBounds([[0, 0], mapSize])
// 禁止平移和缩放
map.boxZoom.disable()
map.doubleClickZoom.disable()
map.dragging.disable()
map.keyboard.disable()
map.scrollWheelZoom.disable()
map.touchZoom.disable()
map.zoomControl.remove()
// 自定义样式
document.head.appendChild(document.createElement('style')).appendChild(
document.createTextNode(`
#filter-name {
max-width: 90px;
}
#filter-id {
max-width: 30px;
}
#alworldmap input:invalid {
border-color: rgba(255, 0, 0, 0.5);
}
/* 存/读档按钮 */
.achievement-backup-load {
display: flex;
justify-content: space-around;
}
/* 防止用户选中奇怪的东西 */
#alworldmap {
user-select: none;
}
.leaflet-rrose-content, .achievement-count-text {
user-select: text;
}
/* 能点的东西给个提示啊 */
#alworldmap .achievement,
#alworldmap label,
#alworldmap input[type="checkbox"],
#alworldmap input[type="radio"],
.leaflet-rrose-close-button,
.achievement-backup-load button {
cursor: pointer;
}
`),
)
}
// 实测 bwiki 上 media wiki api 存的东西有时会乱套(读到了别人的存档?),先把网络同步禁用掉
function loadAchievements() {
return localStorage.getItem('userjs-worldmap-achievements') || ''
}
function updateAchievements(achievements) {
const updated = new Set()
for (const achievement of achievements.split(',')) {
const [i, j] = achievement.split('-')
const { completed } = mapModel.mapSection[i]
if (!updated.has(i)) {
completed.fill(false)
updated.add(i)
}
completed[j] = true
}
updateMap()
}
// eslint-disable-next-line no-unused-vars
function replaceLoadAchievements() {
window.loadAchievements = loadAchievements
}
if (typeof mapModel === 'undefined') {
// 早于地图初始化
Object.defineProperty(window, 'mapModel', {
get() {
return undefined
},
set(value) {
Object.defineProperty(window, 'mapModel', {
value,
writable: true,
enumerable: true,
configurable: true,
})
// replaceLoadAchievements()
// 等初始化完了再执行 patch
queueMicrotask(patch)
},
enumerable: false,
configurable: true,
})
} else {
// 晚于地图初始化
// replaceLoadAchievements()
// 执行时机晚则需要手动读取本地存档以更新地图
// updateAchievements(loadAchievements())
patch()
}
// 下面是引用的库,用来调整弹出框位置
function initRrose() {
/*
Copyright (c) 2012 Eric S. Theise
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
L.Rrose = L.Popup.extend({
_initLayout: function () {
const prefix = 'leaflet-rrose'
const container = (this._container = L.DomUtil.create(
'div',
prefix + ' ' + this.options.className + ' leaflet-zoom-animated',
))
let closeButton
let wrapper
if (this.options.closeButton) {
closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container)
closeButton.href = '#close'
closeButton.innerHTML = '×'
L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this)
}
// Set the pixel distances from the map edges at which popups are too close and need to be re-oriented.
const xBound = 200
const yBound = 200
// Determine the alternate direction to pop up; north mimics Leaflet's default behavior, so we initialize to that.
this.options.position = 'n'
// Then see if the point is too far north...
const yDiff = yBound - this._map.latLngToContainerPoint(this._latlng).y
if (yDiff > 0) {
this.options.position = 's'
}
// or too far east...
let xDiff = this._map.latLngToContainerPoint(this._latlng).x - (this._map.getSize().x - xBound)
if (xDiff > 0) {
this.options.position += 'w'
} else {
// or too far west.
xDiff = xBound - this._map.latLngToContainerPoint(this._latlng).x
if (xDiff > 0) {
this.options.position += 'e'
}
}
// Create the necessary DOM elements in the correct order. Pure 'n' and 's' conditions need only one class for styling, others need two.
if (/s/.test(this.options.position)) {
if (this.options.position === 's') {
this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container)
wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container)
} else {
this._tipContainer = L.DomUtil.create(
'div',
prefix + '-tip-container' + ' ' + prefix + '-tip-container-' + this.options.position,
container,
)
wrapper = this._wrapper = L.DomUtil.create(
'div',
prefix + '-content-wrapper' + ' ' + prefix + '-content-wrapper-' + this.options.position,
container,
)
}
this._tip = L.DomUtil.create(
'div',
prefix + '-tip' + ' ' + prefix + '-tip-' + this.options.position,
this._tipContainer,
)
L.DomEvent.disableClickPropagation(wrapper)
this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper)
L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation)
if (closeButton) closeButton.style.top = '20px'
} else {
if (this.options.position === 'n') {
wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container)
this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container)
} else {
wrapper = this._wrapper = L.DomUtil.create(
'div',
prefix + '-content-wrapper' + ' ' + prefix + '-content-wrapper-' + this.options.position,
container,
)
this._tipContainer = L.DomUtil.create(
'div',
prefix + '-tip-container' + ' ' + prefix + '-tip-container-' + this.options.position,
container,
)
}
L.DomEvent.disableClickPropagation(wrapper)
this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper)
L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation)
this._tip = L.DomUtil.create(
'div',
prefix + '-tip' + ' ' + prefix + '-tip-' + this.options.position,
this._tipContainer,
)
}
},
_updatePosition: function () {
const pos = this._map.latLngToLayerPoint(this._latlng)
const is3d = L.Browser.any3d
const offset = new L.Point(...this.options.offset)
L.DomUtil.setPosition(this._container, pos)
if (/s/.test(this.options.position)) {
this._containerBottom = -this._container.offsetHeight + offset.y - (is3d ? 0 : pos.y)
} else {
this._containerBottom = -offset.y - (is3d ? 0 : pos.y)
}
if (/e/.test(this.options.position)) {
this._containerLeft = offset.x + (is3d ? 0 : pos.x)
} else if (/w/.test(this.options.position)) {
this._containerLeft = -Math.round(this._containerWidth) + offset.x + (is3d ? 0 : pos.x)
} else {
this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x)
}
this._container.style.bottom = this._containerBottom + 'px'
this._container.style.left = this._containerLeft + 'px'
},
})
document.head.appendChild(document.createElement('style')).appendChild(
document.createTextNode(`/* Rrose layout */
.leaflet-rrose {
position: absolute;
text-align: center;
}
.leaflet-rrose-content-wrapper {
padding: 1px;
text-align: left;
}
.leaflet-rrose-content {
margin: 14px 20px;
}
.leaflet-rrose-tip-container {
margin: 0 auto;
width: 40px;
height: 20px;
position: relative;
overflow: hidden;
}
.leaflet-rrose-tip-container-se, .leaflet-rrose-tip-container-ne {
margin-left: 0;
}
.leaflet-rrose-tip-container-sw, .leaflet-rrose-tip-container-nw {
margin-right: 0;
}
.leaflet-rrose-tip {
width: 15px;
height: 15px;
padding: 1px;
-moz-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-rrose-tip-n {
margin: -8px auto 0;
}
.leaflet-rrose-tip-s {
margin: 11px auto 0;
}
.leaflet-rrose-tip-se {
margin: 11px 11px 11px -8px; overflow: hidden;
}
.leaflet-rrose-tip-sw {
margin: 11px 11px 11px 32px; overflow: hidden;
}
.leaflet-rrose-tip-ne {
margin: -8px 11px 11px -8px; overflow: hidden;
}
.leaflet-rrose-tip-nw {
margin: -8px 11px 11px 32px; overflow: hidden;
}
a.leaflet-rrose-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 5px 0 0;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
}
a.leaflet-rrose-close-button:hover {
color: #999;
}
.leaflet-rrose-content p {
margin: 18px 0;
}
.leaflet-rrose-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
/* Visual appearance */
.leaflet-rrose-content-wrapper, .leaflet-rrose-tip {
background: white;
box-shadow: 0 3px 10px #888;
-moz-box-shadow: 0 3px 10px #888;
-webkit-box-shadow: 0 3px 14px #999;
}
.leaflet-rrose-content-wrapper {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
}
.leaflet-rrose-content-wrapper-se {
-moz-border-radius: 0 20px 20px 20px;
-webkit-border-radius: 0 20px 20px 20px;
border-radius: 0 20px 20px 20px;
}
.leaflet-rrose-content-wrapper-sw {
-moz-border-radius: 20px 0 20px 20px;
-webkit-border-radius: 20px 0 20px 20px;
border-radius: 20px 0 20px 20px;
}
.leaflet-rrose-content-wrapper-nw, .leaflet-rrose-content-wrapper-w {
-moz-border-radius: 20px 20px 0 20px;
-webkit-border-radius: 20px 20px 0 20px;
border-radius: 20px 20px 0 20px;
}
.leaflet-rrose-content-wrapper-ne, .leaflet-rrose-content-wrapper-e {
-moz-border-radius: 20px 20px 20px 0;
-webkit-border-radius: 20px 20px 20px 0;
border-radius: 20px 20px 20px 0;
}
.leaflet-rrose-content {
font: 12px/1.4 "Helvetica Neue", Arial, Helvetica, sans-serif;
}`),
)
}
})()