Skip to content

Commit 6e71c8d

Browse files
tomasciccolaTomás CiccolaEvanHahngmaclennan
authored
feat: add track data type & update icon schema
* 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>
1 parent e7a7a95 commit 6e71c8d

14 files changed

Lines changed: 316 additions & 108 deletions

File tree

drizzle/project/0000_supreme_forgotten_one.sql renamed to drizzle/project/0000_burly_network.sql

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,27 @@ CREATE TABLE `role` (
145145
`links` text NOT NULL,
146146
`deleted` integer NOT NULL,
147147
`roleId` text NOT NULL,
148-
`fromIndex` real NOT NULL,
148+
`fromIndex` integer NOT NULL,
149+
`forks` text NOT NULL
150+
);
151+
--> statement-breakpoint
152+
CREATE TABLE `track_backlink` (
153+
`versionId` text PRIMARY KEY NOT NULL
154+
);
155+
--> statement-breakpoint
156+
CREATE TABLE `track` (
157+
`docId` text PRIMARY KEY NOT NULL,
158+
`versionId` text NOT NULL,
159+
`schemaName` text NOT NULL,
160+
`createdAt` text NOT NULL,
161+
`createdBy` text NOT NULL,
162+
`updatedAt` text NOT NULL,
163+
`links` text NOT NULL,
164+
`deleted` integer NOT NULL,
165+
`locations` text NOT NULL,
166+
`refs` text NOT NULL,
167+
`attachments` text NOT NULL,
168+
`tags` text NOT NULL,
149169
`forks` text NOT NULL
150170
);
151171
--> statement-breakpoint

