Skip to content

Instantly share code, notes, and snippets.

@renoirb
Created November 5, 2021 01:41
Show Gist options
  • Select an option

  • Save renoirb/d0d92314b04927c8513a86810902a53a to your computer and use it in GitHub Desktop.

Select an option

Save renoirb/d0d92314b04927c8513a86810902a53a to your computer and use it in GitHub Desktop.
Lit.Dev with @lit-labs/context
import { LitElement, html, TemplateResult } from 'lit'
import { property } from 'lit/decorators/property.js'
import { state } from 'lit/decorators/state.js'
import { assert } from '@esm-bundle/chai'
import { ContextProvider, createContext } from '../context.js'
import { ContextConsumer } from '../lib/controllers/context-consumer.js'
/**
* Say we have many components that has disperse data that would come from
* many backends.
* The "userId" field can be used to tell which component it is targeted for.
* Sometimes, we already have list of "userId"s, but miss the rest of the data.
*/
interface IUserInfo {
/**
* The foreign key
*/
userId: string
}
/**
* A slice of user information might come from one data source.
*/
interface IUserInfoBasics extends IUserInfo {
firstName: string
lastName: string
}
// const UserInfoBasicsDefault: Readonly<IUserInfoBasics> = Object.freeze({
// userId: '',
// firstName: 'i18n:firstName',
// lastName: 'i18n:lastName',
// })
const PRESENCE_SYMBOLS = ['online', 'offline', 'away', 'unknown'] as const
type IPresenceSymbol = typeof PRESENCE_SYMBOLS[number]
/**
* Displaying its presence status from another data source.
*/
interface IUserInfoPresence extends IUserInfo {
presence: IPresenceSymbol
}
const UserInfoPresenceDefault: Readonly<IUserInfoPresence> = Object.freeze({
userId: '',
presence: 'unknown',
})
const extractPresence = (
maybe: { presence?: string } | unknown,
): IPresenceSymbol => {
let outcome: IPresenceSymbol = UserInfoPresenceDefault.presence
if (typeof maybe === 'object' && maybe && 'presence' in maybe) {
const dto = maybe as IUserInfoPresence
if (PRESENCE_SYMBOLS.includes(dto?.presence ?? '')) {
outcome = dto.presence
}
}
return outcome
}
const extractInitials = (dto: Omit<IUserInfoBasics, 'userId'>): string => {
const { firstName = '', lastName = '' } = dto ?? {}
return [firstName, lastName]
.map((i) => i.trim().toLocaleUpperCase().charAt(0))
.join('')
}
const UserInfoPresenceContext =
createContext<IUserInfoPresence>('user-info-presence')
const UserInfoBasicsContext = createContext<IUserInfoBasics>('user-info-basics')
class UserInfoPresenceContextProvider extends LitElement {
private provider = new ContextProvider(this, UserInfoPresenceContext, {
presence: 'unknown',
userId: '',
})
public setValue(value: IUserInfoPresence) {
this.provider.setValue(value)
}
}
class UserInfoBasicsContextProvider extends LitElement {
private provider = new ContextProvider(this, UserInfoBasicsContext, {
firstName: '',
lastName: '',
userId: '',
})
public setValue(value: IUserInfoBasics) {
this.provider.setValue(value)
}
}
class UserPresenceIndicator extends LitElement {
@property({ type: String, attribute: 'data-user-id' })
public userId = ''
@state()
private _presence: IPresenceSymbol | '' = ''
public constructor() {
super()
// this.setAttribute('data-presence', this._presence)
new ContextConsumer(this, UserInfoPresenceContext, this.#onCallback)
}
#onCallback = (value: IUserInfoPresence) => {
const { userId = '' } = value ?? {}
// Use in payload "userId" property (name not important)
// to help re-use same context object, yet target a destination
if (this.userId === userId) {
const presence = extractPresence(value)
this._presence = presence
// this.setAttribute('data-presence', this._presence)
}
}
render() {
// Just mimicking an actual presence indicator
const fillColor: Record<string, boolean> = {
white: this._presence === 'unknown' || this._presence === 'away',
green: this._presence === 'online',
black: this._presence === 'offline',
}
const strokeColor: Record<string, boolean> = {
white: fillColor.white === false,
black: fillColor.white,
}
return html`<!-- -->
<svg width="100" height="100">
<circle
cx="50"
cy="50"
r="40"
stroke=${strokeColor}
stroke-width="4"
fill=${fillColor}
/>
</svg>
<!-- -->`
}
}
class UserBadge extends LitElement implements IUserInfoBasics {
@property({ type: String, attribute: 'data-user-id' })
public readonly userId: string = ''
@state()
public firstName = ''
@state()
public lastName = ''
public constructor() {
super()
new ContextConsumer(this, UserInfoBasicsContext, this.#onCallback)
}
#onCallback = (value: IUserInfoBasics) => {
const { userId = '', ...rest } = value ?? {}
// Use in payload "userId" property (name not important)
// to help re-use same context object, yet target a destination
if (this.userId === userId) {
const { firstName = '', lastName = '' } = rest ?? {}
if (this.firstName !== firstName) {
this.firstName = firstName
}
if (this.lastName !== lastName) {
this.lastName = lastName
}
}
}
render(): TemplateResult {
const dto: Omit<IUserInfoBasics, 'userId'> = {
lastName: this.lastName,
firstName: this.firstName,
}
const initials = extractInitials(dto)
return html`<div title="${this.firstName} ${this.lastName}">
${initials}
<user-presence-indicator
data-user-id=${this.userId}
></user-presence-indicator>
</div>`
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment