diff --git a/src/components/map/marker/GenericMarker.vue b/src/components/map/marker/GenericMarker.vue index 92b76c6..8f99a9b 100644 --- a/src/components/map/marker/GenericMarker.vue +++ b/src/components/map/marker/GenericMarker.vue @@ -46,7 +46,7 @@ export default defineComponent({ icon: new DynmapIcon({ icon: this.options.icon, label: this.options.label, - dimensions: this.options.dimensions, + iconSize: this.options.dimensions, showLabel: true, isHtml: this.options.isHTML, }), diff --git a/src/leaflet/icon/DynmapIcon.ts b/src/leaflet/icon/DynmapIcon.ts index 109be28..fe0cc8c 100644 --- a/src/leaflet/icon/DynmapIcon.ts +++ b/src/leaflet/icon/DynmapIcon.ts @@ -1,24 +1,24 @@ -import L, {PointTuple} from 'leaflet'; +import L, {DivIconOptions, PointExpression, PointTuple} from 'leaflet'; -export interface DynmapIconOptions { +export interface DynmapIconOptions extends DivIconOptions { icon: string; label: string; - dimensions: PointTuple; showLabel: boolean; isHtml?: boolean; - className?: string; } -export class DynmapIcon extends L.Icon { +export class DynmapIcon extends L.DivIcon { static defaultOptions: DynmapIconOptions = { icon: 'default', label: '', - dimensions: [16,16], + iconSize: [16, 16], showLabel: false, isHtml: false, className: '', }; - private _container?: HTMLDivElement; + + // @ts-ignore + options: DynmapIconOptions; constructor(options: DynmapIconOptions) { super(Object.assign(DynmapIcon.defaultOptions, options)); @@ -29,27 +29,19 @@ export class DynmapIcon extends L.Icon { L.DomUtil.remove(oldIcon); } - const + const div = document.createElement('div'), img = document.createElement('img'), label = document.createElement('span'), - url = `${window.config.url.markers}_markers_/${this.options.icon}.png`; + url = `${window.config.url.markers}_markers_/${this.options.icon}.png`, + size = L.point(this.options.iconSize as PointExpression); - // this._container.classList.add('Marker', 'mapMarker'); - this._container = document.createElement('div'); - this._container.classList.add('leaflet-div-icon'); - this._container.style.backgroundColor = 'pink'; - this._container.style.width = '16px'; - this._container.style.height = '16px'; + const sizeClass = [size.x, size.y].join('x'); - if(this.options.className) { - this._container.classList.add(this.options.className); - } - - img.classList.add('markerIcon', `markerIcon${this.options.dimensions.join('x')}`); + img.className = `marker__icon marker__icon--${sizeClass}`; img.src = url; - label.classList.add(this.options.showLabel ? 'markerName-show' : 'markerName'); - label.classList.add(/*'markerName_' + set.id,*/ `markerName${this.options.dimensions.join('x')}`); + label.className = this.options.showLabel ? 'marker__label marker__label--show' : 'marker__label'; + label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`); if (this.options.isHtml) { label.insertAdjacentHTML('afterbegin', this.options.label); @@ -57,13 +49,20 @@ export class DynmapIcon extends L.Icon { label.textContent = this.options.label; } - // this._container.insertAdjacentElement('beforeend', img); - // this._container.insertAdjacentElement('beforeend', label); + // @ts-ignore + L.Icon.prototype._setIconStyles.call(this, div, 'icon'); - return this._container; - } + div.appendChild(img); + div.appendChild(label); - update() { + div.classList.add('marker'); + if(this.options.className) { + div.classList.add(this.options.className); + } + + console.log(div.className); + + return div; } } diff --git a/src/leaflet/icon/PlayerIcon.ts b/src/leaflet/icon/PlayerIcon.ts index f1aee05..000b966 100644 --- a/src/leaflet/icon/PlayerIcon.ts +++ b/src/leaflet/icon/PlayerIcon.ts @@ -1,10 +1,25 @@ import L, {MarkerOptions} from 'leaflet'; import {DynmapPlayer} from "@/dynmap"; +import Util from '@/util'; const noSkinImage: HTMLImageElement = document.createElement('img'); noSkinImage.height = 16; noSkinImage.width = 16; -noSkinImage.src = 'images/player.png'; + +const smallImage: HTMLImageElement = document.createElement('img'); +smallImage.height = 16; +smallImage.width = 16; + +const largeImage: HTMLImageElement = document.createElement('img'); +largeImage.height = 32; +largeImage.width = 32; + +const bodyImage: HTMLImageElement = document.createElement('img'); +bodyImage.height = 32; +bodyImage.width = 32; + +noSkinImage.src = smallImage.src = largeImage.src = bodyImage.src = 'images/player.png'; +noSkinImage.className = smallImage.className = largeImage.className = bodyImage.className = 'player__icon'; export interface PlayerIconOptions extends MarkerOptions { smallFace: boolean, @@ -17,13 +32,13 @@ export class PlayerIcon extends L.DivIcon { private readonly _player: DynmapPlayer; private _container?: HTMLDivElement; private _playerImage?: HTMLImageElement; + private _playerInfo?: HTMLSpanElement; private _playerName?: HTMLSpanElement; private _playerHealth?: HTMLDivElement; - private _playerHealthBg?: HTMLDivElement; private _playerHealthBar?: HTMLDivElement; - private _playerArmourBg?: HTMLDivElement; - private _playerArmourBar?: HTMLDivElement; + private _playerArmor?: HTMLDivElement; + private _playerArmorBar?: HTMLDivElement; // @ts-ignore options: PlayerIconOptions; @@ -42,62 +57,57 @@ export class PlayerIcon extends L.DivIcon { this._container = document.createElement('div'); - this._container.classList.add('Marker', 'playerMarker', 'leaflet-marker-icon'); + this._container.classList.add('marker', 'marker--player', 'leaflet-marker-icon'); + + this._playerInfo = document.createElement('div'); + this._playerInfo.className = 'marker__label'; this._playerName = document.createElement('span'); - this._playerName.classList.add(this.options.smallFace ? 'playerNameSm' : 'playerName'); + this._playerName.className = 'player__name'; this._playerName.innerText = player.name; if (this.options.showSkinFace) { - this._playerImage = document.createElement('img'); - this._playerImage.classList.add(this.options.smallFace ? 'playerIconSm' : 'playerIcon'); + let size; - // if (this.options.smallFace) { - // getMinecraftHead(player.account, 16, head => { - // this._playerImage!.src = head.src; - // }); - // } else if (this.options.showBody) { - // getMinecraftHead(player.account, 'body', head => { - // this._playerImage!.src = head.src; - // }); - // } else { - // getMinecraftHead(player.account, 32, head => { - // this._playerImage!.src = head.src; - // }); - // } + if (this.options.smallFace) { + this._playerImage = smallImage.cloneNode() as HTMLImageElement; + size = '16'; + } else if(this.options.showBody) { + this._playerImage = bodyImage.cloneNode() as HTMLImageElement; + size = 'body'; + } else { + this._playerImage = largeImage.cloneNode() as HTMLImageElement; + size = '32'; + } + + Util.getMinecraftHead(player, size).then(head => { + this._playerImage!.src = head.src; + }); } else { this._playerImage = noSkinImage.cloneNode(false) as HTMLImageElement; - this._playerImage.classList.add(this.options.smallFace ? 'playerIconSm' : 'playerIcon'); } this._container.appendChild(this._playerImage); - this._container.appendChild(this._playerName); + this._container.appendChild(this._playerInfo); + this._playerInfo.appendChild(this._playerName); if (this.options.showHealth) { this._playerHealth = document.createElement('div'); + this._playerHealth.className = 'player__health'; - this._playerHealth.classList.add(this.options.smallFace ? 'healthContainerSm' : 'healthContainer'); - this._container.appendChild(this._playerHealth) + this._playerArmor = document.createElement('div'); + this._playerArmor.className = 'player__armor'; + + this._playerInfo.appendChild(this._playerHealth); + this._playerInfo.appendChild(this._playerArmor); this._playerHealthBar = document.createElement('div'); - this._playerHealthBar.classList.add('playerHealth'); + this._playerHealthBar.className = 'player__health-bar'; - this._playerArmourBar = document.createElement('div'); - this._playerArmourBar.classList.add('playerHealth'); + this._playerArmorBar = document.createElement('div'); + this._playerArmorBar.className = 'player__armor-bar'; - this._playerHealthBg = document.createElement('div'); - this._playerArmourBg = document.createElement('div'); - - this._playerHealthBg.classList.add('playerHealthBackground'); - this._playerArmourBar.classList.add('playerArmorBackground'); - - this._playerHealthBg.appendChild(this._playerHealthBar); - this._playerArmourBg.appendChild(this._playerArmourBar); - - this._playerHealth.appendChild(this._playerHealthBg); - this._playerHealth.appendChild(this._playerArmourBg); - - this._playerHealth.hidden = true; + this._playerHealth.hidden = this._playerArmor.hidden = true; } else { this._playerName.classList.add('playerNameNoHealth'); } @@ -115,10 +125,12 @@ export class PlayerIcon extends L.DivIcon { if(this.options.showHealth) { if (this._player.health !== undefined && this._player.armor !== undefined) { this._playerHealth!.hidden = false; + this._playerArmor!.hidden = false; this._playerHealthBar!.style.width = Math.ceil(this._player.health * 2.5) + 'px'; - this._playerArmourBar!.style.width = Math.ceil(this._player.armor * 2.5) + 'px'; + this._playerArmorBar!.style.width = Math.ceil(this._player.armor * 2.5) + 'px'; } else { this._playerHealth!.hidden = true; + this._playerArmor!.hidden = true; } } } diff --git a/src/scss/style.scss b/src/scss/style.scss index 16c29a7..fa6b053 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -281,111 +281,71 @@ button { * players on the map */ -/* smooth player movements (contrib from KillahKiwi) */ -.dynmap .playerMarker { - transition: transform 0.3s ease-in 0s; +.marker { + display: flex; + align-items: center; + + &.marker--player { + transition: transform 0.3s ease-in 0s; + + .marker__label { + display: block; + } + + .player__health, + .player__armor { + width: 50px; + } + + .player__health, + .player__armor, + .player__health-bg, + .player__armor-bg { + height: 7px; + } + + .player__health { + background: url(../assets/images/heart.png) repeat-x left center; + } + + .player__health-bg { + background: url(../assets/images/heart_depleted.png) repeat-x left center; + } + + .player__armor { + background: url(../assets/images/armor.png) repeat-x left center; + } + + .player__armor-bg { + background: url(../assets/images/armor_depleted.png) repeat-x left center; + } + } + + .marker__label { + flex: 0 0 auto; + margin-left: 2px; + z-index: 20; + + white-space: nowrap; + + color: $global-text-color; + background: #121212; + padding: 0.2rem; + display: none; + max-width: 25vw; + text-overflow: ellipsis; + overflow: hidden; + + &.marker__label--show { + display: block; + } + } + + &:hover .marker__label { + display: block; + } } -.dynmap .playerIcon { - margin-top: -16px; - margin-left: -16px; - width: 32px; - height: 32px; -} - -.dynmap .playerIconSm { - margin-top: -8px; - margin-left: -8px; - width: 16px; - height: 16px; -} - -.dynmap .playerName { - position: absolute; - top: -19px; - left: 18px; - z-index:20; - - white-space: nowrap; - - color: $global-text-color; - background: #121212; - padding: 0.2rem; -} - -.dynmap .playerNameSm { - position: absolute; - top: -16px; - left: 10px; - - white-space: nowrap; - - color: $global-text-color; - background: #121212; - padding: 0.2rem; -} - -.dynmap .playerNameNoHealth { - top: -7px; -} - -.dynmap .healthContainer { - display: block; - position: absolute; - top: 1px; - left: 18px; - - width: 50px; - - background: rgba(0,0,0,0.6); - padding: 2px; - - border-radius: 3px; - - z-index: 21; -} - -.dynmap .healthContainerSm { - display: block; - position: absolute; - top: -2px; - left: 10px; - - width: 50px; - - background: rgba(0,0,0,0.6); - padding: 2px; - - border-radius: 3px; -} - -.dynmap .playerHealth { - height: 7px; - - background: url(../assets/images/heart.png) repeat-x left center; -} - -.dynmap .playerHealthBackground { - height: 7px; - width: 50px; - - background: url(../assets/images/heart_depleted.png) repeat-x left center; -} - -.dynmap .playerArmor { - height: 7px; - - background: url(../assets/images/armor.png) repeat-x left center; -} - -.dynmap .playerArmorBackground { - height: 7px; - width: 50px; - - background: url(../assets/images/armor_depleted.png) repeat-x left center; -} - - /******************* * Compass */ @@ -515,7 +475,6 @@ button { overflow-y: auto !important; } - .messagerow { position: relative; max-height: 200px; @@ -539,68 +498,10 @@ button { left: 0; } -.leaflet-popup { - color: black; -} - .balloonmessage { word-wrap: break-word; } -/* Marker styles */ -.dynmap .mapMarker .markerName { - display: none; - z-index: 101; -} - -.dynmap .mapMarker:hover .markerName, -.dynmap .mapMarker .markerName-show { - display: block; - position: absolute; - z-index: 16; - - white-space: nowrap; - - color: #fff; - background: rgba(0,0,0,0.6); - padding: 2px; - - border-radius: 3px; -} - -.dynmap .mapMarker .markerName16x16 { - top: -6px; - left: 10px; -} - -.dynmap .mapMarker .markerName8x8 { - top: -4px; - left: 6px; -} - -.dynmap .mapMarker .markerName32x32 { - top: -8px; - left: 18px; -} - -.dynmap .mapMarker .markerIcon16x16 { - transform: translate(-50%, -50%); - width: 16px; - height: 16px; -} - -.dynmap .mapMarker .markerIcon8x8 { - transform: translate(-50%, -50%); - width: 8px; - height: 8px; -} - -.dynmap .mapMarker .markerIcon32x32 { - transform: translate(-50%, -50%); - width: 32px; - height: 32px; -} - .dynmap .mapMarker .markerName_offline_players { font-style: italic; } diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..a9f8f1c --- /dev/null +++ b/src/util.ts @@ -0,0 +1,53 @@ +import {DynmapPlayer} from "@/dynmap"; + +const headCache = new Map(); + +export default { + getMinecraftTime(serverTime: number) { + const day = serverTime >= 0 && serverTime < 13700; + + return { + serverTime: serverTime, + days: Math.floor((serverTime + 8000) / 24000), + + // Assuming it is day at 6:00 + hours: (Math.floor(serverTime / 1000) + 6) % 24, + minutes: Math.floor(((serverTime / 1000) % 1) * 60), + seconds: Math.floor(((((serverTime / 1000) % 1) * 60) % 1) * 60), + + day: day, + night: !day + }; + }, + + getMinecraftHead(player: DynmapPlayer, size: string): Promise { + if(headCache.has(player)) { + return Promise.resolve(headCache.get(player) as HTMLImageElement); + } + + return new Promise((resolve, reject) => { + const faceImage = new Image(); + + faceImage.onload = function() { + headCache.set(player, faceImage); + resolve(faceImage); + }; + + faceImage.onerror = function() { + console.error('Failed to retrieve face of "', player, '" with size "', size, '"!'); + reject(); + }; + + const src = (size === 'body') ? `faces/body/${player.name}.png` :`faces/${size}x${size}/${player.name}.png`; + faceImage.src = this.concatURL(window.config.url.markers, src); + }); + }, + + concatURL(base: string, addition: string) { + if(base.indexOf('?') >= 0) { + return base + escape(addition); + } + + return base + addition; + } +} \ No newline at end of file