drizzle/project/meta/0000_snapshot.json

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"version": "5",
33
"dialect": "sqlite",
4-
"id": "f68815a6-4e7a-4ca7-9882-6f31bb34b716",
4+
"id": "6f1659f1-038f-4705-8059-b075a5a2c82d",
55
"prevId": "00000000-0000-0000-0000-000000000000",
66
"tables": {
77
"coreOwnership_backlink": {
@@ -863,7 +863,123 @@
863863
},
864864
"fromIndex": {
865865
"name": "fromIndex",
866-
"type": "real",
866+
"type": "integer",
867+
"primaryKey": false,
868+
"notNull": true,
869+
"autoincrement": false
870+
},
871+
"forks": {
872+
"name": "forks",
873+
"type": "text",
874+
"primaryKey": false,
875+
"notNull": true,
876+
"autoincrement": false
877+
}
878+
},
879+
"indexes": {},
880+
"foreignKeys": {},
881+
"compositePrimaryKeys": {},
882+
"uniqueConstraints": {}
883+
},
884+
"track_backlink": {
885+
"name": "track_backlink",
886+
"columns": {
887+
"versionId": {
888+
"name": "versionId",
889+
"type": "text",
890+
"primaryKey": true,
891+
"notNull": true,
892+
"autoincrement": false
893+
}
894+
},
895+
"indexes": {},
896+
"foreignKeys": {},
897+
"compositePrimaryKeys": {},
898+
"uniqueConstraints": {}
899+
},
900+
"track": {
901+
"name": "track",
902+
"columns": {
903+
"docId": {
904+
"name": "docId",
905+
"type": "text",
906+
"primaryKey": true,
907+
"notNull": true,
908+
"autoincrement": false
909+
},
910+
"versionId": {
911+
"name": "versionId",
912+
"type": "text",
913+
"primaryKey": false,
914+
"notNull": true,
915+
"autoincrement": false
916+
},
917+
"schemaName": {
918+
"name": "schemaName",
919+
"type": "text",
920+
"primaryKey": false,
921+
"notNull": true,
922+
"autoincrement": false
923+
},
924+
"createdAt": {
925+
"name": "createdAt",
926+
"type": "text",
927+
"primaryKey": false,
928+
"notNull": true,
929+
"autoincrement": false
930+
},
931+
"createdBy": {
932+
"name": "createdBy",
933+
"type": "text",
934+
"primaryKey": false,
935+
"notNull": true,
936+
"autoincrement": false
937+
},
938+
"updatedAt": {
939+
"name": "updatedAt",
940+
"type": "text",
941+
"primaryKey": false,
942+
"notNull": true,
943+
"autoincrement": false
944+
},
945+
"links": {
946+
"name": "links",
947+
"type": "text",
948+
"primaryKey": false,
949+
"notNull": true,
950+
"autoincrement": false
951+
},
952+
"deleted": {
953+
"name": "deleted",
954+
"type": "integer",
955+
"primaryKey": false,
956+
"notNull": true,
957+
"autoincrement": false
958+
},
959+
"locations": {
960+
"name": "locations",
961+
"type": "text",
962+
"primaryKey": false,
963+
"notNull": true,
964+
"autoincrement": false
965+
},
966+
"refs": {
967+
"name": "refs",
968+
"type": "text",
969+
"primaryKey": false,
970+
"notNull": true,
971+
"autoincrement": false
972+
},
973+
"attachments": {
974+
"name": "attachments",
975+
"type": "text",
976+
"primaryKey": false,
977+
"notNull": true,
978+
"autoincrement": false
979+
},
980+
"tags": {
981+
"name": "tags",
982+
"type": "text",
867983
"primaryKey": false,
868984
"notNull": true,
869985
"autoincrement": false

drizzle/project/meta/_journal.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
{
66
"idx": 0,
77
"version": "5",
8-
"when": 1707965473123,
9-
"tag": "0000_supreme_forgotten_one",
8+
"when": 1713301821225,
9+
"tag": "0000_burly_network",
1010
"breakpoints": true
1111
}
1212
]

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@
128128
"@fastify/type-provider-typebox": "^3.3.0",
129129
"@hyperswarm/secret-stream": "^6.1.2",
130130
"@mapeo/crypto": "1.0.0-alpha.10",
131-
"@mapeo/schema": "3.0.0-next.14",
131+
"@mapeo/schema": "3.0.0-next.15",
132132
"@mapeo/sqlite-indexer": "1.0.0-alpha.8",
133133
"@sinclair/typebox": "^0.29.6",
134134
"b4a": "^1.6.3",

src/datastore/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { createMap } from '../utils.js'
2828
*/
2929

3030
const NAMESPACE_SCHEMAS = /** @type {const} */ ({
31-
data: ['observation'],
31+
data: ['observation', 'track'],
3232
config: [
3333
'translation',
3434
'preset',

src/fastify-plugins/icons.js

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ export default fp(iconServerPlugin, {
1313
const ICON_DOC_ID_STRING = T.String({ pattern: HEX_REGEX_32_BYTES })
1414
const PROJECT_PUBLIC_ID_STRING = T.String({ pattern: Z_BASE_32_REGEX_32_BYTES })
1515

16-
const VALID_SIZES =
17-
docSchemas.icon.properties.variants.items.properties.size.enum
18-
const VALID_MIME_TYPES =
19-
docSchemas.icon.properties.variants.items.properties.mimeType.enum
20-
const VALID_PIXEL_DENSITIES =
21-
docSchemas.icon.properties.variants.items.properties.pixelDensity.enum
16+
const VALID_SIZES = docSchemas.icon.definitions.size.enum
17+
const VALID_MIME_TYPES = docSchemas.icon.properties.variants.items.oneOf.map(
18+
(iconType) => iconType.properties.mimeType.const
19+
)
20+
const VALID_PIXEL_DENSITIES = docSchemas.icon.properties.variants.items.oneOf
21+
.map((iconType) =>
22+
'pixelDensity' in iconType.properties
23+
? iconType.properties.pixelDensity.enum
24+
: []
25+
)
26+
.flat()
2227

2328
const PARAMS_JSON_SCHEMA = T.Object({
2429
iconDocId: ICON_DOC_ID_STRING,
@@ -101,7 +106,10 @@ const DENSITY_MATCH_REGEX = /@(\d+)x$/i
101106
/**
102107
* @param {string} input
103108
*
104-
* @return {Pick<import('@mapeo/schema').Icon['variants'][number], 'size' | 'pixelDensity'>}
109+
* @return {{
110+
* pixelDensity: import('../icon-api.js').BitmapOpts['pixelDensity'],
111+
* size: import('../icon-api.js').IconVariant['size']
112+
* }}
105113
*/
106114
function extractSizeAndPixelDensity(input) {
107115
const result = DENSITY_MATCH_REGEX.exec(input)
@@ -138,16 +146,11 @@ function assertValidSize(value) {
138146
}
139147

140148
/**
141-
* @param {number} value
142-
* @returns {asserts value is import('@mapeo/schema').Icon['variants'][number]['pixelDensity']}
149+
* @param {unknown} value
150+
* @returns {asserts value is import('../icon-api.js').BitmapOpts['pixelDensity']}
143151
*/
144152
function assertValidPixelDensity(value) {
145-
if (
146-
!VALID_PIXEL_DENSITIES.includes(
147-
// @ts-expect-error
148-
value
149-
)
150-
) {
153+
if (!VALID_PIXEL_DENSITIES.includes(/** @type {any} */ (value))) {
151154
throw new Error(`${value} is not a valid icon pixel density`)
152155
}
153156
}

src/icon-api.js

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const kGetIconBlob = Symbol('getIcon')
66
/**
77
* @typedef {Object} BitmapOpts
88
* @property {Extract<IconVariant['mimeType'], 'image/png'>} mimeType
9-
* @property {IconVariant['pixelDensity']} pixelDensity
9+
* @property {Extract<IconVariant, {mimeType: 'image/png'}>['pixelDensity']} pixelDensity
1010
* @property {IconVariant['size']} size
1111
*
1212
* @typedef {Object} SvgOpts
@@ -58,17 +58,7 @@ export class IconApi {
5858
const savedVariants = await Promise.all(
5959
icon.variants.map(async ({ blob, ...variant }) => {
6060
const blobVersionId = await this.#dataStore.writeRaw(blob)
61-
62-
return {
63-
...variant,
64-
blobVersionId,
65-
pixelDensity:
66-
// Pixel density does not apply to svg variants
67-
// TODO: Ideally @mapeo/schema wouldn't require pixelDensity when the mime type is svg
68-
variant.mimeType === 'image/svg+xml'
69-
? /** @type {const} */ (1)
70-
: variant.pixelDensity,
71-
}
61+
return { ...variant, blobVersionId }
7262
})
7363
)
7464

@@ -146,7 +136,7 @@ const SIZE_AS_NUMERIC = {
146136
* 2. Matching size. If no exact match:
147137
* 1. If smaller ones exist, prefer closest smaller size.
148138
* 2. Otherwise prefer closest larger size.
149-
* 3. Matching pixel density. If no exact match:
139+
* 3. Matching pixel density (when asking for PNGs). If no exact match:
150140
* 1. If smaller ones exist, prefer closest smaller density.
151141
* 2. Otherwise prefer closest larger density.
152142
*
@@ -155,10 +145,11 @@ const SIZE_AS_NUMERIC = {
155145
*/
156146
export function getBestVariant(variants, opts) {
157147
const { size: wantedSize, mimeType: wantedMimeType } = opts
158-
// Pixel density doesn't matter for svg so default to 1
159-
const wantedPixelDensity =
160-
opts.mimeType === 'image/svg+xml' ? 1 : opts.pixelDensity
161-
148+
/** @type {BitmapOpts['pixelDensity']} */
149+
let wantedPixelDensity
150+
if (opts.mimeType === 'image/png') {
151+
wantedPixelDensity = opts.pixelDensity
152+
}
162153
if (variants.length === 0) {
163154
throw new Error('No variants exist')
164155
}
@@ -170,7 +161,6 @@ export function getBestVariant(variants, opts) {
170161
`No variants with desired mime type ${wantedMimeType} exist`
171162
)
172163
}
173-
174164
const wantedSizeNum = SIZE_AS_NUMERIC[wantedSize]
175165

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

185-
// Both variants match desired size, use pixel density to determine preferred match
175+
// Both variants match desired size, use pixel density (when png) to determine preferred match
186176
if (aSizeDiff === 0 && bSizeDiff === 0) {
187-
// Pixel density doesn't matter for svg but prefer lower for consistent results
188-
if (opts.mimeType === 'image/svg+xml') {
189-
return a.pixelDensity <= b.pixelDensity ? -1 : 1
177+
// 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?
178+
// For now, we don't change sort order
179+
if (wantedMimeType === 'image/svg+xml') {
180+
return 0
181+
} else if (
182+
wantedMimeType === 'image/png' &&
183+
a.mimeType === 'image/png' &&
184+
b.mimeType === 'image/png'
185+
) {
186+
return determineSortValue(
187+
wantedPixelDensity,
188+
a.pixelDensity,
189+
b.pixelDensity
190+
)
190191
}
191-
192-
return determineSortValue(
193-
wantedPixelDensity,
194-
a.pixelDensity,
195-
b.pixelDensity
196-
)
197192
}
198193

199194
return determineSortValue(wantedSizeNum, aSizeNum, bSizeNum)

0 commit comments

Comments
 (0)