-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add OLED Display modules * add test * add size and resolution * typo * change name size to display_width * fix format
- Loading branch information
1 parent
e9e9df8
commit a3f1f9c
Showing
7 changed files
with
375 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import type { DerivedTableSpec } from "./types" | ||
import type { KyselyDatabaseInstance } from "../kysely-types" | ||
import { extractMinQPrice } from "lib/util/extract-min-quantity-price" | ||
|
||
interface OLEDDisplay { | ||
lcsc: number | ||
mfr: string | ||
description: string | ||
stock: number | ||
price1: number | null | ||
in_stock: boolean | ||
package?: string | ||
protocol?: string | ||
display_width?: string | ||
pixel_resolution?: string | ||
} | ||
|
||
export const oledDisplayTableSpec: DerivedTableSpec<OLEDDisplay> = { | ||
tableName: "oled_display", | ||
extraColumns: [ | ||
{ name: "package", type: "text" }, | ||
{ name: "protocol", type: "text" }, | ||
{ name: "display_width", type: "text" }, | ||
{ name: "pixel_resolution", type: "text" }, | ||
], | ||
|
||
listCandidateComponents(db: KyselyDatabaseInstance) { | ||
return db | ||
.selectFrom("components") | ||
.innerJoin("categories", "components.category_id", "categories.id") | ||
.selectAll() | ||
.where((eb) => eb("description", "like", "%OLED Display%")) | ||
}, | ||
|
||
mapToTable(components) { | ||
return components.map((c) => { | ||
try { | ||
const extraData = c.extra ? JSON.parse(c.extra) : {} | ||
const attrs = extraData.attributes || {} | ||
// Extract protocol from description or interface attribute | ||
let protocol | ||
if (c.description.includes("I2C")) { | ||
protocol = "I2C" | ||
} else if (attrs.Interface) { | ||
protocol = attrs.Interface | ||
} | ||
// Extract display_width and resolution from description | ||
let display_width = undefined | ||
let pixel_resolution = undefined | ||
const description = c.description || "" | ||
// Extract resolution (e.g., "128x64") | ||
const resMatch = description.match(/(\d+x\d+)/) | ||
if (resMatch) { | ||
pixel_resolution = resMatch[1] | ||
} | ||
// Extract display_width (e.g., "0.96") | ||
const widthMatch = description.match(/\s(\d+\.\d+)\s/) | ||
if (widthMatch) { | ||
display_width = widthMatch[1] | ||
} | ||
|
||
return { | ||
lcsc: Number(c.lcsc), | ||
mfr: String(c.mfr || ""), | ||
description: String(description), | ||
stock: Number(c.stock || 0), | ||
price1: extractMinQPrice(c.price), | ||
in_stock: Boolean((c.stock || 0) > 0), | ||
package: String(c.package || ""), | ||
protocol: protocol || undefined, | ||
display_width, | ||
pixel_resolution, | ||
} | ||
} catch (e) { | ||
return null | ||
} | ||
}) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
import list from "./list" | ||
export default list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
import { Table } from "lib/ui/Table" | ||
import { withWinterSpec } from "lib/with-winter-spec" | ||
import { z } from "zod" | ||
import { formatPrice } from "lib/util/format-price" | ||
|
||
export default withWinterSpec({ | ||
auth: "none", | ||
methods: ["GET", "POST"], | ||
commonParams: z.object({ | ||
json: z.boolean().optional(), | ||
package: z.string().optional(), | ||
protocol: z.string().optional(), | ||
display_width: z.string().optional(), | ||
pixel_resolution: z.string().optional(), | ||
mfr: z.string().optional(), | ||
description: z.string().optional(), | ||
}), | ||
jsonResponse: z.object({ | ||
oled_displays: z.array( | ||
z.object({ | ||
lcsc: z.number(), | ||
mfr: z.string(), | ||
package: z.string(), | ||
description: z.string(), | ||
stock: z.number(), | ||
price1: z.number(), | ||
protocol: z.string().optional(), | ||
display_width: z.string().optional(), | ||
pixel_resolution: z.string().optional(), | ||
}), | ||
), | ||
}), | ||
} as const)(async (req, ctx) => { | ||
const params = req.commonParams | ||
const limit = 100 | ||
const search = "OLED Display" | ||
const searchPattern = `%${search}%` | ||
|
||
let query = ctx.db | ||
.selectFrom("oled_display") | ||
.select([ | ||
"lcsc", | ||
"mfr", | ||
"package", | ||
"description", | ||
"stock", | ||
"price1", | ||
"protocol", | ||
"display_width", | ||
"pixel_resolution", | ||
] as const) | ||
.limit(limit) | ||
.orderBy("stock", "desc") | ||
.where("stock", ">", 0) | ||
.where((eb) => | ||
eb("description", "like", searchPattern) | ||
.or("mfr", "like", searchPattern) | ||
.or( | ||
search.match(/^\d+$/) | ||
? eb("lcsc", "=", parseInt(search)) | ||
: eb("description", "like", searchPattern), | ||
), | ||
) | ||
|
||
// Add filters for each column | ||
if (params.package) { | ||
query = query.where("package", "=", params.package) | ||
} | ||
if (params.protocol) { | ||
query = query.where("protocol", "=", params.protocol) | ||
} | ||
if (params.display_width) { | ||
query = query.where("display_width", "=", params.display_width) | ||
} | ||
if (params.pixel_resolution) { | ||
query = query.where("pixel_resolution", "=", params.pixel_resolution) | ||
} | ||
if (params.mfr) { | ||
query = query.where("mfr", "like", `%${params.mfr}%`) | ||
} | ||
if (params.description) { | ||
query = query.where("description", "like", `%${params.description}%`) | ||
} | ||
|
||
const fullComponents = await query.execute() | ||
const components = fullComponents.map((c) => ({ | ||
lcsc: c.lcsc, | ||
mfr: c.mfr, | ||
package: c.package, | ||
description: c.description, | ||
stock: c.stock, | ||
price: c.price1, | ||
protocol: c.protocol, | ||
display_width: c.display_width, | ||
pixel_resolution: c.pixel_resolution, | ||
})) | ||
|
||
if (ctx.isApiRequest) { | ||
return ctx.json({ | ||
oled_displays: fullComponents | ||
.map((c) => ({ | ||
lcsc: c.lcsc ?? 0, | ||
mfr: c.mfr ?? "", | ||
package: c.package ?? "", | ||
description: c.description ?? "", | ||
stock: c.stock ?? 0, | ||
price1: c.price1 ?? 0, | ||
protocol: c.protocol ?? undefined, | ||
display_width: c.display_width ?? undefined, | ||
pixel_resolution: c.pixel_resolution ?? undefined, | ||
})) | ||
.filter((c) => c.lcsc !== 0 && c.package !== ""), | ||
}) | ||
} | ||
|
||
const protocols = await ctx.db | ||
.selectFrom("oled_display") | ||
.select("protocol") | ||
.distinct() | ||
.orderBy("protocol") | ||
.execute() | ||
|
||
const packages = await ctx.db | ||
.selectFrom("oled_display") | ||
.select("package") | ||
.distinct() | ||
.orderBy("package") | ||
.execute() | ||
|
||
const widths = await ctx.db | ||
.selectFrom("oled_display") | ||
.select("display_width") | ||
.distinct() | ||
.orderBy("display_width") | ||
.execute() | ||
|
||
const resolutions = await ctx.db | ||
.selectFrom("oled_display") | ||
.select("pixel_resolution") | ||
.distinct() | ||
.orderBy("pixel_resolution") | ||
.execute() | ||
|
||
return ctx.react( | ||
<div> | ||
<h2>OLED Displays</h2> | ||
<form method="GET" className="flex flex-row gap-4"> | ||
<div> | ||
<label>Package: </label> | ||
<select name="package" className="border px-2 py-1 rounded"> | ||
<option value="">All</option> | ||
{packages.map((p) => ( | ||
<option | ||
key={p.package} | ||
value={p.package ?? ""} | ||
selected={p.package === params.package} | ||
> | ||
{p.package || "-"} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div> | ||
<label>Protocol: </label> | ||
<select name="protocol" className="border px-2 py-1 rounded"> | ||
<option value="">All</option> | ||
{protocols.map((p) => ( | ||
<option | ||
key={p.protocol} | ||
value={p.protocol ?? ""} | ||
selected={p.protocol === params.protocol} | ||
> | ||
{p.protocol || "N/A"} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div> | ||
<label>Display Width: </label> | ||
<select name="display_width" className="border px-2 py-1 rounded"> | ||
<option value="">All</option> | ||
{widths.map((w) => ( | ||
<option | ||
key={w.display_width} | ||
value={w.display_width ?? ""} | ||
selected={w.display_width === params.display_width} | ||
> | ||
{w.display_width || "N/A"} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<div> | ||
<label>Resolution: </label> | ||
<select name="pixel_resolution" className="border px-2 py-1 rounded"> | ||
<option value="">All</option> | ||
{resolutions.map((r) => ( | ||
<option | ||
key={r.pixel_resolution} | ||
value={r.pixel_resolution ?? ""} | ||
selected={r.pixel_resolution === params.pixel_resolution} | ||
> | ||
{r.pixel_resolution || "N/A"} | ||
</option> | ||
))} | ||
</select> | ||
</div> | ||
<button | ||
type="submit" | ||
className="bg-blue-500 text-white px-4 py-1 rounded hover:bg-blue-600" | ||
> | ||
Filter | ||
</button> | ||
</form> | ||
<Table | ||
rows={components.map((c) => ({ | ||
lcsc: c.lcsc, | ||
mfr: c.mfr, | ||
package: c.package || "-", | ||
description: c.description, | ||
display_width: c.display_width ?? "N/A", | ||
pixel_resolution: c.pixel_resolution ?? "N/A", | ||
stock: <span className="tabular-nums">{c.stock}</span>, | ||
price: <span className="tabular-nums">{formatPrice(c.price)}</span>, | ||
protocol: c.protocol ?? "N/A", | ||
}))} | ||
/> | ||
</div>, | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { test, expect } from "bun:test" | ||
import { getTestServer } from "../fixtures/get-test-server" | ||
|
||
test("GET /oled_display/list.json with json param returns OLED Display data", async () => { | ||
const { axios } = await getTestServer() | ||
|
||
const res = await axios.get("/oled_display/list.json?json=true") | ||
|
||
expect(res.data).toHaveProperty("oled_displays") | ||
expect(Array.isArray(res.data.oled_displays)).toBe(true) | ||
|
||
// check structure of first OLED Display if array not empty | ||
if (res.data.oled_displays.length > 0) { | ||
const oledDisplay = res.data.oled_displays[0] | ||
expect(oledDisplay).toHaveProperty("lcsc") | ||
expect(oledDisplay).toHaveProperty("mfr") | ||
expect(oledDisplay).toHaveProperty("package") | ||
expect(oledDisplay).toHaveProperty("description") | ||
expect(oledDisplay).toHaveProperty("stock") | ||
expect(oledDisplay).toHaveProperty("price1") | ||
expect(oledDisplay).toHaveProperty("protocol") | ||
|
||
expect(typeof oledDisplay.lcsc).toBe("number") | ||
expect(typeof oledDisplay.mfr).toBe("string") | ||
expect(typeof oledDisplay.package).toBe("string") | ||
expect(typeof oledDisplay.description).toBe("string") | ||
expect(typeof oledDisplay.stock).toBe("number") | ||
expect(typeof oledDisplay.price1).toBe("number") | ||
if (oledDisplay.protocol) { | ||
expect(typeof oledDisplay.protocol).toBe("string") | ||
} | ||
} | ||
}) | ||
|
||
test("GET /oled_display/list.json with filters returns filtered data", async () => { | ||
const { axios } = await getTestServer() | ||
|
||
// Test with package filter | ||
const res = await axios.get("/oled_display/list.json?json=true&package=QFN32") | ||
|
||
expect(res.data).toHaveProperty("oled_displays") | ||
expect(Array.isArray(res.data.oled_displays)).toBe(true) | ||
|
||
// Verify all returned OLED Displays have the specified package | ||
for (const oledDisplay of res.data.oled_displays) { | ||
expect(oledDisplay.package).toBe("QFN32") | ||
} | ||
|
||
// Test with protocol filter | ||
const protocolRes = await axios.get( | ||
"/oled_display/list.json?json=true&protocol=I2C", | ||
) | ||
|
||
expect(protocolRes.data).toHaveProperty("oled_displays") | ||
expect(Array.isArray(protocolRes.data.oled_displays)).toBe(true) | ||
|
||
// Verify all returned OLED Displays have the specified protocol | ||
for (const oledDisplay of protocolRes.data.oled_displays) { | ||
expect(oledDisplay.protocol).toBe("I2C") | ||
} | ||
}) |