Skip to content

Commit

Permalink
feat: add track data type & update icon schema
Browse files Browse the repository at this point in the history
* update some types, install locally generated mapeo-schema

* fix missing type errors

* horrible big logic prevails

* remove invalid fields from icons test, skip svg test with PixelDensity
(unnecessary now?)

* update icon sorting to reflect absense of pixelDensity on svgs

* remove unnecessary test

* typeguard when filtering valid pixel densities

Co-authored-by: Evan Hahn <me@evanhahn.com>

* fix missing type errors

* fix type (unknown)

* update @mapeo/schema

* feat: add tracks data type (#564)

* feat: add track data type

* Add e2e CRUD tests for tracks

---------

Co-authored-by: Gregor MacLennan <gmaclennan@digital-democracy.org>

* fix up package-lock.json

---------

Co-authored-by: Tomás Ciccola <tciccola@digital-democracy.com>
Co-authored-by: Evan Hahn <me@evanhahn.com>
Co-authored-by: Gregor MacLennan <gmaclennan@digital-democracy.org>
  • Loading branch information
4 people committed Apr 17, 2024
1 parent e7a7a95 commit 6e71c8d
Show file tree
Hide file tree
Showing 14 changed files with 316 additions and 108 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,27 @@ CREATE TABLE `role` (
`links` text NOT NULL,
`deleted` integer NOT NULL,
`roleId` text NOT NULL,
`fromIndex` real NOT NULL,
`fromIndex` integer NOT NULL,
`forks` text NOT NULL
);
--> statement-breakpoint
CREATE TABLE `track_backlink` (
`versionId` text PRIMARY KEY NOT NULL
);
--> statement-breakpoint
CREATE TABLE `track` (
`docId` text PRIMARY KEY NOT NULL,
`versionId` text NOT NULL,
`schemaName` text NOT NULL,
`createdAt` text NOT NULL,
`createdBy` text NOT NULL,
`updatedAt` text NOT NULL,
`links` text NOT NULL,
`deleted` integer NOT NULL,
`locations` text NOT NULL,
`refs` text NOT NULL,
`attachments` text NOT NULL,
`tags` text NOT NULL,
`forks` text NOT NULL
);
--> statement-breakpoint
Expand Down
120 changes: 118 additions & 2 deletions drizzle/project/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": "5",
"dialect": "sqlite",
"id": "f68815a6-4e7a-4ca7-9882-6f31bb34b716",
"id": "6f1659f1-038f-4705-8059-b075a5a2c82d",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"coreOwnership_backlink": {
Expand Down Expand Up @@ -863,7 +863,123 @@
},
"fromIndex": {
"name": "fromIndex",
"type": "real",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"forks": {
"name": "forks",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"track_backlink": {
"name": "track_backlink",
"columns": {
"versionId": {
"name": "versionId",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"track": {
"name": "track",
"columns": {
"docId": {
"name": "docId",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"versionId": {
"name": "versionId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"schemaName": {
"name": "schemaName",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"createdBy": {
"name": "createdBy",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"updatedAt": {
"name": "updatedAt",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"links": {
"name": "links",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"deleted": {
"name": "deleted",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"locations": {
"name": "locations",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"refs": {
"name": "refs",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"attachments": {
"name": "attachments",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"tags": {
"name": "tags",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
Expand Down
4 changes: 2 additions & 2 deletions drizzle/project/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1707965473123,
"tag": "0000_supreme_forgotten_one",
"when": 1713301821225,
"tag": "0000_burly_network",
"breakpoints": true
}
]
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
"@fastify/type-provider-typebox": "^3.3.0",
"@hyperswarm/secret-stream": "^6.1.2",
"@mapeo/crypto": "1.0.0-alpha.10",
"@mapeo/schema": "3.0.0-next.14",
"@mapeo/schema": "3.0.0-next.15",
"@mapeo/sqlite-indexer": "1.0.0-alpha.8",
"@sinclair/typebox": "^0.29.6",
"b4a": "^1.6.3",
Expand Down
2 changes: 1 addition & 1 deletion src/datastore/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { createMap } from '../utils.js'
*/

const NAMESPACE_SCHEMAS = /** @type {const} */ ({
data: ['observation'],
data: ['observation', 'track'],
config: [
'translation',
'preset',
Expand Down
33 changes: 18 additions & 15 deletions src/fastify-plugins/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ export default fp(iconServerPlugin, {
const ICON_DOC_ID_STRING = T.String({ pattern: HEX_REGEX_32_BYTES })
const PROJECT_PUBLIC_ID_STRING = T.String({ pattern: Z_BASE_32_REGEX_32_BYTES })

const VALID_SIZES =
docSchemas.icon.properties.variants.items.properties.size.enum
const VALID_MIME_TYPES =
docSchemas.icon.properties.variants.items.properties.mimeType.enum
const VALID_PIXEL_DENSITIES =
docSchemas.icon.properties.variants.items.properties.pixelDensity.enum
const VALID_SIZES = docSchemas.icon.definitions.size.enum
const VALID_MIME_TYPES = docSchemas.icon.properties.variants.items.oneOf.map(
(iconType) => iconType.properties.mimeType.const
)
const VALID_PIXEL_DENSITIES = docSchemas.icon.properties.variants.items.oneOf
.map((iconType) =>
'pixelDensity' in iconType.properties
? iconType.properties.pixelDensity.enum
: []
)
.flat()

const PARAMS_JSON_SCHEMA = T.Object({
iconDocId: ICON_DOC_ID_STRING,
Expand Down Expand Up @@ -101,7 +106,10 @@ const DENSITY_MATCH_REGEX = /@(\d+)x$/i
/**
* @param {string} input
*
* @return {Pick<import('@mapeo/schema').Icon['variants'][number], 'size' | 'pixelDensity'>}
* @return {{
* pixelDensity: import('../icon-api.js').BitmapOpts['pixelDensity'],
* size: import('../icon-api.js').IconVariant['size']
* }}
*/
function extractSizeAndPixelDensity(input) {
const result = DENSITY_MATCH_REGEX.exec(input)
Expand Down Expand Up @@ -138,16 +146,11 @@ function assertValidSize(value) {
}

/**
* @param {number} value
* @returns {asserts value is import('@mapeo/schema').Icon['variants'][number]['pixelDensity']}
* @param {unknown} value
* @returns {asserts value is import('../icon-api.js').BitmapOpts['pixelDensity']}
*/
function assertValidPixelDensity(value) {
if (
!VALID_PIXEL_DENSITIES.includes(
// @ts-expect-error
value
)
) {
if (!VALID_PIXEL_DENSITIES.includes(/** @type {any} */ (value))) {
throw new Error(`${value} is not a valid icon pixel density`)
}
}
51 changes: 23 additions & 28 deletions src/icon-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const kGetIconBlob = Symbol('getIcon')
/**
* @typedef {Object} BitmapOpts
* @property {Extract<IconVariant['mimeType'], 'image/png'>} mimeType
* @property {IconVariant['pixelDensity']} pixelDensity
* @property {Extract<IconVariant, {mimeType: 'image/png'}>['pixelDensity']} pixelDensity
* @property {IconVariant['size']} size
*
* @typedef {Object} SvgOpts
Expand Down Expand Up @@ -58,17 +58,7 @@ export class IconApi {
const savedVariants = await Promise.all(
icon.variants.map(async ({ blob, ...variant }) => {
const blobVersionId = await this.#dataStore.writeRaw(blob)

return {
...variant,
blobVersionId,
pixelDensity:
// Pixel density does not apply to svg variants
// TODO: Ideally @mapeo/schema wouldn't require pixelDensity when the mime type is svg
variant.mimeType === 'image/svg+xml'
? /** @type {const} */ (1)
: variant.pixelDensity,
}
return { ...variant, blobVersionId }
})
)

Expand Down Expand Up @@ -146,7 +136,7 @@ const SIZE_AS_NUMERIC = {
* 2. Matching size. If no exact match:
* 1. If smaller ones exist, prefer closest smaller size.
* 2. Otherwise prefer closest larger size.
* 3. Matching pixel density. If no exact match:
* 3. Matching pixel density (when asking for PNGs). If no exact match:
* 1. If smaller ones exist, prefer closest smaller density.
* 2. Otherwise prefer closest larger density.
*
Expand All @@ -155,10 +145,11 @@ const SIZE_AS_NUMERIC = {
*/
export function getBestVariant(variants, opts) {
const { size: wantedSize, mimeType: wantedMimeType } = opts
// Pixel density doesn't matter for svg so default to 1
const wantedPixelDensity =
opts.mimeType === 'image/svg+xml' ? 1 : opts.pixelDensity

/** @type {BitmapOpts['pixelDensity']} */
let wantedPixelDensity
if (opts.mimeType === 'image/png') {
wantedPixelDensity = opts.pixelDensity
}
if (variants.length === 0) {
throw new Error('No variants exist')
}
Expand All @@ -170,7 +161,6 @@ export function getBestVariant(variants, opts) {
`No variants with desired mime type ${wantedMimeType} exist`
)
}

const wantedSizeNum = SIZE_AS_NUMERIC[wantedSize]

// Sort the relevant variants based on the desired size and pixel density, using the rules of the preference.
Expand All @@ -182,18 +172,23 @@ export function getBestVariant(variants, opts) {
const aSizeDiff = aSizeNum - wantedSizeNum
const bSizeDiff = bSizeNum - wantedSizeNum

// Both variants match desired size, use pixel density to determine preferred match
// Both variants match desired size, use pixel density (when png) to determine preferred match
if (aSizeDiff === 0 && bSizeDiff === 0) {
// Pixel density doesn't matter for svg but prefer lower for consistent results
if (opts.mimeType === 'image/svg+xml') {
return a.pixelDensity <= b.pixelDensity ? -1 : 1
// What to do if asking for an svg and both (a and b) are svgs and have the same size, what criteria do we use?
// For now, we don't change sort order
if (wantedMimeType === 'image/svg+xml') {
return 0
} else if (
wantedMimeType === 'image/png' &&
a.mimeType === 'image/png' &&
b.mimeType === 'image/png'
) {
return determineSortValue(
wantedPixelDensity,
a.pixelDensity,
b.pixelDensity
)
}

return determineSortValue(
wantedPixelDensity,
a.pixelDensity,
b.pixelDensity
)
}

return determineSortValue(wantedSizeNum, aSizeNum, bSizeNum)
Expand Down
Loading

0 comments on commit 6e71c8d

Please sign in to comment.