1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-01 15:11:21 +01:00

Merge pull request #642 from immich-app/add/ci-web-checks

Add web test / check commands and workflow to run in CI
This commit is contained in:
Jaime Baez 2022-09-08 19:12:39 +02:00 committed by GitHub
commit cc79ff1ca3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 324 additions and 274 deletions

View file

@ -28,3 +28,14 @@ jobs:
- name: Run tests - name: Run tests
run: cd server && npm ci && npm run check:all run: cd server && npm ci && npm run check:all
web-unit-tests:
name: Run web unit test suites and checks
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Run tests
run: cd web && npm ci && npm run check:all

32
dev-setup.md Normal file
View file

@ -0,0 +1,32 @@
# Development Setup
## Lint / format extensions
Setting these in the IDE give a better developer experience auto-formatting code on save and providing instant feedback on lint issues.
### VSCode
Install Prettier, ESLint and Svelte extensions.
in User `settings.json` (`cmd + shift + p` and search for Open User Settings JSON) add the following:
```json
{
"editor.formatOnSave": true,
"[javascript][typescript][css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode",
"editor.tabSize": 2
},
"svelte.enable-ts-plugin": true,
"eslint.validate": ["javascript", "svelte"]
}
```
## Running tests / checks
In both server and web:
`npm run check:all`

View file

@ -16,5 +16,8 @@ module.exports = {
browser: true, browser: true,
es2017: true, es2017: true,
node: true node: true
},
globals: {
NodeJS: true
} }
}; };

View file

@ -6,6 +6,9 @@ node_modules
.env .env
.env.* .env.*
!.env.example !.env.example
src/api/open-api
*.md
*.json
# Ignore files for PNPM, NPM and YARN # Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml pnpm-lock.yaml

View file

@ -6,10 +6,14 @@
"build": "vite build", "build": "vite build",
"package": "svelte-kit package", "package": "svelte-kit package",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json", "check": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "npm run check -- --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .", "check:code": "npm run format && npm run lint && npm run check",
"format": "prettier --write --plugin-search-dir=. .", "check:all": "npm run check:code && npm test",
"lint": "eslint . --max-warnings 0",
"lint:fix": "npm run lint -- --fix",
"format": "prettier --check --plugin-search-dir=. .",
"format:fix": "prettier --write --plugin-search-dir=. .",
"test": "jest", "test": "jest",
"test:watch": "npm test -- --watch" "test:watch": "npm test -- --watch"
}, },

View file

@ -1,5 +1,4 @@
import { AssetCountByTimeGroupResponseDto } from '@api'; const _basePath = '/api';
let _basePath = '/api';
export function getFileUrl(aid: string, did: string, isThumb?: boolean, isWeb?: boolean) { export function getFileUrl(aid: string, did: string, isThumb?: boolean, isWeb?: boolean) {
const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file`); const urlObj = new URL(`${window.location.origin}${_basePath}/asset/file`);

View file

@ -51,7 +51,7 @@ input:focus-visible {
@layer utilities { @layer utilities {
.immich-form-input { .immich-form-input {
@apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm ; @apply bg-slate-100 p-2 rounded-md focus:border-immich-primary text-sm;
} }
.immich-form-label { .immich-form-label {

View file

@ -15,12 +15,11 @@
import { AlbumResponseDto, api, ThumbnailFormat } from '@api'; import { AlbumResponseDto, api, ThumbnailFormat } from '@api';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte'; import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import { fly } from 'svelte/transition';
import CircleIconButton from '../shared-components/circle-icon-button.svelte'; import CircleIconButton from '../shared-components/circle-icon-button.svelte';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
let imageData: string = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`; let imageData = `/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`;
const dispatchClick = createEventDispatcher<OnClick>(); const dispatchClick = createEventDispatcher<OnClick>();
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>(); const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
@ -29,7 +28,7 @@
return; return;
} }
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId!, ThumbnailFormat.Jpeg, { const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Jpeg, {
responseType: 'blob' responseType: 'blob'
}); });

View file

@ -11,7 +11,6 @@
import CircleAvatar from '../shared-components/circle-avatar.svelte'; import CircleAvatar from '../shared-components/circle-avatar.svelte';
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
import AssetSelection from './asset-selection.svelte'; import AssetSelection from './asset-selection.svelte';
import _ from 'lodash-es';
import UserSelectionModal from './user-selection-modal.svelte'; import UserSelectionModal from './user-selection-modal.svelte';
import ShareInfoModal from './share-info-modal.svelte'; import ShareInfoModal from './share-info-modal.svelte';
import CircleIconButton from '../shared-components/circle-icon-button.svelte'; import CircleIconButton from '../shared-components/circle-icon-button.svelte';
@ -53,8 +52,7 @@
let currentViewAssetIndex = 0; let currentViewAssetIndex = 0;
let viewWidth: number; let viewWidth: number;
let thumbnailSize: number = 300; let thumbnailSize = 300;
let border = '';
let backUrl = '/albums'; let backUrl = '/albums';
let currentAlbumName = ''; let currentAlbumName = '';
let currentUser: UserResponseDto; let currentUser: UserResponseDto;

View file

@ -2,9 +2,7 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte'; import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte'; import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
import TrashCanOutline from 'svelte-material-icons/TrashCanOutline.svelte';
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte'; import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
import CircleIconButton from '../shared-components/circle-icon-button.svelte'; import CircleIconButton from '../shared-components/circle-icon-button.svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

View file

@ -18,7 +18,7 @@
$: { $: {
appearsInAlbums = []; appearsInAlbums = [];
api.albumApi.getAllAlbums(undefined, asset.id).then(result => { api.albumApi.getAllAlbums(undefined, asset.id).then((result) => {
appearsInAlbums = result.data; appearsInAlbums = result.data;
}); });
} }
@ -29,12 +29,14 @@
let isShowDetail = false; let isShowDetail = false;
let appearsInAlbums: AlbumResponseDto[] = []; let appearsInAlbums: AlbumResponseDto[] = [];
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
onMount(() => { onMount(() => {
document.addEventListener('keydown', (keyInfo) => handleKeyboardPress(keyInfo.key)); document.addEventListener('keydown', onKeyboardPress);
}); });
onDestroy(() => { onDestroy(() => {
document.removeEventListener('keydown', (e) => {}); document.removeEventListener('keydown', onKeyboardPress);
}); });
const handleKeyboardPress = (key: string) => { const handleKeyboardPress = (key: string) => {

View file

@ -9,10 +9,14 @@
import { browser } from '$app/env'; import { browser } from '$app/env';
import { AssetResponseDto, AlbumResponseDto } from '@api'; import { AssetResponseDto, AlbumResponseDto } from '@api';
type Leaflet = typeof import('leaflet');
type LeafletMap = import('leaflet').Map;
type LeafletMarker = import('leaflet').Marker;
// Map Property // Map Property
let map: any; let map: LeafletMap;
let leaflet: any; let leaflet: Leaflet;
let marker: any; let marker: LeafletMarker;
export let asset: AssetResponseDto; export let asset: AssetResponseDto;
$: if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) { $: if (asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null) {
@ -31,7 +35,6 @@
async function drawMap(lat: number, lon: number) { async function drawMap(lat: number, lon: number) {
if (!leaflet) { if (!leaflet) {
// @ts-ignore
leaflet = await import('leaflet'); leaflet = await import('leaflet');
} }

View file

@ -10,7 +10,7 @@
export let root: HTMLElement | null = null; export let root: HTMLElement | null = null;
let intersecting = false; let intersecting = false;
let container: any; let container: HTMLDivElement;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
onMount(() => { onMount(() => {

View file

@ -33,7 +33,9 @@
const assetData = URL.createObjectURL(data); const assetData = URL.createObjectURL(data);
return assetData; return assetData;
} catch (e) {} } catch {
// Do nothing
}
}; };
</script> </script>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { createEventDispatcher, onMount } from 'svelte'; import { onMount } from 'svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte'; import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { api, AssetResponseDto, getFileUrl } from '@api'; import { api, AssetResponseDto, getFileUrl } from '@api';
@ -9,8 +9,6 @@
let asset: AssetResponseDto; let asset: AssetResponseDto;
const dispatch = createEventDispatcher();
let videoPlayerNode: HTMLVideoElement; let videoPlayerNode: HTMLVideoElement;
let isVideoLoading = true; let isVideoLoading = true;
let videoUrl: string; let videoUrl: string;

View file

@ -5,8 +5,8 @@
let error: string; let error: string;
let success: string; let success: string;
let password: string = ''; let password = '';
let confirmPassowrd: string = ''; let confirmPassowrd = '';
let canRegister = false; let canRegister = false;

View file

@ -6,8 +6,8 @@
let error: string; let error: string;
let success: string; let success: string;
let password: string = ''; let password = '';
let confirmPassowrd: string = ''; let confirmPassowrd = '';
let changeChagePassword = false; let changeChagePassword = false;

View file

@ -34,7 +34,7 @@
const firstName = form.get('firstName'); const firstName = form.get('firstName');
const lastName = form.get('lastName'); const lastName = form.get('lastName');
const {status} = await api.userApi.createUser({ const { status } = await api.userApi.createUser({
email: String(email), email: String(email),
password: String(password), password: String(password),
firstName: String(firstName), firstName: String(firstName),
@ -55,7 +55,7 @@
<div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8"> <div class="border bg-white p-4 shadow-sm w-[500px] rounded-3xl py-8">
<div class="flex flex-col place-items-center place-content-center gap-4 px-4"> <div class="flex flex-col place-items-center place-content-center gap-4 px-4">
<img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo"/> <img class="text-center" src="/immich-logo.svg" height="100" width="100" alt="immich-logo" />
<h1 class="text-2xl text-immich-primary font-medium">Create new user</h1> <h1 class="text-2xl text-immich-primary font-medium">Create new user</h1>
<p class="text-sm border rounded-md p-4 font-mono text-gray-600"> <p class="text-sm border rounded-md p-4 font-mono text-gray-600">
Please provide your user with the password, they will have to change it on their first sign Please provide your user with the password, they will have to change it on their first sign
@ -66,7 +66,7 @@
<form on:submit|preventDefault={registerUser} autocomplete="off"> <form on:submit|preventDefault={registerUser} autocomplete="off">
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="email">Email</label> <label class="immich-form-label" for="email">Email</label>
<input class="immich-form-input" id="email" name="email" type="email" required/> <input class="immich-form-input" id="email" name="email" type="email" required />
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
@ -95,12 +95,12 @@
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="firstName">First Name</label> <label class="immich-form-label" for="firstName">First Name</label>
<input class="immich-form-input" id="firstName" name="firstName" type="text" required/> <input class="immich-form-input" id="firstName" name="firstName" type="text" required />
</div> </div>
<div class="m-4 flex flex-col gap-2"> <div class="m-4 flex flex-col gap-2">
<label class="immich-form-label" for="lastName">Last Name</label> <label class="immich-form-label" for="lastName">Last Name</label>
<input class="immich-form-input" id="lastName" name="lastName" type="text" required/> <input class="immich-form-input" id="lastName" name="lastName" type="text" required />
</div> </div>
{#if error} {#if error}
@ -115,8 +115,7 @@
type="submit" type="submit"
class="m-4 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium" class="m-4 bg-immich-primary hover:bg-immich-primary/75 px-6 py-3 text-white rounded-full shadow-md w-full font-medium"
>Create >Create
</button </button>
>
</div> </div>
</form> </form>
</div> </div>

View file

@ -14,7 +14,6 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
// eslint-disable-next-line no-undef
const editUser = async (event: SubmitEvent) => { const editUser = async (event: SubmitEvent) => {
try { try {
const formElement = event.target as HTMLFormElement; const formElement = event.target as HTMLFormElement;
@ -25,8 +24,8 @@
const { status } = await api.userApi.updateUser({ const { status } = await api.userApi.updateUser({
id: user.id, id: user.id,
firstName: firstName!.toString(), firstName: firstName?.toString(),
lastName: lastName!.toString() lastName: lastName?.toString()
}); });
if (status == 200) { if (status == 200) {

View file

@ -4,8 +4,8 @@
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
let error: string; let error: string;
let email: string = ''; let email = '';
let password: string = ''; let password = '';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

View file

@ -7,7 +7,6 @@
import lodash from 'lodash-es'; import lodash from 'lodash-es';
import moment from 'moment'; import moment from 'moment';
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
import { createEventDispatcher } from 'svelte';
import { import {
assetInteractionStore, assetInteractionStore,
assetsInAlbumStoreState, assetsInAlbumStoreState,
@ -22,7 +21,7 @@
let isMouseOverGroup = false; let isMouseOverGroup = false;
let actualBucketHeight: number; let actualBucketHeight: number;
let hoveredDateGroup: string = ''; let hoveredDateGroup = '';
$: assetsGroupByDate = lodash $: assetsGroupByDate = lodash
.chain(assets) .chain(assets)
.groupBy((a) => moment(a.createdAt).format('ddd, MMM DD YYYY')) .groupBy((a) => moment(a.createdAt).format('ddd, MMM DD YYYY'))

View file

@ -1,8 +1,5 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { page } from '$app/stores';
import FullScreenModal from './full-screen-modal.svelte'; import FullScreenModal from './full-screen-modal.svelte';
export let localVersion: string; export let localVersion: string;
export let remoteVersion: string; export let remoteVersion: string;

View file

@ -22,7 +22,7 @@
onDestroy(() => { onDestroy(() => {
if (browser) { if (browser) {
window.onscroll = function () {}; window.onscroll = null;
} }
}); });
</script> </script>

View file

@ -5,7 +5,7 @@
export let user: UserResponseDto; export let user: UserResponseDto;
// Avatar Size In Pixel // Avatar Size In Pixel
export let size: number = 48; export let size = 48;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

View file

@ -4,10 +4,12 @@
*/ */
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
// TODO: why any here?
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let logo: any; export let logo: any;
export let backgroundColor: string = 'transparent'; export let backgroundColor = 'transparent';
export let hoverColor: string = '#e2e7e9'; export let hoverColor = '#e2e7e9';
export let logoColor: string = '#5f6368'; export let logoColor = '#5f6368';
export let size = '24'; export let size = '24';
export let title = ''; export let title = '';
let iconButton: HTMLButtonElement; let iconButton: HTMLButtonElement;

View file

@ -6,15 +6,13 @@
/** /**
* x coordiante of the context menu. * x coordiante of the context menu.
* @type {number}
*/ */
export let x: number = 0; export let x = 0;
/** /**
* x coordiante of the context menu. * x coordiante of the context menu.
* @type {number}
*/ */
export let y: number = 0; export let y = 0;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();

View file

@ -12,21 +12,23 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
onMount(() => { const onScroll = () => {
if (browser) {
document.addEventListener('scroll', (e) => {
if (window.pageYOffset > 80) { if (window.pageYOffset > 80) {
appBarBorder = 'border border-gray-200 bg-gray-50'; appBarBorder = 'border border-gray-200 bg-gray-50';
} else { } else {
appBarBorder = 'bg-immich-bg border border-transparent'; appBarBorder = 'bg-immich-bg border border-transparent';
} }
}); };
onMount(() => {
if (browser) {
document.addEventListener('scroll', onScroll);
} }
}); });
onDestroy(() => { onDestroy(() => {
if (browser) { if (browser) {
document.removeEventListener('scroll', (e) => {}); document.removeEventListener('scroll', onScroll);
} }
}); });
</script> </script>

View file

@ -6,7 +6,7 @@
import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte'; import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte';
import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte'; import PauseCircleOutline from 'svelte-material-icons/PauseCircleOutline.svelte';
import LoadingSpinner from './loading-spinner.svelte'; import LoadingSpinner from './loading-spinner.svelte';
import { api, AssetResponseDto, AssetTypeEnum, getFileUrl, ThumbnailFormat } from '@api'; import { AssetResponseDto, AssetTypeEnum, getFileUrl, ThumbnailFormat } from '@api';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -14,14 +14,14 @@
export let groupIndex = 0; export let groupIndex = 0;
export let thumbnailSize: number | undefined = undefined; export let thumbnailSize: number | undefined = undefined;
export let format: ThumbnailFormat = ThumbnailFormat.Webp; export let format: ThumbnailFormat = ThumbnailFormat.Webp;
export let selected: boolean = false; export let selected = false;
export let disabled: boolean = false; export let disabled = false;
let imageData: string; let imageData: string;
let mouseOver: boolean = false; let mouseOver = false;
$: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex }); $: dispatch('mouse-event', { isMouseOver: mouseOver, selectedGroupIndex: groupIndex });
let mouseOverIcon: boolean = false; let mouseOverIcon = false;
let videoPlayerNode: HTMLVideoElement; let videoPlayerNode: HTMLVideoElement;
let isThumbnailVideoPlaying = false; let isThumbnailVideoPlaying = false;
let calculateVideoDurationIntervalHandler: NodeJS.Timer; let calculateVideoDurationIntervalHandler: NodeJS.Timer;

View file

@ -2,7 +2,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page } from '$app/stores'; import { page } from '$app/stores';
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { fade, fly, slide } from 'svelte/transition'; import { fade, fly } from 'svelte/transition';
import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte'; import TrayArrowUp from 'svelte-material-icons/TrayArrowUp.svelte';
import { clickOutside } from '../../utils/click-outside'; import { clickOutside } from '../../utils/click-outside';
import { api, UserResponseDto } from '@api'; import { api, UserResponseDto } from '@api';

View file

@ -3,13 +3,10 @@
/** /**
* Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}> * Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
*
* @param {HTMLElement} el
* @param {HTMLElement|string} target DOM Element or CSS Selector
*/ */
export function portal(el: any, target: any = 'body') { export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
let targetEl; let targetEl;
async function update(newTarget: any) { async function update(newTarget: HTMLElement | string) {
target = newTarget; target = newTarget;
if (typeof target === 'string') { if (typeof target === 'string') {
targetEl = document.querySelector(target); targetEl = document.querySelector(target);

View file

@ -3,14 +3,14 @@
import { SegmentScrollbarLayout } from './segment-scrollbar-layout'; import { SegmentScrollbarLayout } from './segment-scrollbar-layout';
export let scrollTop = 0; export let scrollTop = 0;
export let viewportWidth = 0; // export let viewportWidth = 0;
export let scrollbarHeight = 0; export let scrollbarHeight = 0;
let timelineHeight = 0; let timelineHeight = 0;
let segmentScrollbarLayout: SegmentScrollbarLayout[] = []; let segmentScrollbarLayout: SegmentScrollbarLayout[] = [];
let isHover = false; let isHover = false;
let hoveredDate: Date; let hoveredDate: Date;
let currentMouseYLocation: number = 0; let currentMouseYLocation = 0;
let scrollbarPosition = 0; let scrollbarPosition = 0;
$: { $: {
@ -34,20 +34,18 @@
onMount(() => { onMount(() => {
// segmentScrollbarLayout = getLayoutDistance(); // segmentScrollbarLayout = getLayoutDistance();
return () => {};
}); });
const getSegmentHeight = (groupCount: number) => { // const getSegmentHeight = (groupCount: number) => {
// if (segmentData.groups.length > 0) { // if (segmentData.groups.length > 0) {
// const percentage = (groupCount * 100) / segmentData.totalAssets; // const percentage = (groupCount * 100) / segmentData.totalAssets;
// return Math.round((percentage * scrollbarHeight) / 100); // return Math.round((percentage * scrollbarHeight) / 100);
// } else { // } else {
// return 0; // return 0;
// } // }
}; // };
const getLayoutDistance = () => { // const getLayoutDistance = () => {
// let result: SegmentScrollbarLayout[] = []; // let result: SegmentScrollbarLayout[] = [];
// for (const segment of segmentData.groups) { // for (const segment of segmentData.groups) {
// let segmentLayout = new SegmentScrollbarLayout(); // let segmentLayout = new SegmentScrollbarLayout();
@ -57,7 +55,7 @@
// result.push(segmentLayout); // result.push(segmentLayout);
// } // }
// return result; // return result;
}; // };
const handleMouseMove = (e: MouseEvent, currentDate: Date) => { const handleMouseMove = (e: MouseEvent, currentDate: Date) => {
currentMouseYLocation = e.clientY - 71 - 30; currentMouseYLocation = e.clientY - 71 - 30;

View file

@ -1,5 +1,7 @@
<script lang="ts"> <script lang="ts">
export let title: string; export let title: string;
// TODO: why `any` here? There should be a expected type for this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export let logo: any; export let logo: any;
export let actionType: AdminSideBarSelection | AppSideBarSelection; export let actionType: AdminSideBarSelection | AppSideBarSelection;
export let isSelected: boolean; export let isSelected: boolean;

View file

@ -8,13 +8,13 @@
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte'; import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
import SideBarButton from './side-bar-button.svelte'; import SideBarButton from './side-bar-button.svelte';
import StatusBox from '../status-box.svelte'; import StatusBox from '../status-box.svelte';
import { AlbumCountResponseDto, api, AssetCountByUserIdResponseDto } from '@api'; import { api } from '@api';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import LoadingSpinner from '../loading-spinner.svelte'; import LoadingSpinner from '../loading-spinner.svelte';
let selectedAction: AppSideBarSelection; let selectedAction: AppSideBarSelection;
let showAssetCount: boolean = false; let showAssetCount = false;
let showSharingCount = false; let showSharingCount = false;
let showAlbumsCount = false; let showAlbumsCount = false;

View file

@ -5,8 +5,6 @@
import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte'; import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte';
import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte'; import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
import type { UploadAsset } from '$lib/models/upload-asset'; import type { UploadAsset } from '$lib/models/upload-asset';
import { goto } from '$app/navigation';
import { assetStore } from '$lib/stores/assets.store';
import { notificationController, NotificationType } from './notification/notification'; import { notificationController, NotificationType } from './notification/notification';
let showDetail = true; let showDetail = true;
@ -22,9 +20,13 @@
const blob = new Blob([arrayBufferView], { type: 'image/jpeg' }); const blob = new Blob([arrayBufferView], { type: 'image/jpeg' });
const urlCreator = window.URL || window.webkitURL; const urlCreator = window.URL || window.webkitURL;
const imageUrl = urlCreator.createObjectURL(blob); const imageUrl = urlCreator.createObjectURL(blob);
// TODO: There is probably a cleaner way of doing this
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const img: any = document.getElementById(`${a.id}`); const img: any = document.getElementById(`${a.id}`);
img.src = imageUrl; img.src = imageUrl;
} catch (e) {} } catch {
// Do nothing?
}
} }
}; };

View file

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api'; import { AlbumResponseDto, api, ThumbnailFormat, UserResponseDto } from '@api';
import { onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let album: AlbumResponseDto; export let album: AlbumResponseDto;
@ -11,7 +10,7 @@
return '/no-thumbnail.png'; return '/no-thumbnail.png';
} }
const { data } = await api.assetApi.getAssetThumbnail(thubmnailId!, ThumbnailFormat.Webp, { const { data } = await api.assetApi.getAssetThumbnail(thubmnailId, ThumbnailFormat.Webp, {
responseType: 'blob' responseType: 'blob'
}); });
if (data instanceof Blob) { if (data instanceof Blob) {

View file

@ -16,17 +16,17 @@ export class AssetGridState {
* The total height of the timeline in pixel * The total height of the timeline in pixel
* This value is first estimated by the number of asset and later is corrected as the user scroll * This value is first estimated by the number of asset and later is corrected as the user scroll
*/ */
timelineHeight: number = 0; timelineHeight = 0;
/** /**
* The fixed viewport height in pixel * The fixed viewport height in pixel
*/ */
viewportHeight: number = 0; viewportHeight = 0;
/** /**
* The fixed viewport width in pixel * The fixed viewport width in pixel
*/ */
viewportWidth: number = 0; viewportWidth = 0;
/** /**
* List of bucket information * List of bucket information

View file

@ -1,9 +1,6 @@
import { TimeGroupEnum } from './../../api/open-api/api'; import { writable } from 'svelte/store';
import { writable, derived, readable } from 'svelte/store';
import lodash from 'lodash-es'; import lodash from 'lodash-es';
import _ from 'lodash'; import { api, AssetCountByTimeBucketResponseDto } from '@api';
import moment from 'moment';
import { api, AssetCountByTimeBucketResponseDto, AssetResponseDto } from '@api';
import { AssetGridState } from '$lib/models/asset-grid-state'; import { AssetGridState } from '$lib/models/asset-grid-state';
import { calculateViewportHeightByNumberOfAsset } from '$lib/utils/viewport-utils'; import { calculateViewportHeightByNumberOfAsset } from '$lib/utils/viewport-utils';
@ -24,7 +21,7 @@ function createAssetStore() {
_loadingBucketState = state; _loadingBucketState = state;
}); });
/** /**
* Set intial state * Set initial state
* @param viewportHeight * @param viewportHeight
* @param viewportWidth * @param viewportWidth
* @param data * @param data
@ -78,6 +75,7 @@ function createAssetStore() {
return state; return state;
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) { } catch (e: any) {
if (e.name === 'CanceledError') { if (e.name === 'CanceledError') {
return; return;

View file

@ -19,7 +19,8 @@ export const openWebsocketConnection = () => {
}; };
const listenToEvent = (socket: Socket) => { const listenToEvent = (socket: Socket) => {
socket.on('on_upload_success', (data) => {}); //TODO: if we are not using this, we should probably remove it?
socket.on('on_upload_success', () => undefined);
socket.on('error', (e) => { socket.on('error', (e) => {
console.log('Websocket Error', e); console.log('Websocket Error', e);

View file

@ -1,6 +1,7 @@
export function clickOutside(node: Node) { export function clickOutside(node: Node) {
const handleClick = (event: any) => { const handleClick = (event: Event) => {
if (!node.contains(event.target)) { const targetNode = event.target as Node | null;
if (!node.contains(targetNode)) {
node.dispatchEvent(new CustomEvent('out-click')); node.dispatchEvent(new CustomEvent('out-click'));
} }
}; };

View file

@ -27,14 +27,18 @@ export enum UploadType {
export const openFileUploadDialog = (uploadType: UploadType) => { export const openFileUploadDialog = (uploadType: UploadType) => {
try { try {
let fileSelector = document.createElement('input'); const fileSelector = document.createElement('input');
fileSelector.type = 'file'; fileSelector.type = 'file';
fileSelector.multiple = true; fileSelector.multiple = true;
fileSelector.accept = 'image/*,video/*,.heic,.heif,.dng,.3gp'; fileSelector.accept = 'image/*,video/*,.heic,.heif,.dng,.3gp';
fileSelector.onchange = async (e: any) => { fileSelector.onchange = async (e: Event) => {
const files = Array.from<File>(e.target.files); const target = e.target as HTMLInputElement;
if (!target.files) {
return;
}
const files = Array.from<File>(target.files);
if (files.length > 50) { if (files.length > 50) {
notificationController.show({ notificationController.show({
@ -67,6 +71,7 @@ export const openFileUploadDialog = (uploadType: UploadType) => {
} }
}; };
//TODO: should probably use the @api SDK
async function fileUploader(asset: File, uploadType: UploadType) { async function fileUploader(asset: File, uploadType: UploadType) {
const assetType = asset.type.split('/')[0].toUpperCase(); const assetType = asset.type.split('/')[0].toUpperCase();
const temp = asset.name.split('.'); const temp = asset.name.split('.');
@ -123,9 +128,10 @@ async function fileUploader(asset: File, uploadType: UploadType) {
if (status === 200) { if (status === 200) {
if (data.isExist) { if (data.isExist) {
if (uploadType === UploadType.ALBUM && data.id) { const dataId = data.id;
if (uploadType === UploadType.ALBUM && dataId) {
albumUploadAssetStore.asset.update((a) => { albumUploadAssetStore.asset.update((a) => {
return [...a, data.id!]; return [...a, dataId];
}); });
} }
return; return;
@ -145,7 +151,7 @@ async function fileUploader(asset: File, uploadType: UploadType) {
uploadAssetsStore.addNewUploadAsset(newUploadAsset); uploadAssetsStore.addNewUploadAsset(newUploadAsset);
}; };
request.upload.onload = (event) => { request.upload.onload = () => {
setTimeout(() => { setTimeout(() => {
uploadAssetsStore.removeUploadAsset(deviceAssetId); uploadAssetsStore.removeUploadAsset(deviceAssetId);
}, 1000); }, 1000);
@ -170,7 +176,7 @@ async function fileUploader(asset: File, uploadType: UploadType) {
}; };
// listen for `error` event // listen for `error` event
request.upload.onerror = (event) => { request.upload.onerror = () => {
uploadAssetsStore.removeUploadAsset(deviceAssetId); uploadAssetsStore.removeUploadAsset(deviceAssetId);
}; };
@ -192,9 +198,10 @@ async function fileUploader(asset: File, uploadType: UploadType) {
console.log('error uploading file ', e); console.log('error uploading file ', e);
} }
} }
// TODO: This should have a proper type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function handleUploadError(asset: File, respBody?: any) { function handleUploadError(asset: File, respBody?: any) {
let extraMsg = respBody ? ' ' + respBody.message : ''; const extraMsg = respBody ? ' ' + respBody.message : '';
notificationController.show({ notificationController.show({
type: NotificationType.Error, type: NotificationType.Error,

View file

@ -1,4 +1,4 @@
import { serverApi, TimeGroupEnum } from '@api'; import { serverApi } from '@api';
import * as cookieParser from 'cookie'; import * as cookieParser from 'cookie';
import type { LayoutServerLoad } from './$types'; import type { LayoutServerLoad } from './$types';

View file

@ -7,7 +7,6 @@
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte'; import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { checkAppVersion } from '$lib/utils/check-app-version'; import { checkAppVersion } from '$lib/utils/check-app-version';
import { page } from '$app/stores';
import { afterNavigate, beforeNavigate } from '$app/navigation'; import { afterNavigate, beforeNavigate } from '$app/navigation';
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte'; import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte'; import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';

View file

@ -1,5 +1,5 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import { serverApi, UserResponseDto } from '@api'; import { serverApi } from '@api';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ parent }) => { export const load: PageServerLoad = async ({ parent }) => {

View file

@ -1,6 +1,6 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { AlbumResponseDto, serverApi } from '@api'; import { serverApi } from '@api';
export const load: PageServerLoad = async ({ parent }) => { export const load: PageServerLoad = async ({ parent }) => {
try { try {

View file

@ -6,8 +6,6 @@
import type { PageData } from './$types'; import type { PageData } from './$types';
import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader'; import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader';
import { onMount } from 'svelte';
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
import { import {
assetInteractionStore, assetInteractionStore,
isMultiSelectStoreState, isMultiSelectStoreState,

View file

@ -1,6 +1,6 @@
import preprocess from 'svelte-preprocess'; import preprocess from 'svelte-preprocess';
import adapter from '@sveltejs/adapter-node'; import adapter from '@sveltejs/adapter-node';
import path from 'path';
/** @type {import('@sveltejs/kit').Config} */ /** @type {import('@sveltejs/kit').Config} */
const config = { const config = {
preprocess: preprocess(), preprocess: preprocess(),