Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Module/map/backend #524

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"css-loader": "1.0.0",
"date-fns": "^1.30.1",
"debounce": "^1.2.0",
"dexie": "^2.0.4",
"dompurify": "^1.0.10",
"dotenv": "6.0.0",
"dotenv-expand": "4.2.0",
Expand Down
10 changes: 5 additions & 5 deletions src/components/Avatar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ interface IState {
export class Avatar extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props)
this.state = {}
this.getUserAvatar(this.props.userName)
this.state = {
avatarUrl: this.getUserAvatar(this.props.userName),
}
}
get injected() {
return this.props as IInjected
Expand All @@ -48,9 +49,8 @@ export class Avatar extends React.Component<IProps, IState> {
notificationUnsubscribeAll()
}

async getUserAvatar(userName: string) {
const url = await this.injected.userStore.getUserAvatar(userName)
this.setState({ avatarUrl: url })
getUserAvatar(userName: string) {
return this.injected.userStore.getUserAvatar(userName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like something that should be done by state and @observer instead of waiting on the store to return something. What you are trying to achieve here should be in a lib, not in a store.

If you're wanting to use the store, then Generally speaking, you would have a placeholder, then on componentDidMount you would tell the store to getUserAvatar. The function would then do this async, set something inside it's internal state for on an @observeable, to which you would then bind that into the component using observer. I see that the code is referenced in the store again in a helper function, but that should really not be public, or should be libbed as above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. It used to be an async function which is why it was there (had to make a call to the database to get urls), but then I realised all the database method was actually doing was reformatting a string in a pretty obvious way. Will change.

}

render() {
Expand Down
6 changes: 6 additions & 0 deletions src/mocks/database.mock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IDbDoc } from 'src/models/common.models'
import { Database } from 'src/stores/database'

// by default all documents saved in the database should contain
// a small subset of meta, including _id, _created, and _modified fields
export const DB_META_MOCK = (): IDbDoc => Database.generateDocMeta('_mocks')
3 changes: 2 additions & 1 deletion src/mocks/maps.mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IDatabaseMapPin,
EntityType,
} from 'src/models/maps.models'
import { DB_META_MOCK } from './database.mock'

export const generatePins = (count: number): Array<IDatabaseMapPin> => {
const filters = generatePinFilters()
Expand All @@ -14,7 +15,7 @@ export const generatePins = (count: number): Array<IDatabaseMapPin> => {
const pinType = filters[Math.floor(Math.random() * filters.length)]

newPins.push({
id: '' + Math.random(),
...DB_META_MOCK(),
location: {
address: 'testing',
lat: 51 + (Math.random() * 1000 - 500) / 500,
Expand Down
7 changes: 4 additions & 3 deletions src/mocks/user.mock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ export const MOCK_USER: IUser = {
_authID: '123',
_deleted: false,
_createdBy: '123',
_created: toTimestamp('Friday, January 2, 2015 12:59 AM'),
_modified: toTimestamp('Friday, January 2, 2015 12:59 AM'),
_created: toTimestamp(new Date()),
_modified: toTimestamp(new Date()),
_lastActive: toTimestamp(new Date()),
country: '',
DHSite_id: 70134,
DHSite_mention_name: 'chris-m-clarke',
country: '',
}
1 change: 1 addition & 0 deletions src/models/common.models.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { firestore } from 'firebase/app'
export interface ITimestamp extends firestore.Timestamp {}

// by default all documents should be populated with the following fields
// this can be generated by the database using `database.generateDocMeta()`
export interface IDbDoc {
_id: string
_created: firestore.Timestamp
Expand Down
7 changes: 4 additions & 3 deletions src/models/maps.models.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
export interface IDatabaseMapPin {
id: string
import { IDbDoc } from './common.models'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. I saw this on the other models but didn't understand it, so thought I'd leave this part to you anyway 👍


export interface IDatabaseMapPin extends IDbDoc {
location: ILatLng & {
address: string
}
pinType: string
}

export interface IMapPin {
id: string
_id: string
location: ILatLng & {
address: string
}
Expand Down
12 changes: 8 additions & 4 deletions src/models/user.models.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { IDbDoc, ITimestamp } from './common.models'
import { ILocation } from 'src/components/LocationSearch/LocationSearch'

export interface IUserState {
user?: IUser
Expand All @@ -13,14 +12,18 @@ export interface ILink {
// IUser retains most of the fields from legacy users (omitting passwords),
// and has a few additional fields. Note 'email' is excluded
// _uid is unique/fixed identifier
// ALL USER INFO BELOW IS PUBLIC
/***************************************************************************
* NOTE - ALL USER INFO BELOW IS PUBLIC
****************************************************************************/
export interface IUser extends IDbDoc {
// authID is additional id populated by firebase auth, required for some auth operations
_authID: string
// want to track active status, however requires upgrade of users in db if want non-optional here
_lastActive?: ITimestamp
// userName is same as legacy 'mention_name', e.g. @my-name. It will also be the doc _id and
// firebase auth displayName property
userName: string
// note, user avatar url is taken direct from userName so no longer populated here
// NOTE, user avatar url is taken direct from userName so no longer populated here
// avatar:string
verified: boolean
userRoles?: UserRole[]
Expand All @@ -29,7 +32,8 @@ export interface IUser extends IDbDoc {
DHSite_mention_name?: string
country?: string
links?: ILink[]
location?: ILocation
// NOTE, user location is stored in the users map pin, and kept out of profile to avoid duplication
// location?: ILocation
year?: ITimestamp
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/Maps/Content/Controls/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Controls extends React.Component<IProps> {
position: 'absolute',
left: '10%',
borderRadius: '5px',
zIndex: 99999,
zIndex: 2,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha, good hack fix is good

marginTop: '30px',
}}
>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Maps/Content/View/Cluster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Clusters: React.SFC<IProps> = ({ pins, onPinClick }) => {
>
{entities[key].map(pin => (
<Marker
key={pin.id}
key={pin._id}
position={[pin.location.lat, pin.location.lng]}
icon={createMarkerIcon(pin)}
onClick={() => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Maps/Content/View/Popup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class Popup extends React.Component<IProps> {

if (
prevProps.pinDetail === undefined ||
prevProps.pinDetail.id !== this.props.pinDetail!.id
prevProps.pinDetail.id !== this.props.pinDetail!._id
) {
this.setPinLocation(this.props.pinDetail)
}
Expand Down
11 changes: 10 additions & 1 deletion src/pages/Maps/Content/View/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class MapView extends React.Component<IProps, IState> {
center={[center.lat, center.lng]}
zoom={zoom}
maxZoom={18}
style={{ height: '100%' }}
style={{ height: '100%', zIndex: 1 }}
onMove={this.updateBoundingBox}
>
<TileLayer
Expand All @@ -79,6 +79,15 @@ class MapView extends React.Component<IProps, IState> {
</Map>
)
}

static defaultProps: Partial<IProps> = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I get what you mean on slack now. Yes, for all intents and purposes, the click binders are actually not "required". It is required for a functioning application, but it's not required in the grand scheme of things. In theory this(ese) components could be a bit more generic, and could thus be thrown into the components folder. Specifically the map view stuff. Hrrmm, food for thought another day though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, my main reason was so I could include it in the profile module without having to write a click handler (was feeling lazy), but I think it also does give that option of making even more standalone. But yeah, later days.

onBoundingBoxChange: () => null,
onPinClicked: () => null,
pins: [],
filters: [],
center: { lat: 51.0, lng: 19.0 },
zoom: 3,
}
}

export { MapView }
2 changes: 2 additions & 0 deletions src/pages/Settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { TextNotification } from 'src/components/Notification/TextNotification'
import { Flex } from 'rebass'
import { Avatar } from 'src/components/Avatar'
import Text from 'src/components/Text'
import { UserMapPinEdit } from './content/UserMapPinEdit'

interface IProps {
user: IUser
Expand Down Expand Up @@ -59,6 +60,7 @@ export class UserSettings extends React.Component<IProps, IState> {
</BoxContainer>

<SettingsEditForm onProfileSave={() => this.showSaveNotification()} />
<UserMapPinEdit />
</BoxContainer>
{/* post guidelines container */}
<BoxContainer
Expand Down
111 changes: 11 additions & 100 deletions src/pages/Settings/content/SettingsEdit.form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,12 @@ import { getCountryCode } from 'src/utils/helpers'
import 'react-flags-select/scss/react-flags-select.scss'
import styled from 'styled-components'
import theme from 'src/themes/styled.theme'

import { Map, TileLayer, Marker, Popup, ZoomControl } from 'react-leaflet'
import L from 'leaflet'
import 'leaflet/dist/leaflet.css'
import { LocationSearchField } from 'src/components/Form/LocationSearch.field'
import { FieldArray } from 'react-final-form-arrays'
import { Link } from './Link.field'
import { timestampToYear } from 'src/utils/helpers'
import { Icon } from 'src/components/Icons'
import { toJS } from 'mobx'
import { ILocation } from 'src/components/LocationSearch/LocationSearch'

interface IFormValues extends Partial<IUser> {
// form values are simply subset of user profile fields
Expand All @@ -48,10 +44,6 @@ interface IState {
showYearSelector?: boolean
showNotification?: boolean
showComLinks?: boolean
showMap?: boolean
lat: number
lng: number
zoom: number
}

// validation - return undefined if no error (i.e. valid)
Expand All @@ -63,12 +55,6 @@ const FlagSelectContainer = styled(Flex)`
height: 40px;
`

const customMarker = L.icon({
iconUrl: require('src/assets/icons/map-marker.png'),
iconSize: [20, 28],
iconAnchor: [20, 56],
})

const YearBox = styled(Box)`
${inputStyles}
cursor: pointer;
Expand All @@ -92,32 +78,15 @@ export class SettingsEditForm extends React.Component<IProps, IState> {
formValues: user ? user : {},
readOnly: true,
showYearSelector: false,
showComLinks: true,
showMap: true,
lat: 51.4416,
lng: 5.4697,
zoom: 8,
}
if (user) {
if (user.links) {
this.setState({ showComLinks: false })
}
if (user.location) {
this.setState({ showMap: false })
}
showComLinks: user && user.links ? true : false,
}
this.changeComLinkSwitch = this.changeComLinkSwitch.bind(this)
this.changeMapSwitch = this.changeMapSwitch.bind(this)
}

public changeComLinkSwitch() {
this.setState({ showComLinks: !this.state.showComLinks })
}

public changeMapSwitch() {
this.setState({ showMap: !this.state.showMap })
}

get injected() {
return this.props as IInjectedProps
}
Expand All @@ -140,16 +109,11 @@ export class SettingsEditForm extends React.Component<IProps, IState> {
}
}

public onLocationChange(v) {
this.setState({ lat: v.latlng.lat, lng: v.latlng.lng, zoom: 15 })
}

render() {
const user = this.injected.userStore.user
// Need to convert mobx observable user object into a Javasrcipt structure using toJS fn
// to allow final-form-array to display the initial values
const initialFormValues = toJS(user)
const { lat, lng, zoom } = this.state

return user ? (
<Form
Expand Down Expand Up @@ -253,70 +217,17 @@ export class SettingsEditForm extends React.Component<IProps, IState> {
)}
</FieldArray>
</HideShowBox>
</BoxContainer>
<BoxContainer id="your-map-pin" mt={4}>
<Heading small bold>
Your map pin
</Heading>
<Flex wrap={'nowrap'} alignItems={'center'}>
<Text inline>Add me to the map</Text>
<Switch
checked={this.state.showMap}
onChange={this.changeMapSwitch}
/>
</Flex>
<HideShowBox disabled={this.state.showMap}>
<Field
name="about"
component={TextAreaField}
placeholder="About"
/>
<Field
name={
user && user.location
? `${user.location.value}`
: 'location'
}
customChange={v => this.onLocationChange(v)}
component={LocationSearchField}
/>
<Map
center={
user.location
? [user.location.latlng.lat, user.location.latlng.lng]
: [lat, lng]
}
zoom={zoom}
zoomControl={false}
style={{
height: '300px',
zIndex: 1,
}}
>
<ZoomControl position="topright" />
<TileLayer
attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker
position={
user.location && user.location.latlng
? [
user.location.latlng.lat,
user.location.latlng.lng,
]
: [lat, lng]
}
icon={customMarker}
>
<Popup maxWidth={225} minWidth={225}>
Add more content here later
</Popup>
</Marker>
</Map>
</HideShowBox>
<Text width={1} mt={2} medium>
About Me
</Text>
<Field
name="about"
component={TextAreaField}
placeholder="About"
/>
</BoxContainer>
</form>
{/* Map update separate to rest of form */}
</>
)
}}
Expand Down
Loading