Skip to content

Commit

Permalink
Merge pull request #116 from Disfactory/release/2.3.0
Browse files Browse the repository at this point in the history
Release 2.3.0
  • Loading branch information
Yukaii authored Sep 8, 2021
2 parents 897728d + 1ccfcbe commit 0a571e4
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: Install and Build
run: |
npm ci
echo "VUE_APP_BASE_URL=https://api.disfactory.tw/api" > .env
echo "VUE_APP_BASE_URL=https://staging.disfactory.tw/api" > .env
npm run build
- name: Deploy to GitHub Pages
uses: JamesIves/[email protected]
Expand Down
56 changes: 37 additions & 19 deletions src/components/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ import AppNavbar from '@/components/AppNavbar.vue'
import AppTextField from '@/components/AppTextField.vue'
import DisplaySettingBottomSheet from '@/components/DisplaySettingBottomSheet.vue'
import { initializeMap, MapFactoryController } from '../lib/map'
import { initializeMap, MapFactoryController, getFactoryStatus } from '../lib/map'
import { getFactories } from '../api'
import { MainMapControllerSymbol } from '../symbols'
import { Feature, Overlay } from 'ol'
import Point from 'ol/geom/Point'
import OverlayPositioning from 'ol/OverlayPositioning'
import { defaultFactoryDisplayStatuses, FactoryDisplayStatusType, getDisplayStatusColor, getDisplayStatusText } from '../types'
import { useGA } from '@/lib/useGA'
Expand All @@ -46,7 +47,6 @@ import { useAppState } from '../lib/appState'
import { useAlertState } from '../lib/useAlert'
import { moveToSharedFactory, permalink } from '../lib/permalink'
import { waitNextTick } from '../lib/utils'
import { Style } from 'ol/style'
export default createComponent({
components: {
Expand All @@ -73,7 +73,9 @@ export default createComponent({
const openFactoryDetail = (feature: Feature) => {
if (!mapControllerRef.value) return
const factory = mapControllerRef.value.getFactory(feature.getId() as string)
const factoryId = feature.get('factoryId') as string
const factory = mapControllerRef.value.getFactory(factoryId)
if (factory) {
factory.feature = feature
Expand Down Expand Up @@ -109,19 +111,23 @@ export default createComponent({
}
if (feature) {
if ('setStyle' in feature) {
const zoomedStyle = (feature.get('defaultStyle') as Style).clone()
const originalImage = zoomedStyle.getImage().clone()
originalImage.setScale(1.25)
zoomedStyle.setImage(originalImage)
zoomedStyle.setZIndex(2)
feature.setStyle(zoomedStyle)
}
event('clickFactoryPin')
openFactoryDetail(feature)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((context.root as any).$vuetify.breakpoint.mdAndUp) {
expandFactoryDetail()
if (feature.get('factoryId')) {
openFactoryDetail(feature)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((context.root as any).$vuetify.breakpoint.mdAndUp) {
expandFactoryDetail()
}
} else {
// eslint-disable-next-line
// @ts-ignore
const p: Point = feature.getGeometry()
// eslint-disable-next-line
// @ts-ignore
const c: Coordinate = p.getCoordinates()
mapControllerRef?.value?.mapInstance?.map?.getView().setCenter(c)
const zoom = mapControllerRef?.value?.mapInstance?.map?.getView().getZoom()
mapControllerRef?.value?.mapInstance?.map?.getView().setZoom((zoom || 0) + 1)
}
} else {
appState.factoryData = null
Expand Down Expand Up @@ -170,9 +176,12 @@ export default createComponent({
})
moveToSharedFactory(mapController, window.location, (factoryId) => {
const feature = mapController.factoriesLayerSource.getFeatureById(factoryId)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
onClickFactoryFeature([0, 0], feature as Feature)
const factory = mapControllerRef?.value?.getFactory(factoryId)
if (factory) {
const feature = mapController.getFactoriesLayerForStatus(getFactoryStatus(factory)).getFeatureById(factoryId)
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
onClickFactoryFeature([0, 0], feature as Feature)
}
})
mapController.mapInstance.map.addOverlay(popupOverlay)
Expand Down Expand Up @@ -214,7 +223,16 @@ export default createComponent({
]
}
mapControllerRef.value.setFactoryStatusFilter(appliedFilters.value)
mapControllerRef.value.mapInstance.map.getLayers().forEach((layer) => {
const factoryStatus = layer.get('factoryStatus')
if (factoryStatus === undefined) return
const comparableFactoryStatus = factoryStatus === 'default' ? factoryStatus : Number(factoryStatus)
if (!appliedFilters.value.includes(comparableFactoryStatus)) {
layer.setVisible(false)
} else {
layer.setVisible(true)
}
})
}
const filterButtonsData = defaultFactoryDisplayStatuses.map(v => ({
Expand Down
6 changes: 4 additions & 2 deletions src/lib/appState.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject, provide, reactive, computed } from '@vue/composition-api'
import { useGA } from './useGA'
import { FactoryData } from '../types'
import { featureStyleCache } from './map'

const AppStateSymbol = Symbol('AppState')

Expand Down Expand Up @@ -208,8 +209,9 @@ const registerMutator = (appState: AppState) => {
}

function collapseFactoryDetail () {
if (appState.factoryData?.feature?.get('defaultStyle')) {
appState.factoryData?.feature?.setStyle(appState.factoryData?.feature?.get('defaultStyle'))
const defaultStyle = featureStyleCache.get(appState.factoryData?.id as string)
if (defaultStyle) {
appState.factoryData?.feature?.setStyle(defaultStyle)
}
appState.factoryDetailsExpanded = false
appState.factoryData = null
Expand Down
130 changes: 105 additions & 25 deletions src/lib/map.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Map as OlMap, View, Feature, MapBrowserEvent } from 'ol'
import { Style, Icon, Circle, Fill, Stroke } from 'ol/style'
import { Style, Icon, Circle, Fill, Stroke, Text } from 'ol/style'
import IconAnchorUnits from 'ol/style/IconAnchorUnits'
import { Point } from 'ol/geom'
import WMTS from 'ol/source/WMTS'
import WMTSTileGrid from 'ol/tilegrid/WMTS'
import { get as getProjection, transform, transformExtent } from 'ol/proj'
import { getWidth, getTopLeft } from 'ol/extent'
import { Tile as TileLayer, Vector as VectorLayer, Layer } from 'ol/layer'
import { Vector as VectorSource, OSM } from 'ol/source'
import { Vector as VectorSource, OSM, Cluster } from 'ol/source'
import { Zoom, ScaleLine, Rotate, Attribution } from 'ol/control'
import Geolocation from 'ol/Geolocation'
import { defaults as defaultInteractions, PinchRotate } from 'ol/interaction'
Expand All @@ -25,7 +25,10 @@ import { baseStyle } from './layerStyle'

const getFactoryStatusImage = (status: FactoryDisplayStatusType) => `/images/marker-${status}.svg`
export const getStatusBorderColor = (status: FactoryDisplayStatusType) => {
return FactoryDisplayStatuses.find(s => s.type === status)?.color
// We use == instead of === here because `type` can be either a string or number
// == results in a form of typecasting that works good enough for here
// eslint-disable-next-line
return FactoryDisplayStatuses.find(s => s.type == status)?.color
}

export function getFactoryStatus (factory: FactoryData): FactoryDisplayStatusType {
Expand Down Expand Up @@ -100,14 +103,18 @@ const minimapPinStyle = new Style({
})
})

export const featureStyleCache = new Map<string, Style>()

export class MapFactoryController {
private _map: OLMap
private appliedFilters: FactoryDisplayStatusType[] = defaultFactoryDisplayStatuses
private _factoriesLayerSource?: VectorSource
private _factoriesLayerStatusMap: {[key: string]: VectorSource}
private factoryMap = new Map<string, FactoryData>()

constructor (map: OLMap) {
this._map = map
this._factoriesLayerStatusMap = {} as {[key: string]: VectorSource}
}

get mapInstance () {
Expand All @@ -118,20 +125,59 @@ export class MapFactoryController {
return [...this.factoryMap.values()]
}

get factoriesLayerSource () {
// create or return _factoriesLayerSource
if (!this._factoriesLayerSource) {
this._factoriesLayerSource = new VectorSource({ features: [] })

getFactoriesLayerForStatus (factoryStatus: FactoryDisplayStatusType) {
if (!this._factoriesLayerStatusMap[`${factoryStatus}`]) {
this._factoriesLayerStatusMap[`${factoryStatus}`] = new VectorSource({ features: [] })
const clusterSource = new Cluster({
distance: 50,
source: this._factoriesLayerStatusMap[`${factoryStatus}`]
})
const styleCache = {}
const vectorLayer = new VectorLayer({
source: this._factoriesLayerSource,
zIndex: 3
source: clusterSource,
zIndex: 3,
style: function (feature) {
const features = feature.get('features')
if (features.length > 1) {
const size = features.length
// eslint-disable-next-line
// @ts-ignore
let style = styleCache[size]
if (!style) {
style = new Style({
image: new Circle({
radius: 20,
stroke: new Stroke({
color: '#fff'
}),
fill: new Fill({
color: getStatusBorderColor(factoryStatus)
})
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff'
}),
scale: 1.8
})
})
// eslint-disable-next-line
// @ts-ignore
styleCache[size] = style
}
return style
} else {
const factoryFeature = features[0]
;(feature as Feature).set('factoryId', factoryFeature.get('factoryId'))
return factoryFeature.getStyle()
}
}
})

vectorLayer.setProperties({ factoryStatus })
this.mapInstance.map.addLayer(vectorLayer)
}

return this._factoriesLayerSource
return this._factoriesLayerStatusMap[`${factoryStatus}`]
}

public getFactory (id: string) {
Expand All @@ -142,27 +188,49 @@ export class MapFactoryController {
this.factoryMap.set(id, factory)

// Update factory feature style base on new data
const feature = this.factoriesLayerSource.getFeatureById(id)
const feature = this.getFactoriesLayerForStatus(getFactoryStatus(factory)).getFeatureById(id)
if (feature) {
const style = this.getFactoryStyle(factory)
feature.set('defaultStyle', style.clone())
featureStyleCache.set(id, style.clone())
feature.setStyle(style)
}
}

public addFactories (factories: FactoryData[]) {
const createFactoryFeature = this.createFactoryFeature.bind(this)
const filteredFactories = factories.filter(factory => !this.factoryMap.has(factory.id))
const features = filteredFactories.map(createFactoryFeature)
const factoriesToAdd = [] as FactoryData[]
const factoriesToUpdate = [] as FactoryData[]

this.factoriesLayerSource.addFeatures(features)
factories.forEach(factory => {
if (this.factoryMap.has(factory.id)) {
factoriesToUpdate.push(factory)
} else {
factoriesToAdd.push(factory)
}
})

const features = factoriesToAdd.map(createFactoryFeature)
interface FeatureFactoryStatusMap {
[key: string]: Feature[]
}
const featuresByFactoryStatus = {} as FeatureFactoryStatusMap
features.forEach((feature) => {
if (!featuresByFactoryStatus[feature.get('factoryStatus')]) {
featuresByFactoryStatus[feature.get('factoryStatus')] = []
}
featuresByFactoryStatus[feature.get('factoryStatus')].push(feature)
})
Object.entries(featuresByFactoryStatus).forEach(([factoryStatus, features]) => {
this.getFactoriesLayerForStatus(factoryStatus as FactoryDisplayStatusType).addFeatures(features)
})

filteredFactories.forEach((factory) => this.updateFactory(factory.id, factory))
// ? Disable updating factory style when fetching new factories, user now should refresh the page manually to get factory status updated
// factoriesToUpdate.forEach((factory) => this.updateFactory(factory.id, factory))
}

public hideFactories (factories: FactoryData[]) {
factories.forEach(factory => {
const feature = this.factoriesLayerSource.getFeatureById(factory.id)
const feature = this.getFactoriesLayerForStatus(getFactoryStatus(factory)).getFeatureById(factory.id)
feature.setStyle(nullStyle)
})
}
Expand All @@ -187,19 +255,25 @@ export class MapFactoryController {
geometry: new Point(transform([factory.lng, factory.lat], 'EPSG:4326', 'EPSG:3857'))
})
feature.setId(factory.id)
feature.set('factoryId', factory.id)
feature.set('factoryStatus', getFactoryStatus(factory))
const style = this.getFactoryStyle(factory)
feature.set('defaultStyle', style.clone())
featureStyleCache.set(factory.id, style.clone())
feature.setStyle(style)

this.factoryMap.set(factory.id, factory)

return feature
}

private forEachFeatureFactory (fn: (feature: Feature, factory: FactoryData) => void) {
this.factoriesLayerSource.getFeatures().forEach(feature => {
const id = feature.getId() as string
const factory = this.factoryMap.get(id) as FactoryData
Object.entries(this._factoriesLayerStatusMap).forEach(([, layerSource]) => {
layerSource.getFeatures().forEach(feature => {
const id = feature.getId() as string
const factory = this.factoryMap.get(id) as FactoryData

fn(feature, factory)
fn(feature, factory)
})
})
}

Expand Down Expand Up @@ -329,6 +403,9 @@ const getLUIMapLayer = (wmtsTileGrid: WMTSTileGrid) => {
}),
opacity: 0.5,
zIndex: 2,
// TS is wrong, for some reason className is a valid property
// eslint-disable-next-line
// @ts-ignore
className: 'lui-layer'
})
}
Expand Down Expand Up @@ -603,6 +680,9 @@ export class OLMap {
resolved = true
resolve(a !== 128)
}, {
// TS is wrong, for some reason layer does have .getClassName()
// eslint-disable-next-line
// @ts-ignore
layerFilter: (layer) => layer.getClassName() === 'lui-layer'
})

Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export type FactoryData = {

export type FactoriesResponse = Array<FactoryData>

export interface FactoriesByStatus {
[key: string]: FactoryData[]
}

export type FactoryPostData = {
name: string,
type?: FactoryType,
Expand Down

0 comments on commit 0a571e4

Please sign in to comment.