Skip to content

Commit

Permalink
feat: integrate search into sidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
coderbyheart committed Feb 21, 2024
1 parent 1702aec commit 2b58169
Show file tree
Hide file tree
Showing 12 changed files with 398 additions and 166 deletions.
6 changes: 0 additions & 6 deletions src/MapApp.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Search } from './component/Search.js'
import { Devices } from './component/Devices.js'
import { Sidebar } from './component/Sidebar.js'

import './MapApp.css'
Expand All @@ -8,10 +6,6 @@ export const MapApp = () => {
return (
<>
<Sidebar />
<Search />
<main>
<Devices />
</main>
</>
)
}
7 changes: 7 additions & 0 deletions src/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ h6 {
font-weight: 300;
font-style: normal;
}

.boxed {
background-color: #ffffff22;
padding: 1rem;
border-radius: 10px;
box-shadow: 0 0 4px #000000b8;
}
22 changes: 22 additions & 0 deletions src/component/DeviceSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Device } from '../context/Devices.jsx'
import { link } from '../util/link.js'
import { Close } from './LucideIcon.js'
import { SidebarContent } from './Sidebar.js'

export const DeviceSidebar = ({ device }: { device: Device }) => (

Check warning on line 6 in src/component/DeviceSidebar.tsx

View workflow job for this annotation

GitHub Actions / main

Missing return type on function
<SidebarContent>
<header>
<h1>
<code>{device.id}</code>
</h1>
<a href={link('/#')} class="close">
<Close size={20} />
</a>
</header>
<div style={{ padding: '1rem' }}>
<div class="boxed">
<p>Model: {device.model}</p>
</div>
</div>
</SidebarContent>
)
21 changes: 1 addition & 20 deletions src/component/Devices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
LwM2MObjectID,
timestampResources,
type LwM2MResourceValue,
type LwM2MResourceInfo,
} from '@hello.nrfcloud.com/proto-lwm2m'
import { formatDistanceToNow } from 'date-fns'
export const Devices = () => {

Check warning on line 14 in src/component/Devices.tsx

View workflow job for this annotation

GitHub Actions / main

Missing return type on function
Expand Down Expand Up @@ -113,23 +114,3 @@ const DescribeResource = ({
</>
)
}

// FIXME: use from proto-lwm2m
type LwM2MResourceInfo = {
ResourceID: number
Name: string
Mandatory: boolean
Type: ResourceType
Description: string // e.g. 'The decimal notation of latitude, e.g. -43.5723 [World Geodetic System 1984].'
RangeEnumeration?: string // e.g. ''
Units?: string // e.g. 'lat'
}
// FIXME: use from proto-lwm2m
enum ResourceType {
String = 'String',
Integer = 'Integer',
Float = 'Float',
Boolean = 'Boolean',
Opaque = 'Opaque',
Time = 'Time',
}
88 changes: 37 additions & 51 deletions src/component/Search.css
Original file line number Diff line number Diff line change
@@ -1,42 +1,6 @@
aside.search {
position: absolute;
top: 0;
left: var(--base-size-sidebar);
width: calc(100vw - var(--base-size-sidebar));
display: flex;
justify-content: center;
}

@media (min-width: 600px) {
aside.search {
left: calc(2 * var(--base-size-sidebar));
width: calc(100vw - 4 * var(--base-size-sidebar));
}
}

aside.search div.wrapper {
display: flex;
flex-direction: column;
padding: 0.5rem;
align-items: center;
background-color: rgb(236, 236, 236);
color: #333;
border-top-color: white;
border-bottom-color: #333;
width: 100%;
max-width: 750px;
}

@media (min-width: 600px) {
aside.search div.wrapper {
flex-direction: row;
border-radius: 10px;
padding: 1rem;
}
}

aside.search .form-wrapper {
width: 100%;
aside.search .wrapper {
margin: 0 1rem;
--color-forms-text: #333;
}

aside.search form {
Expand All @@ -45,6 +9,7 @@ aside.search form {
}

aside.search form input[type="search"] {
color: var(--color-forms-text);
border-style: solid;
border-width: 1px;
border-right-width: 0;
Expand Down Expand Up @@ -80,17 +45,6 @@ aside.search form button svg {
opacity: 0.8;
}

aside.search div.stats {
padding: 0 0 0.25rem 0;
}

@media (min-width: 600px) {
aside.search div.stats {
padding: 0 1rem 0 0;
flex-shrink: 0;
}
}

aside.search div.terms {
margin-top: 0.5rem;
flex-direction: row;
Expand All @@ -103,7 +57,7 @@ aside.search div.terms button {
align-items: center;
color: #153e57;
border: 1px solid #00000033;
background-color: #ffffff66;
background-color: #ffffffde;
border-radius: 5px;
padding: 0.25rem 0.5rem;
}
Expand All @@ -116,3 +70,35 @@ aside.search div.terms button:first-child,
aside.search div.terms button + button {
margin-right: 0.25rem;
}

aside.search div.terms button svg {
margin-left: 0.5rem;
}

aside.search section.results {
margin: 2rem 1rem 1rem 1rem;
}

aside.search section.results header {
margin-bottom: 1rem;
}

aside.search section.results .result {
display: flex;
align-items: center;
margin-left: 0.25rem;
}

aside.search section.results .result .icon {
width: 24px;
margin-right: 0.5rem;
}

aside.search section.results .result small {
opacity: 0.75;
margin-left: 0.5rem;
}

aside.search section.results .result a {
text-decoration: underline;
}
165 changes: 114 additions & 51 deletions src/component/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,131 @@
import { useDevices } from '../context/Devices.js'
import { type Device, useDevices } from '../context/Devices.js'
import './Search.css'
import { AddToSearch, Close } from './LucideIcon.js'
import { createSignal, For, Show } from 'solid-js'
import { createSignal, For, Show, createEffect } from 'solid-js'
import { SidebarContent } from './Sidebar.jsx'
import { link } from '../util/link.js'
import { Device as DeviceIcon } from '../icons/Device.js'

export const Search = () => {
const devices = useDevices()
const [searchTerms, setSearchTerms] = createSignal<string[]>([
`id:pentacid-coxalgia-backheel`,
`model:PCA20035+solar`,
`model:PCA20035+solar`,
`model:PCA20035+solar`,
enum SearchTermType {
Id = 'id',
Model = 'model',
}
type SearchTerm = {
type: SearchTermType
term: string
}
const allowedTypes = [SearchTermType.Id, SearchTermType.Model]

const isSearchTermType = (term: unknown): term is SearchTermType =>
typeof term === 'string' && allowedTypes.includes((term ?? '') as any)

const Search = () => {
const [searchTerms, setSearchTerms] = createSignal<SearchTerm[]>([
{ type: SearchTermType.Id, term: 'pentacid-coxalgia-backheel' },
{ type: SearchTermType.Model, term: 'PCA20035+solar' },
])
let input!: HTMLInputElement

const addSearchTerm = () => {
console.log({ value: input.value })
if (input.value.length > 0) {
setSearchTerms((prev) => [...prev, input.value])
const [type, term] = input.value.split(':')
if (isSearchTermType(type) && term !== undefined) {
setSearchTerms((prev) => [...prev, { type, term } as SearchTerm])
}
}
input.value = ''
}

return (
<aside class="search">
<div class="wrapper">
<div class="stats">{devices().length} devices</div>
<div class="form-wrapper">
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
<>
<div class="wrapper boxed">
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
}}
>
<input
type="search"
placeholder='e.g. "id:<device id>"'
ref={input}
onKeyUp={(e) => {
if (e.key === 'Enter') addSearchTerm()
}}
>
<input
type="search"
placeholder='e.g. "id:<device id>"'
ref={input}
onKeyUp={(e) => {
if (e.key === 'Enter') addSearchTerm()
}}
/>
<button type="button">
<AddToSearch size={20} />
</button>
</form>
<Show when={searchTerms().length > 0}>
<div class="terms">
<For each={searchTerms()}>
{(term) => (
<button
type="button"
onClick={() =>
setSearchTerms((terms) => terms.filter((t) => t !== term))
}
>
{term}
<Close size={16} />
</button>
)}
</For>
</div>
</Show>
</div>
/>
<button type="button">
<AddToSearch size={20} />
</button>
</form>
<Show when={searchTerms().length > 0}>
<div class="terms">
<For each={searchTerms()}>
{(term) => (
<button
type="button"
onClick={() =>
setSearchTerms((terms) => terms.filter((t) => t !== term))
}
>
<span>{term.type}:</span>
<span>{term.term}</span>
<Close size={16} />
</button>
)}
</For>
</div>
</Show>
</div>
</aside>
<Show when={searchTerms().length > 0}>
<SearchResult terms={searchTerms()} />
</Show>
</>
)
}

export const Sidebar = () => {
const devices = useDevices()
const [numDevices, setNumDevices] = createSignal<number>(0)
createEffect(() => setNumDevices(devices().length))

return (
<SidebarContent class="search">
<header>
<h1>Search {numDevices()} devices</h1>
<a href={link('/#')} class="close">
<Close size={20} />
</a>
</header>
<Search />
</SidebarContent>
)
}

const SearchResult = ({ terms }: { terms: SearchTerm[] }) => {
const [results, setResults] = createSignal<Device[]>([])
const devices = useDevices()

createEffect(() => setResults(devices().filter(matches(terms))))

return (
<section class="results boxed">
<header>
<h2>Results</h2>
</header>
<For each={results()} fallback={<p>No matching devices found.</p>}>
{(device) => (
<span class="result">
<DeviceIcon class="icon" />
<a href={link(`/#id:${device.id}`)}>
<code>{device.id}</code>
</a>
<small>({device.model})</small>
</span>
)}
</For>
</section>
)
}

const matches = (terms: SearchTerm[]) => (/*device: Device*/) =>
terms.reduce((matches) => matches, true)
Loading

0 comments on commit 2b58169

Please sign in to comment.