All files / lib/schemas sessions.js

81.25% Statements 13/16
0% Branches 0/2
83.33% Functions 5/6
81.25% Lines 13/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149                      20x             20x             20x                                                                           20x                             20x           20x           50x           20x           20x                                 50x                 50x                 50x                 50x    
import { scope, type } from 'arktype';
 
// schemas are imported by scripts that are run by Bun directly, so dont use $lib here
import { keys } from '../utils.js';
import { ID, NamespacedMetadataID } from './common.js';
import { MetadataValues } from './metadata.js';
 
/**
 * @import * as DB from '$lib/database.js';
 */
 
export const SORT_FIELDS = /** @type {const} */ ({
	metadataValue: { label: 'Métadonnée…', needsMetadata: true },
	metadataConfidence: { label: 'Confiance en…', needsMetadata: true },
	id: { label: 'ID', needsMetadata: false },
	name: { label: 'Nom', needsMetadata: false },
});
 
export const GROUP_FIELDS = /** @type {const} */ ({
	metadataValue: { label: 'Métadonnée…', needsMetadata: true, needsTolerance: true },
	metadataPresence: { label: 'Présence de…', needsMetadata: true, needsTolerance: false },
	metadataConfidence: { label: 'Confiance en…', needsMetadata: true, needsTolerance: false },
	none: { label: 'Aucun regroupement', needsMetadata: false, needsTolerance: false },
});
 
export const GROUPING_TOLERANCES = /** @type {const} */ ({
	dates: {
		label: 'Dates',
		help: 'Précision des dates',
		affectedTypes: /** @type {DB.MetadataType[]} */ (['date']),
		options: {
			year: { scientific: '', casual: 'Par année' },
			month: { scientific: '', casual: 'Par mois' },
			day: { scientific: '', casual: 'Par jour' },
			hour: { scientific: '', casual: 'Par heure' },
			minute: { scientific: '', casual: 'Par minute' },
		},
	},
	decimal: {
		label: 'Nombres',
		help: 'Précision des nombres',
		affectedTypes: /** @type {DB.MetadataType[]} */ ([
			'integer',
			'float',
			'boundingbox',
			'location',
		]),
		options: {
			giga: { scientific: 'G', casual: 'Au milliard' },
			mega: { scientific: 'M', casual: 'Au million' },
			kilo: { scientific: 'k', casual: 'Au millier' },
			hecto: { scientific: '100', casual: 'À la centaine' },
			deca: { scientific: '10', casual: 'À la dizaine' },
			unit: { scientific: '1', casual: "À l'entier" },
			deci: { scientific: '0.1', casual: 'Au dixième' },
			centi: { scientific: '0.01', casual: 'Au centième' },
			milli: { scientific: 'm', casual: 'Au millième' },
			micro: { scientific: 'µ', casual: 'Au millionième' },
			nano: { scientific: 'n', casual: 'Au milliardième' },
		},
	},
});
 
export const FULLSCREEN_CLASSIFY_LAYOUTS = /** @type {const} */ (['top-bottom', 'left-right']);
 
/**
 * @param {['sort', keyof typeof SORT_FIELDS] | ['group', keyof typeof GROUP_FIELDS]} param0 [task, field]
 * @returns {boolean}
 */
export function sortOrGroupFieldNeedsMetadata(...[task, field]) {
	switch (task) {
		case 'sort':
			return SORT_FIELDS[field].needsMetadata;
		case 'group':
			return GROUP_FIELDS[field].needsMetadata;
	}
}
 
export const SortSettings = type({
	field: type.enumerated(...keys(SORT_FIELDS)),
	'metadata?': ID,
	direction: type.enumerated('asc', 'desc'),
});
 
export const GroupSettings = type({
	field: type.enumerated(...keys(GROUP_FIELDS)),
	'metadata?': ID,
	tolerances: type({
		dates: type.enumerated(...keys(GROUPING_TOLERANCES.dates.options)),
		decimal: type.enumerated(...keys(GROUPING_TOLERANCES.decimal.options)),
	}).default(() => ({
		dates: 'day',
		decimal: 'unit',
	})),
});
 
export const SessionRemoteID = type('string#Session.remote');
 
/**
 * @typedef {typeof SessionRemoteID['infer']} SessionRemoteID
 */
 
export const Session = type({
	id: ID,
	/** When the session is hosted on a remote server. On the root of the object for indexing purposes. Empty if session is local-only  */
	remoteId: SessionRemoteID.default(''),
	/** When the session is hosted on a remote server */
	'account?': ID,
	name: 'string',
	// Date is not compatible with JSON Schemas, use a datestring instead
	createdAt: 'string.date.iso',
	openedAt: 'string.date.iso',
	description: 'string',
	protocol: ID,
	metadata: MetadataValues,
	fullscreenClassifier: type({
		layout: type.enumerated(...FULLSCREEN_CLASSIFY_LAYOUTS),
		'focusedMetadata?': NamespacedMetadataID,
		'narrowableGroup?': 'string',
	}).default(() => ({
		layout: 'top-bottom',
	})),
	sort: type({
		global: SortSettings,
		// Per-tab
		'import?': SortSettings,
		'crop?': SortSettings,
		'classify?': SortSettings,
	}).default(() => ({
		global: { field: 'id', direction: 'asc' },
	})),
	group: type({
		global: GroupSettings,
		// Per-tab
		'import?': GroupSettings,
		'crop?': GroupSettings,
		'classify?': GroupSettings,
	}).default(() => ({
		global: { field: 'none' },
	})),
	inferenceModels: scope({ ID })
		.type({
			// -1 is for none selected
			'[ID]': 'number.integer >= -1',
		})
		.describe('Maps metadata IDs to selected model indices')
		.default(() => ({})),
});