diff --git a/next-app/README-Tech.md b/next-app/README-Tech.md index 66e998b3fe..f027d551ac 100644 --- a/next-app/README-Tech.md +++ b/next-app/README-Tech.md @@ -71,13 +71,11 @@ Of course choosing tailwindcss comes with a number of criticisms but we believe 1. The codebase can be kept free of class clutter by simply utlizing `@apply` in the ` diff --git a/next-app/src/lib/error/index.ts b/next-app/src/lib/error/index.ts index c4972f9494..49b3a42745 100644 --- a/next-app/src/lib/error/index.ts +++ b/next-app/src/lib/error/index.ts @@ -1,28 +1,14 @@ import { browser } from '$app/environment' import { writable, type Writable } from 'svelte/store' -interface LfError { - message: string, - code?: number, -} -export const error: Writable = writable({ message: '' }) - -export function throw_error(message: string, code: number = 0) { - throw set({ message, code }) -} +export const error: Writable = writable(Error()) -export const dismiss = set +export const dismiss = () => error.set(Error()) if (browser) { // https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror#window.addEventListenererror - window.addEventListener('error', (event: ErrorEvent) => set(event.error)) + window.addEventListener('error', (event: ErrorEvent) => error.set(event.error)) // https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent - window.onunhandledrejection = (event: PromiseRejectionEvent) => set(event.reason) -} - -function set({ message, code = 0 }: LfError) { - error.set({ code, message }) - - return { code, message } + window.onunhandledrejection = (event: PromiseRejectionEvent) => error.set(event.reason) } diff --git a/next-app/src/lib/fetch.ts b/next-app/src/lib/fetch.ts index ec83049dfe..193a3283e6 100644 --- a/next-app/src/lib/fetch.ts +++ b/next-app/src/lib/fetch.ts @@ -1,48 +1,37 @@ -import { throw_error } from '$lib/error' import { start, stop } from '$lib/progress' +import type { HttpMethod } from '@sveltejs/kit/types/private' -export async function CREATE(url, body) { return await custom_fetch('post' , url, body) } -export async function GET (url ) { return await custom_fetch('get' , url ) } -export async function UPDATE(url, body) { return await custom_fetch('put' , url, body) } -export async function DELETE(url ) { return await custom_fetch('delete', url ) } +type FetchArgs = { + url: string, + body?: object, +} +interface AdaptedFetchArgs extends FetchArgs { + method: HttpMethod, +} -// https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData -// export const upload = async formData => await CREATE('post', formData) +export async function CREATE({url, body}: FetchArgs) { return await adapted_fetch({method: 'POST' , url, body}) } +export async function GET ({url }: FetchArgs) { return await adapted_fetch({method: 'GET' , url }) } +export async function UPDATE({url, body}: FetchArgs) { return await adapted_fetch({method: 'PUT' , url, body}) } +export async function DELETE({url }: FetchArgs) { return await adapted_fetch({method: 'DELETE', url }) } // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Supplying_request_options -async function custom_fetch(method, url, body) { - const headers = { - 'content-type': 'application/json', - } - - // when dealing with FormData, i.e., when uploading files, allow the browser to set the request up - // so boundary information is built properly. - if (body instanceof FormData) { - delete headers['content-type'] - } else { - body = JSON.stringify(body) - } - +async function adapted_fetch({method, url, body}: AdaptedFetchArgs) { start(url) - const response = await fetch(url, { + + const response: Response = await fetch(url, { method, - headers, - body, + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(body), }) - .catch (throw_error) // these only occur for network errors, like these: - // * request made with a bad host, e.g., //httpbin - // * the host is refusing connections - // * client is offline, i.e., airplane mode or something - // * CORS preflight failures .finally(() => stop(url)) // reminder: fetch does not throw exceptions for non-200 responses (https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch) if (! response.ok) { const results = await response.json().catch(() => {}) || {} - const message = results.message || response.statusText - - throw_error(message, response.status) + throw Error(results.message || response.statusText) } return await response.json() diff --git a/next-app/src/lib/forms/Button.svelte b/next-app/src/lib/forms/Button.svelte index 1c09b49a9b..fe0859947d 100644 --- a/next-app/src/lib/forms/Button.svelte +++ b/next-app/src/lib/forms/Button.svelte @@ -1,14 +1,11 @@ - - - diff --git a/next-app/src/lib/forms/Form.svelte b/next-app/src/lib/forms/Form.svelte index 9f75430bbc..f6dcb75a09 100644 --- a/next-app/src/lib/forms/Form.svelte +++ b/next-app/src/lib/forms/Form.svelte @@ -2,7 +2,7 @@ - diff --git a/next-app/src/lib/stats/Stats.svelte b/next-app/src/lib/stats/Stats.svelte deleted file mode 100644 index e8e46f1419..0000000000 --- a/next-app/src/lib/stats/Stats.svelte +++ /dev/null @@ -1,4 +0,0 @@ - -
- -
diff --git a/next-app/src/lib/stats/index.js b/next-app/src/lib/stats/index.js deleted file mode 100644 index c96c624988..0000000000 --- a/next-app/src/lib/stats/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import Stats from './Stats.svelte' -import Stat from './Stat.svelte' - -Stats.Stat = Stat - -export default Stats diff --git a/next-app/src/routes/+layout.svelte b/next-app/src/routes/+layout.svelte index 5d12b4b518..9ba64a36ad 100644 --- a/next-app/src/routes/+layout.svelte +++ b/next-app/src/routes/+layout.svelte @@ -1,4 +1,4 @@ - diff --git a/next-app/src/routes/+page.svelte b/next-app/src/routes/+page.svelte index 7e115d5f7e..356aa27a56 100644 --- a/next-app/src/routes/+page.svelte +++ b/next-app/src/routes/+page.svelte @@ -1,12 +1,36 @@ @@ -67,7 +76,7 @@ { activity.date_locale } { action_display[activity.action] || activity.action } { activity.entry || '—' } - { activity.fields || '—' } + { activity.field_names || '—' } {:else} No activity diff --git a/next-app/src/routes/projects/[project_code]/activities/+server.ts b/next-app/src/routes/projects/[project_code]/activities/+server.ts index 26ccf6c6b1..55bce46c43 100644 --- a/next-app/src/routes/projects/[project_code]/activities/+server.ts +++ b/next-app/src/routes/projects/[project_code]/activities/+server.ts @@ -1,8 +1,43 @@ import { json } from '@sveltejs/kit' -import { sf } from '$lib/server/sf' +import { sf, type Rpc } from '$lib/server/sf' +import type { RequestEvent } from './$types' -export async function GET({ params: { project_code }, request: { headers } }) { - const cookie = headers.get('cookie') +type ActivitiesInput = { + cookie: string, + start_date?: Date, + end_date?: Date, +} + +type LegacyResult = { + activity: LegacyActivity[], +} + +export type Field = { + name: string, +} + +type LegacyActivity = { + id: string, + action: string, + date: string, + content: { + user: string, + entry?: string, + changes?: Field[], + }, +} + +export type Activity = { + id: string, + action: string, + date: string, + user: string, + entry: string, + fields: Field[], +} + +export async function GET({ params: { project_code }, request: { headers } }: RequestEvent) { + const cookie = headers.get('cookie') || '' await sf({ name: 'set_project', args: [ project_code ], cookie }) @@ -13,8 +48,8 @@ export async function GET({ params: { project_code }, request: { headers } }) { // src/Api/Model/Shared/Dto/ActivityListDto.php // src/Api/Model/Shared/Dto/ActivityListDto.php->ActivityListModel.__construct -export async function fetch_activities({ cookie, start_date, end_date }) { - const args = { +export async function fetch_activities({ cookie, start_date, end_date }: ActivitiesInput) { + const args: Rpc = { name: 'activity_list_dto_for_current_project', args: [ { @@ -26,12 +61,12 @@ export async function fetch_activities({ cookie, start_date, end_date }) { cookie, } - const { activity } = await sf(args) + const { activity }: LegacyResult = await sf(args) return activity.map(transform) } -function transform({ id, action, date, content }) { +function transform({ id, action, date, content }: LegacyActivity): Activity { return { id, action, diff --git a/next-app/src/routes/projects/[project_code]/meta/+server.ts b/next-app/src/routes/projects/[project_code]/meta/+server.ts index 14f178c6d4..35bc918f3d 100644 --- a/next-app/src/routes/projects/[project_code]/meta/+server.ts +++ b/next-app/src/routes/projects/[project_code]/meta/+server.ts @@ -2,11 +2,37 @@ import { can_view_comments } from '$lib/auth' import { fetch_current_user } from '$lib/server/user' import { sf } from '$lib/server/sf' +type LegacyProjectDetails = { + id: string, + projectName: string, + users: object[], +} + +type LegacyStats = { + entries: object[], + comments: Comment[], +} + +type Comment = { + status: string, +} + +export type ProjectDetails = { + id: string, + code: string, + name: string, + num_users: number, + num_entries: number, + num_entries_with_audio: number, + num_entries_with_pictures: number, + num_unresolved_comments?: number, +} + export async function fetch_project_details({ project_code, cookie }) { - const { id, projectName: name, users } = await sf({ name: 'set_project', args: [ project_code ], cookie }) - const { entries, comments } = await sf({ name: 'lex_stats', cookie }) + const { id, projectName: name, users }: LegacyProjectDetails = await sf({ name: 'set_project', args: [ project_code ], cookie }) + const { entries, comments }: LegacyStats = await sf({ name: 'lex_stats', cookie }) - const details = { + const details: ProjectDetails = { id, code: project_code, name, @@ -26,56 +52,11 @@ export async function fetch_project_details({ project_code, cookie }) { return details } -function has_picture(entry) { - return entry.senses?.some(sense => sense.pictures) +function has_picture(entry: object) { + return JSON.stringify(entry).includes('"pictures":') } // audio can be found in lots of places other than lexeme, ref impl used: https://github.com/sillsdev/web-languageforge/blob/develop/src/angular-app/bellows/core/offline/editor-data.service.ts#L523 -function has_audio(entry) { - const contains_audio = writing_system => writing_system.endsWith('-audio') // naming convention imposed by src/angular-app/languageforge/lexicon/settings/configuration/input-system-view.model.ts L81 - - // examples of possible locations where audio may be found in the entry's data: - // 1. Fields within an "entry" - // { - // lexeme: { - // '...-audio': '...' - // }, - // pronunciation: { - // '...-audio': '...' - // } - // } - const in_fields = fields => Object.keys(fields).some(name => Object.keys(fields[name]).some(contains_audio)) - - // 2. Fields within a "meaning" (note: senses may not be present) - // { - // lexeme: '...', - // pronunciation: '...', - // senses: [{ - // '...': { - // '...-audio': '...' - // } - // }] - // } - const in_meaning = (senses = []) => senses.some(in_fields) - - // 3. Fields within a "meaning"'s example (note: senses may not be present) - // { - // lexeme: '...', - // pronunciation: '...', - // senses: [{ - // examples: { - // sentence: { - // '...-audio': '...' - // }, - // '...': { - // '...-audio': '...' - // } - // } - // }] - // } - const in_example = (senses = []) => senses.some(sense => in_meaning(sense.examples)) - - const { senses, ...fields } = entry - - return in_fields(fields) || in_meaning(senses) || in_example(senses) +function has_audio(entry: object) { + return JSON.stringify(entry).includes('-audio":') // naming convention imposed by src/angular-app/languageforge/lexicon/settings/configuration/input-system-view.model.ts L81 }