mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 96e3f3f495 |
@@ -1,406 +0,0 @@
|
|||||||
name: Visual Review
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [labeled, synchronize]
|
|
||||||
|
|
||||||
permissions: {}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
visual-diff:
|
|
||||||
name: Visual Diff Screenshots
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
(github.event.action == 'labeled' && github.event.label.name == 'visual-review') ||
|
|
||||||
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'visual-review'))
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: ./e2e
|
|
||||||
steps:
|
|
||||||
- id: token
|
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@05e16407c0a5492138bb38139c9d9bf067b40886 # create-workflow-token-action-v1.0.1
|
|
||||||
with:
|
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
|
||||||
|
|
||||||
- name: Checkout PR branch
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
token: ${{ steps.token.outputs.token }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Determine changed web files
|
|
||||||
id: changed-files
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const files = [];
|
|
||||||
const perPage = 100;
|
|
||||||
let page = 1;
|
|
||||||
while (true) {
|
|
||||||
const { data } = await github.rest.pulls.listFiles({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
pull_number: context.issue.number,
|
|
||||||
per_page: perPage,
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
files.push(...data);
|
|
||||||
if (data.length < perPage) break;
|
|
||||||
page++;
|
|
||||||
}
|
|
||||||
|
|
||||||
const webPrefixes = ['web/', 'i18n/', 'open-api/typescript-sdk/'];
|
|
||||||
const webFiles = files
|
|
||||||
.map(f => f.filename)
|
|
||||||
.filter(f => webPrefixes.some(p => f.startsWith(p)));
|
|
||||||
|
|
||||||
console.log(`Total PR files: ${files.length}`);
|
|
||||||
console.log(`Web-related files: ${webFiles.length}`);
|
|
||||||
for (const f of webFiles) {
|
|
||||||
console.log(` ${f}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
core.setOutput('files', webFiles.join('\n'));
|
|
||||||
core.setOutput('has_changes', webFiles.length > 0 ? 'true' : 'false');
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true'
|
|
||||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true'
|
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
|
||||||
with:
|
|
||||||
node-version-file: './e2e/.nvmrc'
|
|
||||||
cache: 'pnpm'
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
|
|
||||||
- name: Install e2e dependencies
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true'
|
|
||||||
run: pnpm install --frozen-lockfile
|
|
||||||
|
|
||||||
- name: Install Playwright
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true'
|
|
||||||
run: pnpm exec playwright install chromium --only-shell
|
|
||||||
|
|
||||||
- name: Analyze affected routes
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true'
|
|
||||||
id: routes
|
|
||||||
env:
|
|
||||||
CHANGED_FILES: ${{ steps.changed-files.outputs.files }}
|
|
||||||
run: |
|
|
||||||
echo "Changed files:"
|
|
||||||
echo "$CHANGED_FILES"
|
|
||||||
echo "---"
|
|
||||||
|
|
||||||
ROUTES=$(echo "$CHANGED_FILES" | xargs pnpm exec tsx src/screenshots/analyze-deps.ts 2>&1 | tee /dev/stderr | grep "^ /" | sed 's/^ //' || true)
|
|
||||||
|
|
||||||
echo "routes<<EOF" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "$ROUTES" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "EOF" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
if [ -z "$ROUTES" ]; then
|
|
||||||
echo "has_routes=false" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "has_routes=true" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
# Build the scenario filter JSON array
|
|
||||||
SCENARIO_NAMES=$(pnpm exec tsx -e "
|
|
||||||
import { getScenariosForRoutes } from './src/screenshots/page-map.ts';
|
|
||||||
const routes = process.argv.slice(1);
|
|
||||||
const scenarios = getScenariosForRoutes(routes);
|
|
||||||
console.log(JSON.stringify(scenarios.map(s => s.name)));
|
|
||||||
" $ROUTES)
|
|
||||||
echo "scenarios=$SCENARIO_NAMES" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Scenarios: $SCENARIO_NAMES"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Post initial comment
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true' && steps.routes.outputs.has_routes == 'true'
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
env:
|
|
||||||
AFFECTED_ROUTES: ${{ steps.routes.outputs.routes }}
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const routes = process.env.AFFECTED_ROUTES || '';
|
|
||||||
const body = `## Visual Review\n\nGenerating screenshots for affected pages...\n\nAffected routes:\n\`\`\`\n${routes}\n\`\`\``;
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
});
|
|
||||||
const existing = comments.data.find(c => c.body && c.body.includes('## Visual Review'));
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: existing.id,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
# === Screenshot PR version ===
|
|
||||||
- name: Build SDK (PR)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
|
|
||||||
- name: Build web (PR)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
run: pnpm install --frozen-lockfile && pnpm build
|
|
||||||
working-directory: ./web
|
|
||||||
|
|
||||||
- name: Take screenshots (PR)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
env:
|
|
||||||
PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS: '1'
|
|
||||||
SCREENSHOT_OUTPUT_DIR: ${{ github.workspace }}/screenshots/pr
|
|
||||||
SCREENSHOT_SCENARIOS: ${{ steps.routes.outputs.scenarios }}
|
|
||||||
SCREENSHOT_BASE_URL: http://127.0.0.1:4173
|
|
||||||
run: |
|
|
||||||
# Start the preview server in background
|
|
||||||
cd ../web && pnpm preview --port 4173 --host 127.0.0.1 &
|
|
||||||
SERVER_PID=$!
|
|
||||||
|
|
||||||
# Wait for server to be ready
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if curl -s http://127.0.0.1:4173 > /dev/null 2>&1; then
|
|
||||||
echo "Server ready after ${i}s"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Run screenshot tests
|
|
||||||
pnpm exec playwright test --config playwright.screenshot.config.ts || true
|
|
||||||
|
|
||||||
# Stop the preview server and all children (pnpm spawns vite as child)
|
|
||||||
kill $SERVER_PID 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
# Ensure port is fully released — kill any lingering vite process
|
|
||||||
fuser -k 4173/tcp 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# === Screenshot base version ===
|
|
||||||
# Disable pnpm's verifyDepsBeforeRun for all base steps since the base
|
|
||||||
# checkout changes package.json files, making them mismatch the lockfile.
|
|
||||||
- name: Checkout base web directory
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
env:
|
|
||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
||||||
run: |
|
|
||||||
# Restore web directory from base branch
|
|
||||||
git checkout "$BASE_SHA" -- web/ open-api/typescript-sdk/ i18n/ || true
|
|
||||||
# Clear SvelteKit build cache to avoid stale artifacts from the PR build
|
|
||||||
rm -rf web/.svelte-kit web/build
|
|
||||||
working-directory: .
|
|
||||||
|
|
||||||
- name: Build SDK (base)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
id: base-sdk
|
|
||||||
env:
|
|
||||||
PNPM_VERIFY_DEPS_BEFORE_RUN: 'false'
|
|
||||||
run: pnpm build
|
|
||||||
working-directory: ./open-api/typescript-sdk
|
|
||||||
|
|
||||||
- name: Build web (base)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true' && steps.base-sdk.outcome == 'success'
|
|
||||||
continue-on-error: true
|
|
||||||
id: base-web
|
|
||||||
env:
|
|
||||||
PNPM_VERIFY_DEPS_BEFORE_RUN: 'false'
|
|
||||||
run: pnpm build
|
|
||||||
working-directory: ./web
|
|
||||||
|
|
||||||
- name: Take screenshots (base)
|
|
||||||
if: steps.routes.outputs.has_routes == 'true' && steps.base-web.outcome == 'success'
|
|
||||||
env:
|
|
||||||
PW_EXPERIMENTAL_SERVICE_WORKER_NETWORK_EVENTS: '1'
|
|
||||||
SCREENSHOT_OUTPUT_DIR: ${{ github.workspace }}/screenshots/base
|
|
||||||
SCREENSHOT_SCENARIOS: ${{ steps.routes.outputs.scenarios }}
|
|
||||||
SCREENSHOT_BASE_URL: http://127.0.0.1:4173
|
|
||||||
PNPM_VERIFY_DEPS_BEFORE_RUN: 'false'
|
|
||||||
run: |
|
|
||||||
# Kill any process still on port 4173 from the PR step
|
|
||||||
fuser -k 4173/tcp 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
# Start the preview server in background
|
|
||||||
cd ../web && pnpm preview --port 4173 --host 127.0.0.1 &
|
|
||||||
SERVER_PID=$!
|
|
||||||
|
|
||||||
# Wait for server to be ready
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if curl -s http://127.0.0.1:4173 > /dev/null 2>&1; then
|
|
||||||
echo "Server ready after ${i}s"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Run screenshot tests
|
|
||||||
pnpm exec playwright test --config playwright.screenshot.config.ts || true
|
|
||||||
|
|
||||||
# Stop the preview server
|
|
||||||
kill $SERVER_PID 2>/dev/null || true
|
|
||||||
fuser -k 4173/tcp 2>/dev/null || true
|
|
||||||
|
|
||||||
- name: Restore PR source
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
env:
|
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
run: |
|
|
||||||
git checkout "$HEAD_SHA" -- web/ open-api/typescript-sdk/ i18n/ || true
|
|
||||||
working-directory: .
|
|
||||||
|
|
||||||
# === Compare and report ===
|
|
||||||
- name: Compare screenshots
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
env:
|
|
||||||
WORKSPACE_DIR: ${{ github.workspace }}
|
|
||||||
run: |
|
|
||||||
# Ensure directories exist even if base screenshots were skipped
|
|
||||||
mkdir -p "$WORKSPACE_DIR/screenshots/base" "$WORKSPACE_DIR/screenshots/pr" "$WORKSPACE_DIR/screenshots/diff"
|
|
||||||
pnpm exec tsx src/screenshots/compare.ts \
|
|
||||||
"$WORKSPACE_DIR/screenshots/base" \
|
|
||||||
"$WORKSPACE_DIR/screenshots/pr" \
|
|
||||||
"$WORKSPACE_DIR/screenshots/diff"
|
|
||||||
|
|
||||||
- name: Upload screenshot artifacts
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
name: visual-review-screenshots
|
|
||||||
path: screenshots/
|
|
||||||
retention-days: 14
|
|
||||||
|
|
||||||
- name: Upload HTML report
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
id: html-report
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
path: screenshots/diff/visual-review.html
|
|
||||||
archive: false
|
|
||||||
retention-days: 14
|
|
||||||
|
|
||||||
- name: Post comparison results
|
|
||||||
if: steps.routes.outputs.has_routes == 'true'
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
env:
|
|
||||||
REPORT_URL: ${{ steps.html-report.outputs.artifact-url }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const reportPath = path.join(process.env.GITHUB_WORKSPACE, 'screenshots', 'diff', 'report.md');
|
|
||||||
|
|
||||||
let body;
|
|
||||||
try {
|
|
||||||
body = fs.readFileSync(reportPath, 'utf8');
|
|
||||||
} catch {
|
|
||||||
body = '## Visual Review\n\nScreenshot comparison failed. Check the workflow artifacts for details.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append links to the HTML report artifact and workflow run
|
|
||||||
const reportUrl = process.env.REPORT_URL;
|
|
||||||
const runUrl = process.env.RUN_URL;
|
|
||||||
body += '\n---\n';
|
|
||||||
if (reportUrl) {
|
|
||||||
body += `[View full visual comparison](${reportUrl}) | `;
|
|
||||||
}
|
|
||||||
body += `[Download all screenshots](${runUrl}#artifacts)\n`;
|
|
||||||
|
|
||||||
// Find and update existing comment or create new one
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
});
|
|
||||||
|
|
||||||
const botComment = comments.data.find(c =>
|
|
||||||
c.body && c.body.includes('## Visual Review')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (botComment) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
comment_id: botComment.id,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: No web changes
|
|
||||||
if: steps.changed-files.outputs.has_changes != 'true'
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const body = '## Visual Review\n\nNo web-related file changes detected in this PR. Visual review not needed.';
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
});
|
|
||||||
const existing = comments.data.find(c => c.body && c.body.includes('## Visual Review'));
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner, repo: context.repo.repo,
|
|
||||||
comment_id: existing.id, body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner, repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number, body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- name: No affected routes
|
|
||||||
if: steps.changed-files.outputs.has_changes == 'true' && steps.routes.outputs.has_routes != 'true'
|
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
|
||||||
with:
|
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
|
||||||
script: |
|
|
||||||
const body = '## Visual Review\n\nChanged files don\'t affect any pages with screenshot scenarios configured.\nTo add coverage, define new scenarios in `e2e/src/screenshots/page-map.ts`.';
|
|
||||||
const comments = await github.rest.issues.listComments({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
});
|
|
||||||
const existing = comments.data.find(c => c.body && c.body.includes('## Visual Review'));
|
|
||||||
if (existing) {
|
|
||||||
await github.rest.issues.updateComment({
|
|
||||||
owner: context.repo.owner, repo: context.repo.repo,
|
|
||||||
comment_id: existing.id, body,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
owner: context.repo.owner, repo: context.repo.repo,
|
|
||||||
issue_number: context.issue.number, body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -24,7 +24,6 @@ open-api/typescript-sdk/build
|
|||||||
mobile/android/fastlane/report.xml
|
mobile/android/fastlane/report.xml
|
||||||
mobile/ios/fastlane/report.xml
|
mobile/ios/fastlane/report.xml
|
||||||
|
|
||||||
screenshots-output
|
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
.devcontainer/library
|
.devcontainer/library
|
||||||
|
|||||||
+1
-5
@@ -18,10 +18,7 @@
|
|||||||
"format:fix": "prettier --write .",
|
"format:fix": "prettier --write .",
|
||||||
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
"lint": "eslint \"src/**/*.ts\" --max-warnings 0",
|
||||||
"lint:fix": "pnpm run lint --fix",
|
"lint:fix": "pnpm run lint --fix",
|
||||||
"check": "tsc --noEmit",
|
"check": "tsc --noEmit"
|
||||||
"screenshots": "pnpm exec playwright test --config playwright.screenshot.config.ts",
|
|
||||||
"screenshots:compare": "pnpm exec tsx src/screenshots/compare.ts",
|
|
||||||
"screenshots:analyze": "pnpm exec tsx src/screenshots/analyze-deps.ts"
|
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@@ -54,7 +51,6 @@
|
|||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"tsx": "^4.21.0",
|
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"typescript-eslint": "^8.28.0",
|
"typescript-eslint": "^8.28.0",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import { defineConfig, devices } from '@playwright/test';
|
|
||||||
|
|
||||||
const baseUrl = process.env.SCREENSHOT_BASE_URL ?? 'http://127.0.0.1:4173';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: './src/screenshots',
|
|
||||||
testMatch: /run-scenarios\.ts/,
|
|
||||||
fullyParallel: false,
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
retries: 0,
|
|
||||||
reporter: 'list',
|
|
||||||
use: {
|
|
||||||
baseURL: baseUrl,
|
|
||||||
screenshot: 'off',
|
|
||||||
trace: 'off',
|
|
||||||
},
|
|
||||||
workers: 1,
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'screenshots',
|
|
||||||
use: {
|
|
||||||
...devices['Desktop Chrome'],
|
|
||||||
viewport: { width: 1920, height: 1080 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
/**
|
|
||||||
* Reverse dependency analyzer for the Immich web app.
|
|
||||||
*
|
|
||||||
* Given a list of changed files, traces upward through the import graph
|
|
||||||
* to find which +page.svelte routes are affected, then maps those to URL paths.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
||||||
import { dirname, join, relative, resolve } from 'node:path';
|
|
||||||
|
|
||||||
const WEB_SRC = resolve(import.meta.dirname, '../../../web/src');
|
|
||||||
const LIB_ALIAS = resolve(WEB_SRC, 'lib');
|
|
||||||
|
|
||||||
/** Collect all .svelte, .ts, .js files under web/src/ */
|
|
||||||
function collectFiles(dir: string): string[] {
|
|
||||||
const results: string[] = [];
|
|
||||||
for (const entry of readdirSync(dir)) {
|
|
||||||
const full = join(dir, entry);
|
|
||||||
const stat = statSync(full);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
if (entry === 'node_modules' || entry === '.svelte-kit') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.push(...collectFiles(full));
|
|
||||||
} else if (/\.(svelte|ts|js)$/.test(entry)) {
|
|
||||||
results.push(full);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Extract import specifiers from a file's source text. */
|
|
||||||
function extractImports(source: string): string[] {
|
|
||||||
const specifiers: string[] = [];
|
|
||||||
|
|
||||||
// Match: import ... from '...' / import '...' / export ... from '...'
|
|
||||||
const importRegex = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
||||||
let match;
|
|
||||||
while ((match = importRegex.exec(source)) !== null) {
|
|
||||||
specifiers.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match dynamic imports: import('...')
|
|
||||||
const dynamicRegex = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
||||||
while ((match = dynamicRegex.exec(source)) !== null) {
|
|
||||||
specifiers.push(match[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return specifiers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Resolve an import specifier to an absolute file path (or null if external). */
|
|
||||||
function resolveImport(specifier: string, fromFile: string, allFiles: Set<string>): string | null {
|
|
||||||
// Handle $lib alias
|
|
||||||
let resolved: string;
|
|
||||||
if (specifier.startsWith('$lib/') || specifier === '$lib') {
|
|
||||||
resolved = specifier.replace('$lib', LIB_ALIAS);
|
|
||||||
} else if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
||||||
resolved = resolve(dirname(fromFile), specifier);
|
|
||||||
} else {
|
|
||||||
// External package import — not relevant
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try exact match, then common extensions
|
|
||||||
const extensions = ['', '.ts', '.js', '.svelte', '/index.ts', '/index.js', '/index.svelte'];
|
|
||||||
for (const ext of extensions) {
|
|
||||||
const candidate = resolved + ext;
|
|
||||||
if (allFiles.has(candidate)) {
|
|
||||||
return candidate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Build the forward dependency graph: file → set of files it imports. */
|
|
||||||
function buildDependencyGraph(files: string[]): Map<string, Set<string>> {
|
|
||||||
const fileSet = new Set(files);
|
|
||||||
const graph = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const deps = new Set<string>();
|
|
||||||
graph.set(file, deps);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const source = readFileSync(file, 'utf8');
|
|
||||||
for (const specifier of extractImports(source)) {
|
|
||||||
const resolved = resolveImport(specifier, file, fileSet);
|
|
||||||
if (resolved) {
|
|
||||||
deps.add(resolved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Skip files that can't be read
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return graph;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Invert the dependency graph: file → set of files that import it. */
|
|
||||||
function buildReverseDependencyGraph(forwardGraph: Map<string, Set<string>>): Map<string, Set<string>> {
|
|
||||||
const reverse = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
for (const [file, deps] of forwardGraph) {
|
|
||||||
for (const dep of deps) {
|
|
||||||
let importers = reverse.get(dep);
|
|
||||||
if (!importers) {
|
|
||||||
importers = new Set();
|
|
||||||
reverse.set(dep, importers);
|
|
||||||
}
|
|
||||||
importers.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return reverse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** BFS from changed files upward through reverse deps to find +page.svelte files. */
|
|
||||||
function findAffectedPages(changedFiles: string[], reverseGraph: Map<string, Set<string>>): Set<string> {
|
|
||||||
const visited = new Set<string>();
|
|
||||||
const pages = new Set<string>();
|
|
||||||
const queue = [...changedFiles];
|
|
||||||
|
|
||||||
while (queue.length > 0) {
|
|
||||||
const file = queue.shift()!;
|
|
||||||
if (visited.has(file)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
visited.add(file);
|
|
||||||
|
|
||||||
if (file.endsWith('+page.svelte') || file.endsWith('+layout.svelte')) {
|
|
||||||
pages.add(file);
|
|
||||||
// If it's a layout, keep tracing upward because the layout itself
|
|
||||||
// isn't a page — but the pages under it are affected.
|
|
||||||
// If it's a +page.svelte, we still want to continue in case
|
|
||||||
// this page is imported by others.
|
|
||||||
}
|
|
||||||
|
|
||||||
const importers = reverseGraph.get(file);
|
|
||||||
if (importers) {
|
|
||||||
for (const importer of importers) {
|
|
||||||
if (!visited.has(importer)) {
|
|
||||||
queue.push(importer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For +layout.svelte hits, also find all +page.svelte under the same directory tree
|
|
||||||
const layoutDirs: string[] = [];
|
|
||||||
for (const page of pages) {
|
|
||||||
if (page.endsWith('+layout.svelte')) {
|
|
||||||
layoutDirs.push(dirname(page));
|
|
||||||
pages.delete(page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layoutDirs.length > 0) {
|
|
||||||
for (const file of reverseGraph.keys()) {
|
|
||||||
if (file.endsWith('+page.svelte')) {
|
|
||||||
for (const layoutDir of layoutDirs) {
|
|
||||||
if (file.startsWith(layoutDir)) {
|
|
||||||
pages.add(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Also check the forward graph keys for page files under layout dirs
|
|
||||||
for (const layoutDir of layoutDirs) {
|
|
||||||
const allFiles = collectFiles(layoutDir);
|
|
||||||
for (const f of allFiles) {
|
|
||||||
if (f.endsWith('+page.svelte')) {
|
|
||||||
pages.add(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pages;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert a +page.svelte file path to its URL route. */
|
|
||||||
export function pageFileToRoute(pageFile: string): string {
|
|
||||||
const routesDir = resolve(WEB_SRC, 'routes');
|
|
||||||
let rel = relative(routesDir, dirname(pageFile));
|
|
||||||
|
|
||||||
// Remove SvelteKit group markers: (user), (list), etc.
|
|
||||||
rel = rel.replaceAll(/\([^)]+\)\/?/g, '');
|
|
||||||
|
|
||||||
// Remove parameter segments: [albumId=id], [[photos=photos]], [[assetId=id]]
|
|
||||||
rel = rel.replaceAll(/\[\[?[^\]]+\]\]?\/?/g, '');
|
|
||||||
|
|
||||||
// Clean up trailing slashes and normalize
|
|
||||||
rel = rel.replaceAll(/\/+/g, '/').replace(/\/$/, '');
|
|
||||||
|
|
||||||
return '/' + rel;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnalysisResult {
|
|
||||||
affectedPages: string[];
|
|
||||||
affectedRoutes: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Main entry: analyze which routes are affected by the given changed files. */
|
|
||||||
export function analyzeAffectedRoutes(changedFiles: string[]): AnalysisResult {
|
|
||||||
// Resolve changed files to absolute paths relative to web/src
|
|
||||||
const webRoot = resolve(WEB_SRC, '..');
|
|
||||||
const resolvedChanged = changedFiles
|
|
||||||
.filter((f) => f.startsWith('web/'))
|
|
||||||
.map((f) => resolve(webRoot, '..', f))
|
|
||||||
.filter((f) => statSync(f, { throwIfNoEntry: false })?.isFile());
|
|
||||||
|
|
||||||
if (resolvedChanged.length === 0) {
|
|
||||||
return { affectedPages: [], affectedRoutes: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
const allFiles = collectFiles(WEB_SRC);
|
|
||||||
const forwardGraph = buildDependencyGraph(allFiles);
|
|
||||||
const reverseGraph = buildReverseDependencyGraph(forwardGraph);
|
|
||||||
|
|
||||||
const pages = findAffectedPages(resolvedChanged, reverseGraph);
|
|
||||||
|
|
||||||
const affectedPages = [...pages].toSorted();
|
|
||||||
const affectedRoutes = [...new Set(affectedPages.map((f) => pageFileToRoute(f)))].toSorted();
|
|
||||||
|
|
||||||
return { affectedPages, affectedRoutes };
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI usage: node --import tsx analyze-deps.ts file1 file2 ...
|
|
||||||
if (process.argv[1]?.endsWith('analyze-deps.ts') || process.argv[1]?.endsWith('analyze-deps.js')) {
|
|
||||||
const files = process.argv.slice(2);
|
|
||||||
if (files.length === 0) {
|
|
||||||
console.log('Usage: analyze-deps.ts <changed-file1> <changed-file2> ...');
|
|
||||||
console.log('Files should be relative to the repo root (e.g. web/src/lib/components/Button.svelte)');
|
|
||||||
throw new Error('No files provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = analyzeAffectedRoutes(files);
|
|
||||||
console.log('Affected pages:');
|
|
||||||
for (const page of result.affectedPages) {
|
|
||||||
console.log(` ${page}`);
|
|
||||||
}
|
|
||||||
console.log('\nAffected routes:');
|
|
||||||
for (const route of result.affectedRoutes) {
|
|
||||||
console.log(` ${route}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,335 +0,0 @@
|
|||||||
/**
|
|
||||||
* Pixel-level comparison of base vs PR screenshots.
|
|
||||||
*
|
|
||||||
* Uses pixelmatch to generate diff images and calculate change percentages.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* npx tsx e2e/src/screenshots/compare.ts <base-dir> <pr-dir> <output-dir>
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
||||||
import { basename, join, resolve } from 'node:path';
|
|
||||||
import { PNG } from 'pngjs';
|
|
||||||
|
|
||||||
// pixelmatch is a lightweight dependency — use a simple inline implementation
|
|
||||||
// based on the approach from the pixelmatch library to avoid adding a new dependency.
|
|
||||||
// The e2e package already has pngjs.
|
|
||||||
|
|
||||||
function pixelMatch(img1Data: Uint8Array, img2Data: Uint8Array, diffData: Uint8Array): number {
|
|
||||||
let diffCount = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < img1Data.length; i += 4) {
|
|
||||||
const r1 = img1Data[i];
|
|
||||||
const g1 = img1Data[i + 1];
|
|
||||||
const b1 = img1Data[i + 2];
|
|
||||||
|
|
||||||
const r2 = img2Data[i];
|
|
||||||
const g2 = img2Data[i + 1];
|
|
||||||
const b2 = img2Data[i + 2];
|
|
||||||
|
|
||||||
const dr = Math.abs(r1 - r2);
|
|
||||||
const dg = Math.abs(g1 - g2);
|
|
||||||
const db = Math.abs(b1 - b2);
|
|
||||||
|
|
||||||
// Threshold: if any channel differs by more than 25, mark as different
|
|
||||||
const isDiff = dr > 25 || dg > 25 || db > 25;
|
|
||||||
|
|
||||||
if (isDiff) {
|
|
||||||
// Red highlight for diff pixels
|
|
||||||
diffData[i] = 255;
|
|
||||||
diffData[i + 1] = 0;
|
|
||||||
diffData[i + 2] = 0;
|
|
||||||
diffData[i + 3] = 255;
|
|
||||||
diffCount++;
|
|
||||||
} else {
|
|
||||||
// Dimmed original for unchanged pixels
|
|
||||||
const gray = Math.round(0.299 * r1 + 0.587 * g1 + 0.114 * b1);
|
|
||||||
diffData[i] = gray;
|
|
||||||
diffData[i + 1] = gray;
|
|
||||||
diffData[i + 2] = gray;
|
|
||||||
diffData[i + 3] = 128;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diffCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComparisonResult {
|
|
||||||
name: string;
|
|
||||||
baseExists: boolean;
|
|
||||||
prExists: boolean;
|
|
||||||
diffPixels: number;
|
|
||||||
totalPixels: number;
|
|
||||||
changePercent: number;
|
|
||||||
diffImagePath: string | null;
|
|
||||||
baseImagePath: string | null;
|
|
||||||
prImagePath: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compareScreenshots(baseDir: string, prDir: string, outputDir: string): ComparisonResult[] {
|
|
||||||
mkdirSync(outputDir, { recursive: true });
|
|
||||||
|
|
||||||
// Collect all screenshot names from both directories
|
|
||||||
const baseFiles = existsSync(baseDir)
|
|
||||||
? new Set(readdirSync(baseDir).filter((f) => f.endsWith('.png')))
|
|
||||||
: new Set<string>();
|
|
||||||
const prFiles = existsSync(prDir) ? new Set(readdirSync(prDir).filter((f) => f.endsWith('.png'))) : new Set<string>();
|
|
||||||
|
|
||||||
const allNames = new Set([...baseFiles, ...prFiles]);
|
|
||||||
const results: ComparisonResult[] = [];
|
|
||||||
|
|
||||||
for (const fileName of [...allNames].toSorted()) {
|
|
||||||
const name = basename(fileName, '.png');
|
|
||||||
const basePath = join(baseDir, fileName);
|
|
||||||
const prPath = join(prDir, fileName);
|
|
||||||
const baseExists = baseFiles.has(fileName);
|
|
||||||
const prExists = prFiles.has(fileName);
|
|
||||||
|
|
||||||
if (!baseExists || !prExists) {
|
|
||||||
// New or removed page
|
|
||||||
results.push({
|
|
||||||
name,
|
|
||||||
baseExists,
|
|
||||||
prExists,
|
|
||||||
diffPixels: -1,
|
|
||||||
totalPixels: -1,
|
|
||||||
changePercent: 100,
|
|
||||||
diffImagePath: null,
|
|
||||||
baseImagePath: baseExists ? basePath : null,
|
|
||||||
prImagePath: prExists ? prPath : null,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load both PNGs
|
|
||||||
const basePng = PNG.sync.read(readFileSync(basePath));
|
|
||||||
const prPng = PNG.sync.read(readFileSync(prPath));
|
|
||||||
|
|
||||||
// Handle size mismatches by comparing the overlapping region
|
|
||||||
const width = Math.max(basePng.width, prPng.width);
|
|
||||||
const height = Math.max(basePng.height, prPng.height);
|
|
||||||
|
|
||||||
// Resize images to the same dimensions (pad with transparent)
|
|
||||||
const normalizedBase = normalizeImage(basePng, width, height);
|
|
||||||
const normalizedPr = normalizeImage(prPng, width, height);
|
|
||||||
|
|
||||||
const diffPng = new PNG({ width, height });
|
|
||||||
const totalPixels = width * height;
|
|
||||||
const diffPixels = pixelMatch(normalizedBase, normalizedPr, diffPng.data as unknown as Uint8Array);
|
|
||||||
|
|
||||||
const diffImagePath = join(outputDir, `${name}-diff.png`);
|
|
||||||
writeFileSync(diffImagePath, PNG.sync.write(diffPng));
|
|
||||||
|
|
||||||
results.push({
|
|
||||||
name,
|
|
||||||
baseExists,
|
|
||||||
prExists,
|
|
||||||
diffPixels,
|
|
||||||
totalPixels,
|
|
||||||
changePercent: totalPixels > 0 ? (diffPixels / totalPixels) * 100 : 0,
|
|
||||||
diffImagePath,
|
|
||||||
baseImagePath: basePath,
|
|
||||||
prImagePath: prPath,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeImage(png: PNG, targetWidth: number, targetHeight: number): Uint8Array {
|
|
||||||
if (png.width === targetWidth && png.height === targetHeight) {
|
|
||||||
return png.data as unknown as Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = new Uint8Array(targetWidth * targetHeight * 4);
|
|
||||||
for (let y = 0; y < targetHeight; y++) {
|
|
||||||
for (let x = 0; x < targetWidth; x++) {
|
|
||||||
const targetIdx = (y * targetWidth + x) * 4;
|
|
||||||
if (x < png.width && y < png.height) {
|
|
||||||
const sourceIdx = (y * png.width + x) * 4;
|
|
||||||
data[targetIdx] = png.data[sourceIdx];
|
|
||||||
data[targetIdx + 1] = png.data[sourceIdx + 1];
|
|
||||||
data[targetIdx + 2] = png.data[sourceIdx + 2];
|
|
||||||
data[targetIdx + 3] = png.data[sourceIdx + 3];
|
|
||||||
} else {
|
|
||||||
// Transparent padding
|
|
||||||
data[targetIdx + 3] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generate a text-only markdown summary for the PR comment. */
|
|
||||||
export function generateMarkdownReport(results: ComparisonResult[]): string {
|
|
||||||
const changed = results.filter((r) => r.changePercent > 0.1);
|
|
||||||
const unchanged = results.filter((r) => r.changePercent <= 0.1);
|
|
||||||
|
|
||||||
if (changed.length === 0) {
|
|
||||||
return '## Visual Review\n\nNo visual changes detected in the affected pages.';
|
|
||||||
}
|
|
||||||
|
|
||||||
let md = '## Visual Review\n\n';
|
|
||||||
md += `Found **${changed.length}** page(s) with visual changes`;
|
|
||||||
if (unchanged.length > 0) {
|
|
||||||
md += ` (${unchanged.length} unchanged)`;
|
|
||||||
}
|
|
||||||
md += '.\n\n';
|
|
||||||
|
|
||||||
md += '| Page | Status | Change |\n';
|
|
||||||
md += '|------|--------|--------|\n';
|
|
||||||
|
|
||||||
for (const result of changed) {
|
|
||||||
if (result.baseExists && result.prExists) {
|
|
||||||
md += `| ${result.name} | Changed | ${result.changePercent.toFixed(1)}% |\n`;
|
|
||||||
} else if (result.prExists) {
|
|
||||||
md += `| ${result.name} | New | - |\n`;
|
|
||||||
} else {
|
|
||||||
md += `| ${result.name} | Removed | - |\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
md += '\n';
|
|
||||||
|
|
||||||
if (unchanged.length > 0) {
|
|
||||||
md += '<details>\n<summary>Unchanged pages</summary>\n\n';
|
|
||||||
for (const result of unchanged) {
|
|
||||||
md += `- ${result.name}\n`;
|
|
||||||
}
|
|
||||||
md += '\n</details>\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return md;
|
|
||||||
}
|
|
||||||
|
|
||||||
function imgTag(filePath: string | null, alt: string): string {
|
|
||||||
if (!filePath || !existsSync(filePath)) {
|
|
||||||
return `<div class="no-image">${alt} not available</div>`;
|
|
||||||
}
|
|
||||||
const data = readFileSync(filePath);
|
|
||||||
return `<img src="data:image/png;base64,${data.toString('base64')}" alt="${alt}" loading="lazy" />`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generate an HTML report with embedded base64 images for the artifact. */
|
|
||||||
export function generateHtmlReport(results: ComparisonResult[]): string {
|
|
||||||
const changed = results.filter((r) => r.changePercent > 0.1);
|
|
||||||
const unchanged = results.filter((r) => r.changePercent <= 0.1);
|
|
||||||
|
|
||||||
let html = `<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Visual Review</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
|
||||||
background: #0d1117; color: #e6edf3; padding: 32px; line-height: 1.5; }
|
|
||||||
.container { max-width: 1800px; margin: 0 auto; }
|
|
||||||
h1 { font-size: 24px; border-bottom: 1px solid #30363d; padding-bottom: 12px; margin-bottom: 24px; }
|
|
||||||
.summary { color: #8b949e; margin-bottom: 32px; font-size: 16px; }
|
|
||||||
.scenario { margin-bottom: 40px; border: 1px solid #30363d; border-radius: 8px; overflow: hidden; }
|
|
||||||
.scenario-header { background: #161b22; padding: 12px 16px; display: flex; align-items: center; gap: 12px; }
|
|
||||||
.scenario-header h2 { font-size: 16px; font-weight: 600; }
|
|
||||||
.badge { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; }
|
|
||||||
.badge-changed { background: #da363380; color: #f85149; }
|
|
||||||
.badge-new { background: #1f6feb80; color: #58a6ff; }
|
|
||||||
.badge-removed { background: #6e767e80; color: #8b949e; }
|
|
||||||
.grid { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1px; background: #30363d; }
|
|
||||||
.grid-cell { background: #0d1117; }
|
|
||||||
.grid-label { text-align: center; padding: 8px; font-size: 13px; color: #8b949e; font-weight: 600;
|
|
||||||
background: #161b22; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
||||||
.grid-cell img { width: 100%; display: block; }
|
|
||||||
.no-image { padding: 40px; text-align: center; color: #484f58; font-style: italic; }
|
|
||||||
.unchanged-section { margin-top: 32px; color: #8b949e; }
|
|
||||||
.unchanged-section summary { cursor: pointer; font-size: 14px; }
|
|
||||||
.unchanged-section ul { margin-top: 8px; padding-left: 24px; }
|
|
||||||
.unchanged-section li { font-size: 14px; margin: 4px 0; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>Visual Review</h1>
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (changed.length === 0) {
|
|
||||||
html += '<p class="summary">No visual changes detected in the affected pages.</p>';
|
|
||||||
} else {
|
|
||||||
html += `<p class="summary">Found <strong>${changed.length}</strong> page(s) with visual changes`;
|
|
||||||
if (unchanged.length > 0) {
|
|
||||||
html += ` (${unchanged.length} unchanged)`;
|
|
||||||
}
|
|
||||||
html += '.</p>\n';
|
|
||||||
|
|
||||||
for (const result of changed) {
|
|
||||||
html += '<div class="scenario">\n<div class="scenario-header">\n';
|
|
||||||
html += `<h2>${result.name}</h2>\n`;
|
|
||||||
|
|
||||||
if (!result.baseExists) {
|
|
||||||
html += '<span class="badge badge-new">New</span>\n';
|
|
||||||
html += '</div>\n';
|
|
||||||
html += `<div style="padding: 16px;">${imgTag(result.prImagePath, 'PR')}</div>\n`;
|
|
||||||
html += '</div>\n';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.prExists) {
|
|
||||||
html += '<span class="badge badge-removed">Removed</span>\n';
|
|
||||||
html += '</div>\n</div>\n';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `<span class="badge badge-changed">${result.changePercent.toFixed(1)}% changed</span>\n`;
|
|
||||||
html += '</div>\n';
|
|
||||||
html += '<div class="grid">\n';
|
|
||||||
html += `<div class="grid-cell"><div class="grid-label">Base</div>${imgTag(result.baseImagePath, 'Base')}</div>\n`;
|
|
||||||
html += `<div class="grid-cell"><div class="grid-label">PR</div>${imgTag(result.prImagePath, 'PR')}</div>\n`;
|
|
||||||
html += `<div class="grid-cell"><div class="grid-label">Diff</div>${imgTag(result.diffImagePath, 'Diff')}</div>\n`;
|
|
||||||
html += '</div>\n</div>\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unchanged.length > 0) {
|
|
||||||
html += '<div class="unchanged-section">\n<details>\n<summary>Unchanged pages</summary>\n<ul>\n';
|
|
||||||
for (const result of unchanged) {
|
|
||||||
html += `<li>${result.name}</li>\n`;
|
|
||||||
}
|
|
||||||
html += '</ul>\n</details>\n</div>\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>\n</body>\n</html>';
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CLI usage
|
|
||||||
if (process.argv[1]?.endsWith('compare.ts') || process.argv[1]?.endsWith('compare.js')) {
|
|
||||||
const [baseDir, prDir, outputDir] = process.argv.slice(2);
|
|
||||||
|
|
||||||
if (!baseDir || !prDir || !outputDir) {
|
|
||||||
throw new Error('Usage: compare.ts <base-dir> <pr-dir> <output-dir>');
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedOutputDir = resolve(outputDir);
|
|
||||||
const results = compareScreenshots(resolve(baseDir), resolve(prDir), resolvedOutputDir);
|
|
||||||
|
|
||||||
console.log('\nComparison Results:');
|
|
||||||
console.log('==================');
|
|
||||||
for (const r of results) {
|
|
||||||
const status = r.changePercent > 0.1 ? 'CHANGED' : 'unchanged';
|
|
||||||
console.log(` ${r.name}: ${status} (${r.changePercent.toFixed(1)}%)`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const report = generateMarkdownReport(results);
|
|
||||||
const reportPath = join(resolvedOutputDir, 'report.md');
|
|
||||||
writeFileSync(reportPath, report);
|
|
||||||
console.log(`\nMarkdown report written to: ${reportPath}`);
|
|
||||||
|
|
||||||
const htmlReport = generateHtmlReport(results);
|
|
||||||
const htmlPath = join(resolvedOutputDir, 'visual-review.html');
|
|
||||||
writeFileSync(htmlPath, htmlReport);
|
|
||||||
console.log(`HTML report written to: ${htmlPath}`);
|
|
||||||
|
|
||||||
const jsonPath = join(resolvedOutputDir, 'results.json');
|
|
||||||
writeFileSync(jsonPath, JSON.stringify(results, null, 2));
|
|
||||||
console.log(`Results JSON written to: ${jsonPath}`);
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
/**
|
|
||||||
* Maps URL routes to screenshot scenario keys.
|
|
||||||
*
|
|
||||||
* Routes discovered by the dependency analyzer are matched against this map
|
|
||||||
* to determine which screenshot scenarios to run. Routes not in this map
|
|
||||||
* are skipped (they don't have a scenario defined yet).
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface ScenarioDefinition {
|
|
||||||
/** The URL path to navigate to */
|
|
||||||
url: string;
|
|
||||||
/** Human-readable name for the screenshot file */
|
|
||||||
name: string;
|
|
||||||
/** Which mock networks this scenario needs */
|
|
||||||
mocks: ('base' | 'timeline' | 'memory')[];
|
|
||||||
/** Optional: selector to wait for before screenshotting */
|
|
||||||
waitForSelector?: string;
|
|
||||||
/** Optional: time to wait after page load (ms) for animations to settle */
|
|
||||||
settleTime?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map from route paths (as output by analyze-deps) to scenario definitions.
|
|
||||||
* A single route might map to multiple scenarios (e.g., different states).
|
|
||||||
*/
|
|
||||||
export const PAGE_SCENARIOS: Record<string, ScenarioDefinition[]> = {
|
|
||||||
'/photos': [
|
|
||||||
{
|
|
||||||
url: '/photos',
|
|
||||||
name: 'photos-timeline',
|
|
||||||
mocks: ['base', 'timeline'],
|
|
||||||
waitForSelector: '[data-thumbnail-focus-container]',
|
|
||||||
settleTime: 500,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/albums': [
|
|
||||||
{
|
|
||||||
url: '/albums',
|
|
||||||
name: 'albums-list',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/explore': [
|
|
||||||
{
|
|
||||||
url: '/explore',
|
|
||||||
name: 'explore',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/favorites': [
|
|
||||||
{
|
|
||||||
url: '/favorites',
|
|
||||||
name: 'favorites',
|
|
||||||
mocks: ['base', 'timeline'],
|
|
||||||
waitForSelector: '#asset-grid',
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/archive': [
|
|
||||||
{
|
|
||||||
url: '/archive',
|
|
||||||
name: 'archive',
|
|
||||||
mocks: ['base', 'timeline'],
|
|
||||||
waitForSelector: '#asset-grid',
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/trash': [
|
|
||||||
{
|
|
||||||
url: '/trash',
|
|
||||||
name: 'trash',
|
|
||||||
mocks: ['base', 'timeline'],
|
|
||||||
waitForSelector: '#asset-grid',
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/people': [
|
|
||||||
{
|
|
||||||
url: '/people',
|
|
||||||
name: 'people',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/sharing': [
|
|
||||||
{
|
|
||||||
url: '/sharing',
|
|
||||||
name: 'sharing',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/search': [
|
|
||||||
{
|
|
||||||
url: '/search',
|
|
||||||
name: 'search',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/memory': [
|
|
||||||
{
|
|
||||||
url: '/memory',
|
|
||||||
name: 'memory',
|
|
||||||
mocks: ['base', 'memory'],
|
|
||||||
settleTime: 500,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/user-settings': [
|
|
||||||
{
|
|
||||||
url: '/user-settings',
|
|
||||||
name: 'user-settings',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/map': [
|
|
||||||
{
|
|
||||||
url: '/map',
|
|
||||||
name: 'map',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 500,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/admin': [
|
|
||||||
{
|
|
||||||
url: '/admin',
|
|
||||||
name: 'admin-dashboard',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/admin/system-settings': [
|
|
||||||
{
|
|
||||||
url: '/admin/system-settings',
|
|
||||||
name: 'admin-system-settings',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/admin/users': [
|
|
||||||
{
|
|
||||||
url: '/admin/users',
|
|
||||||
name: 'admin-users',
|
|
||||||
mocks: ['base'],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/auth/login': [
|
|
||||||
{
|
|
||||||
url: '/auth/login',
|
|
||||||
name: 'login',
|
|
||||||
mocks: [],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'/': [
|
|
||||||
{
|
|
||||||
url: '/',
|
|
||||||
name: 'landing',
|
|
||||||
mocks: [],
|
|
||||||
settleTime: 300,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Given a list of routes from the analyzer, return the matching scenarios. */
|
|
||||||
export function getScenariosForRoutes(routes: string[]): ScenarioDefinition[] {
|
|
||||||
const scenarios: ScenarioDefinition[] = [];
|
|
||||||
const seen = new Set<string>();
|
|
||||||
|
|
||||||
for (const route of routes) {
|
|
||||||
const defs = PAGE_SCENARIOS[route];
|
|
||||||
if (defs) {
|
|
||||||
for (const def of defs) {
|
|
||||||
if (!seen.has(def.name)) {
|
|
||||||
seen.add(def.name);
|
|
||||||
scenarios.push(def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scenarios;
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
* Playwright script to capture screenshots for visual diff scenarios.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* npx playwright test --config e2e/playwright.screenshot.config.ts
|
|
||||||
*
|
|
||||||
* Environment variables:
|
|
||||||
* SCREENSHOT_SCENARIOS - JSON array of scenario names to run (from page-map.ts)
|
|
||||||
* If not set, runs all scenarios.
|
|
||||||
* SCREENSHOT_OUTPUT_DIR - Directory to save screenshots to. Defaults to e2e/screenshots-output.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { faker } from '@faker-js/faker';
|
|
||||||
import type { MemoryResponseDto } from '@immich/sdk';
|
|
||||||
import { test } from '@playwright/test';
|
|
||||||
import { mkdirSync } from 'node:fs';
|
|
||||||
import { resolve } from 'node:path';
|
|
||||||
import { generateMemoriesFromTimeline } from 'src/ui/generators/memory';
|
|
||||||
import {
|
|
||||||
createDefaultTimelineConfig,
|
|
||||||
generateTimelineData,
|
|
||||||
type TimelineAssetConfig,
|
|
||||||
type TimelineData,
|
|
||||||
} from 'src/ui/generators/timeline';
|
|
||||||
import { setupBaseMockApiRoutes } from 'src/ui/mock-network/base-network';
|
|
||||||
import { setupMemoryMockApiRoutes } from 'src/ui/mock-network/memory-network';
|
|
||||||
import { setupTimelineMockApiRoutes, TimelineTestContext } from 'src/ui/mock-network/timeline-network';
|
|
||||||
import { PAGE_SCENARIOS, type ScenarioDefinition } from './page-map';
|
|
||||||
|
|
||||||
const OUTPUT_DIR = process.env.SCREENSHOT_OUTPUT_DIR || resolve(import.meta.dirname, '../../../screenshots-output');
|
|
||||||
const SCENARIO_FILTER: string[] | null = process.env.SCREENSHOT_SCENARIOS
|
|
||||||
? JSON.parse(process.env.SCREENSHOT_SCENARIOS)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// Collect scenarios to run
|
|
||||||
const allScenarios: ScenarioDefinition[] = [];
|
|
||||||
for (const defs of Object.values(PAGE_SCENARIOS)) {
|
|
||||||
for (const def of defs) {
|
|
||||||
if (!SCENARIO_FILTER || SCENARIO_FILTER.includes(def.name)) {
|
|
||||||
allScenarios.push(def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a fixed seed so screenshots are deterministic across runs
|
|
||||||
faker.seed(42);
|
|
||||||
|
|
||||||
let adminUserId: string;
|
|
||||||
let timelineData: TimelineData;
|
|
||||||
let timelineAssets: TimelineAssetConfig[];
|
|
||||||
let memories: MemoryResponseDto[];
|
|
||||||
|
|
||||||
test.beforeAll(async () => {
|
|
||||||
adminUserId = faker.string.uuid();
|
|
||||||
timelineData = generateTimelineData({ ...createDefaultTimelineConfig(), ownerId: adminUserId });
|
|
||||||
|
|
||||||
timelineAssets = [];
|
|
||||||
for (const timeBucket of timelineData.buckets.values()) {
|
|
||||||
timelineAssets.push(...timeBucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
memories = generateMemoriesFromTimeline(
|
|
||||||
timelineAssets,
|
|
||||||
adminUserId,
|
|
||||||
[
|
|
||||||
{ year: 2024, assetCount: 3 },
|
|
||||||
{ year: 2023, assetCount: 2 },
|
|
||||||
],
|
|
||||||
42,
|
|
||||||
);
|
|
||||||
|
|
||||||
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const scenario of allScenarios) {
|
|
||||||
test(`Screenshot: ${scenario.name}`, async ({ context, page }) => {
|
|
||||||
// Set up mocks based on scenario requirements
|
|
||||||
if (scenario.mocks.includes('base')) {
|
|
||||||
await setupBaseMockApiRoutes(context, adminUserId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scenario.mocks.includes('timeline')) {
|
|
||||||
const testContext = new TimelineTestContext();
|
|
||||||
testContext.adminId = adminUserId;
|
|
||||||
await setupTimelineMockApiRoutes(
|
|
||||||
context,
|
|
||||||
timelineData,
|
|
||||||
{
|
|
||||||
albumAdditions: [],
|
|
||||||
assetDeletions: [],
|
|
||||||
assetArchivals: [],
|
|
||||||
assetFavorites: [],
|
|
||||||
},
|
|
||||||
testContext,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scenario.mocks.includes('memory')) {
|
|
||||||
await setupMemoryMockApiRoutes(context, memories, {
|
|
||||||
memoryDeletions: [],
|
|
||||||
assetRemovals: new Map(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Navigate to the page. Use networkidle so SvelteKit hydrates and API
|
|
||||||
// calls complete, but fall back to domcontentloaded if it times out
|
|
||||||
// (e.g. a persistent connection the catch-all mock didn't cover).
|
|
||||||
try {
|
|
||||||
await page.goto(scenario.url, { waitUntil: 'networkidle', timeout: 15_000 });
|
|
||||||
} catch {
|
|
||||||
console.warn(`networkidle timed out for ${scenario.name}, falling back to current state`);
|
|
||||||
// Page has already navigated, just continue with what we have
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for specific selector if specified
|
|
||||||
if (scenario.waitForSelector) {
|
|
||||||
try {
|
|
||||||
await page.waitForSelector(scenario.waitForSelector, { timeout: 15_000 });
|
|
||||||
} catch {
|
|
||||||
console.warn(`Selector ${scenario.waitForSelector} not found for ${scenario.name}, continuing...`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for loading spinners to disappear
|
|
||||||
await page
|
|
||||||
.waitForFunction(() => document.querySelectorAll('[data-testid="loading-spinner"]').length === 0, {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
// Wait for animations/transitions to settle
|
|
||||||
await page.waitForTimeout(scenario.settleTime ?? 500);
|
|
||||||
|
|
||||||
// Take the screenshot
|
|
||||||
await page.screenshot({
|
|
||||||
path: resolve(OUTPUT_DIR, `${scenario.name}.png`),
|
|
||||||
fullPage: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -10,27 +10,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
|||||||
path: '/',
|
path: '/',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Block socket.io connections — these are persistent WebSocket connections
|
|
||||||
// that prevent networkidle from resolving since there's no real server.
|
|
||||||
await context.route('**/api/socket.io**', async (route) => {
|
|
||||||
return route.abort('connectionrefused');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Catch-all for any /api/ endpoint not explicitly mocked below.
|
|
||||||
// Registered FIRST so specific routes (registered after) take priority
|
|
||||||
// (Playwright checks routes in reverse registration order).
|
|
||||||
// Without this, unmocked API calls hit the static preview server which
|
|
||||||
// either hangs or returns HTML, preventing networkidle and causing timeouts.
|
|
||||||
await context.route('**/api/**', async (route) => {
|
|
||||||
const method = route.request().method();
|
|
||||||
return route.fulfill({
|
|
||||||
status: 200,
|
|
||||||
contentType: 'application/json',
|
|
||||||
json: method === 'GET' ? [] : {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await context.route('**/api/users/me', async (route) => {
|
await context.route('**/api/users/me', async (route) => {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
status: 200,
|
status: 200,
|
||||||
|
|||||||
-1
File diff suppressed because one or more lines are too long
@@ -11,10 +11,6 @@ enum AssetType {
|
|||||||
|
|
||||||
enum AssetState { local, remote, merged }
|
enum AssetState { local, remote, merged }
|
||||||
|
|
||||||
// do not change!
|
|
||||||
// keep in sync with PlatformAssetPlaybackStyle
|
|
||||||
enum AssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping }
|
|
||||||
|
|
||||||
sealed class BaseAsset {
|
sealed class BaseAsset {
|
||||||
final String name;
|
final String name;
|
||||||
final String? checksum;
|
final String? checksum;
|
||||||
@@ -47,14 +43,6 @@ sealed class BaseAsset {
|
|||||||
|
|
||||||
bool get isMotionPhoto => livePhotoVideoId != null;
|
bool get isMotionPhoto => livePhotoVideoId != null;
|
||||||
|
|
||||||
AssetPlaybackStyle get playbackStyle {
|
|
||||||
if (isVideo) return AssetPlaybackStyle.video;
|
|
||||||
if (isMotionPhoto) return AssetPlaybackStyle.livePhoto;
|
|
||||||
if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated;
|
|
||||||
if (isImage) return AssetPlaybackStyle.image;
|
|
||||||
return AssetPlaybackStyle.unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration get duration {
|
Duration get duration {
|
||||||
final durationInSeconds = this.durationInSeconds;
|
final durationInSeconds = this.durationInSeconds;
|
||||||
if (durationInSeconds != null) {
|
if (durationInSeconds != null) {
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
final String? remoteAssetId;
|
final String? remoteAssetId;
|
||||||
final String? cloudId;
|
final String? cloudId;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
@override
|
|
||||||
final AssetPlaybackStyle playbackStyle;
|
|
||||||
|
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final double? latitude;
|
final double? latitude;
|
||||||
@@ -27,7 +25,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
super.isFavorite = false,
|
super.isFavorite = false,
|
||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.orientation = 0,
|
this.orientation = 0,
|
||||||
required this.playbackStyle,
|
|
||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
@@ -59,7 +56,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
playbackStyle: $playbackStyle,
|
|
||||||
remoteId: ${remoteId ?? "<NA>"},
|
remoteId: ${remoteId ?? "<NA>"},
|
||||||
cloudId: ${cloudId ?? "<NA>"},
|
cloudId: ${cloudId ?? "<NA>"},
|
||||||
checksum: ${checksum ?? "<NA>"},
|
checksum: ${checksum ?? "<NA>"},
|
||||||
@@ -80,7 +76,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
id == other.id &&
|
id == other.id &&
|
||||||
cloudId == other.cloudId &&
|
cloudId == other.cloudId &&
|
||||||
orientation == other.orientation &&
|
orientation == other.orientation &&
|
||||||
playbackStyle == other.playbackStyle &&
|
|
||||||
adjustmentTime == other.adjustmentTime &&
|
adjustmentTime == other.adjustmentTime &&
|
||||||
latitude == other.latitude &&
|
latitude == other.latitude &&
|
||||||
longitude == other.longitude;
|
longitude == other.longitude;
|
||||||
@@ -92,7 +87,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
id.hashCode ^
|
id.hashCode ^
|
||||||
remoteId.hashCode ^
|
remoteId.hashCode ^
|
||||||
orientation.hashCode ^
|
orientation.hashCode ^
|
||||||
playbackStyle.hashCode ^
|
|
||||||
adjustmentTime.hashCode ^
|
adjustmentTime.hashCode ^
|
||||||
latitude.hashCode ^
|
latitude.hashCode ^
|
||||||
longitude.hashCode;
|
longitude.hashCode;
|
||||||
@@ -111,7 +105,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
int? durationInSeconds,
|
int? durationInSeconds,
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
AssetPlaybackStyle? playbackStyle,
|
|
||||||
DateTime? adjustmentTime,
|
DateTime? adjustmentTime,
|
||||||
double? latitude,
|
double? latitude,
|
||||||
double? longitude,
|
double? longitude,
|
||||||
@@ -131,7 +124,6 @@ class LocalAsset extends BaseAsset {
|
|||||||
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
durationInSeconds: durationInSeconds ?? this.durationInSeconds,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
|
|||||||
@@ -435,19 +435,9 @@ extension PlatformToLocalAsset on PlatformAsset {
|
|||||||
durationInSeconds: durationInSeconds,
|
durationInSeconds: durationInSeconds,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: _toPlaybackStyle(playbackStyle),
|
|
||||||
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) {
|
|
||||||
PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown,
|
|
||||||
PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image,
|
|
||||||
PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video,
|
|
||||||
PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated,
|
|
||||||
PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto,
|
|
||||||
PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
|||||||
|
|
||||||
RealColumn get longitude => real().nullable()();
|
RealColumn get longitude => real().nullable()();
|
||||||
|
|
||||||
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -45,7 +43,6 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
|||||||
width: width,
|
width: width,
|
||||||
remoteId: remoteId,
|
remoteId: remoteId,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.LocalAssetEntityCompanion Function({
|
i1.LocalAssetEntityCompanion Function({
|
||||||
@@ -44,7 +43,6 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$LocalAssetEntityTableFilterComposer
|
class $$LocalAssetEntityTableFilterComposer
|
||||||
@@ -131,16 +129,6 @@ class $$LocalAssetEntityTableFilterComposer
|
|||||||
column: $table.longitude,
|
column: $table.longitude,
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnWithTypeConverterFilters<
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
int
|
|
||||||
>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableOrderingComposer
|
class $$LocalAssetEntityTableOrderingComposer
|
||||||
@@ -226,11 +214,6 @@ class $$LocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.longitude,
|
column: $table.longitude,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableAnnotationComposer
|
class $$LocalAssetEntityTableAnnotationComposer
|
||||||
@@ -294,12 +277,6 @@ class $$LocalAssetEntityTableAnnotationComposer
|
|||||||
|
|
||||||
i0.GeneratedColumn<double> get longitude =>
|
i0.GeneratedColumn<double> get longitude =>
|
||||||
$composableBuilder(column: $table.longitude, builder: (column) => column);
|
$composableBuilder(column: $table.longitude, builder: (column) => column);
|
||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$LocalAssetEntityTableTableManager
|
class $$LocalAssetEntityTableTableManager
|
||||||
@@ -357,8 +334,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.LocalAssetEntityCompanion(
|
}) => i1.LocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -375,7 +350,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -394,8 +368,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.LocalAssetEntityCompanion.insert(
|
}) => i1.LocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -412,7 +384,6 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -625,19 +596,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
playbackStyle =
|
|
||||||
i0.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.int,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultValue: const i4.Constant(0),
|
|
||||||
).withConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle,
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -654,7 +612,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -836,12 +793,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
i0.DriftSqlType.double,
|
i0.DriftSqlType.double,
|
||||||
data['${effectivePrefix}longitude'],
|
data['${effectivePrefix}longitude'],
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromSql(
|
|
||||||
attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.int,
|
|
||||||
data['${effectivePrefix}playback_style'],
|
|
||||||
)!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -852,10 +803,6 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
|
|
||||||
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
||||||
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
|
|
||||||
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i2.AssetPlaybackStyle.values,
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
bool get withoutRowId => true;
|
bool get withoutRowId => true;
|
||||||
@override
|
@override
|
||||||
@@ -879,7 +826,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final double? latitude;
|
final double? latitude;
|
||||||
final double? longitude;
|
final double? longitude;
|
||||||
final i2.AssetPlaybackStyle playbackStyle;
|
|
||||||
const LocalAssetEntityData({
|
const LocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -896,7 +842,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -936,11 +881,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
if (!nullToAbsent || longitude != null) {
|
if (!nullToAbsent || longitude != null) {
|
||||||
map['longitude'] = i0.Variable<double>(longitude);
|
map['longitude'] = i0.Variable<double>(longitude);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(playbackStyle),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -967,9 +907,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
||||||
latitude: serializer.fromJson<double?>(json['latitude']),
|
latitude: serializer.fromJson<double?>(json['latitude']),
|
||||||
longitude: serializer.fromJson<double?>(json['longitude']),
|
longitude: serializer.fromJson<double?>(json['longitude']),
|
||||||
playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromJson(
|
|
||||||
serializer.fromJson<int>(json['playbackStyle']),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -993,9 +930,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
||||||
'latitude': serializer.toJson<double?>(latitude),
|
'latitude': serializer.toJson<double?>(latitude),
|
||||||
'longitude': serializer.toJson<double?>(longitude),
|
'longitude': serializer.toJson<double?>(longitude),
|
||||||
'playbackStyle': serializer.toJson<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toJson(playbackStyle),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1015,7 +949,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
i2.AssetPlaybackStyle? playbackStyle,
|
|
||||||
}) => i1.LocalAssetEntityData(
|
}) => i1.LocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -1036,7 +969,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
latitude: latitude.present ? latitude.value : this.latitude,
|
latitude: latitude.present ? latitude.value : this.latitude,
|
||||||
longitude: longitude.present ? longitude.value : this.longitude,
|
longitude: longitude.present ? longitude.value : this.longitude,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) {
|
||||||
return LocalAssetEntityData(
|
return LocalAssetEntityData(
|
||||||
@@ -1063,9 +995,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
latitude: data.latitude.present ? data.latitude.value : this.latitude,
|
latitude: data.latitude.present ? data.latitude.value : this.latitude,
|
||||||
longitude: data.longitude.present ? data.longitude.value : this.longitude,
|
longitude: data.longitude.present ? data.longitude.value : this.longitude,
|
||||||
playbackStyle: data.playbackStyle.present
|
|
||||||
? data.playbackStyle.value
|
|
||||||
: this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,8 +1015,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
..write('iCloudId: $iCloudId, ')
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude, ')
|
..write('longitude: $longitude')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -1109,7 +1037,6 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
playbackStyle,
|
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -1129,8 +1056,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
other.iCloudId == this.iCloudId &&
|
other.iCloudId == this.iCloudId &&
|
||||||
other.adjustmentTime == this.adjustmentTime &&
|
other.adjustmentTime == this.adjustmentTime &&
|
||||||
other.latitude == this.latitude &&
|
other.latitude == this.latitude &&
|
||||||
other.longitude == this.longitude &&
|
other.longitude == this.longitude);
|
||||||
other.playbackStyle == this.playbackStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalAssetEntityCompanion
|
class LocalAssetEntityCompanion
|
||||||
@@ -1150,7 +1076,6 @@ class LocalAssetEntityCompanion
|
|||||||
final i0.Value<DateTime?> adjustmentTime;
|
final i0.Value<DateTime?> adjustmentTime;
|
||||||
final i0.Value<double?> latitude;
|
final i0.Value<double?> latitude;
|
||||||
final i0.Value<double?> longitude;
|
final i0.Value<double?> longitude;
|
||||||
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
|
|
||||||
const LocalAssetEntityCompanion({
|
const LocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -1167,7 +1092,6 @@ class LocalAssetEntityCompanion
|
|||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
});
|
});
|
||||||
LocalAssetEntityCompanion.insert({
|
LocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -1185,7 +1109,6 @@ class LocalAssetEntityCompanion
|
|||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id);
|
id = i0.Value(id);
|
||||||
@@ -1205,7 +1128,6 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Expression<DateTime>? adjustmentTime,
|
i0.Expression<DateTime>? adjustmentTime,
|
||||||
i0.Expression<double>? latitude,
|
i0.Expression<double>? latitude,
|
||||||
i0.Expression<double>? longitude,
|
i0.Expression<double>? longitude,
|
||||||
i0.Expression<int>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -1223,7 +1145,6 @@ class LocalAssetEntityCompanion
|
|||||||
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
||||||
if (latitude != null) 'latitude': latitude,
|
if (latitude != null) 'latitude': latitude,
|
||||||
if (longitude != null) 'longitude': longitude,
|
if (longitude != null) 'longitude': longitude,
|
||||||
if (playbackStyle != null) 'playback_style': playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1243,7 +1164,6 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Value<DateTime?>? adjustmentTime,
|
i0.Value<DateTime?>? adjustmentTime,
|
||||||
i0.Value<double?>? latitude,
|
i0.Value<double?>? latitude,
|
||||||
i0.Value<double?>? longitude,
|
i0.Value<double?>? longitude,
|
||||||
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i1.LocalAssetEntityCompanion(
|
return i1.LocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1261,7 +1181,6 @@ class LocalAssetEntityCompanion
|
|||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,13 +1234,6 @@ class LocalAssetEntityCompanion
|
|||||||
if (longitude.present) {
|
if (longitude.present) {
|
||||||
map['longitude'] = i0.Variable<double>(longitude.value);
|
map['longitude'] = i0.Variable<double>(longitude.value);
|
||||||
}
|
}
|
||||||
if (playbackStyle.present) {
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,8 +1254,7 @@ class LocalAssetEntityCompanion
|
|||||||
..write('iCloudId: $iCloudId, ')
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude, ')
|
..write('longitude: $longitude')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ SELECT
|
|||||||
NULL as latitude,
|
NULL as latitude,
|
||||||
NULL as longitude,
|
NULL as longitude,
|
||||||
NULL as adjustmentTime,
|
NULL as adjustmentTime,
|
||||||
rae.is_edited,
|
rae.is_edited
|
||||||
0 as playback_style
|
|
||||||
FROM
|
FROM
|
||||||
remote_asset_entity rae
|
remote_asset_entity rae
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
@@ -64,8 +63,7 @@ SELECT
|
|||||||
lae.latitude,
|
lae.latitude,
|
||||||
lae.longitude,
|
lae.longitude,
|
||||||
lae.adjustment_time,
|
lae.adjustment_time,
|
||||||
0 as is_edited,
|
0 as is_edited
|
||||||
lae.playback_style
|
|
||||||
FROM
|
FROM
|
||||||
local_asset_entity lae
|
local_asset_entity lae
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
|
|||||||
+1
-4
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
);
|
);
|
||||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||||
variables: [
|
variables: [
|
||||||
for (var $ in userIds) i0.Variable<String>($),
|
for (var $ in userIds) i0.Variable<String>($),
|
||||||
...generatedlimit.introducedVariables,
|
...generatedlimit.introducedVariables,
|
||||||
@@ -67,7 +67,6 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
longitude: row.readNullable<double>('longitude'),
|
longitude: row.readNullable<double>('longitude'),
|
||||||
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
||||||
isEdited: row.read<bool>('is_edited'),
|
isEdited: row.read<bool>('is_edited'),
|
||||||
playbackStyle: row.read<int>('playback_style'),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -140,7 +139,6 @@ class MergedAssetResult {
|
|||||||
final double? longitude;
|
final double? longitude;
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final bool isEdited;
|
final bool isEdited;
|
||||||
final int playbackStyle;
|
|
||||||
MergedAssetResult({
|
MergedAssetResult({
|
||||||
this.remoteId,
|
this.remoteId,
|
||||||
this.localId,
|
this.localId,
|
||||||
@@ -163,7 +161,6 @@ class MergedAssetResult {
|
|||||||
this.longitude,
|
this.longitude,
|
||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
required this.isEdited,
|
required this.isEdited,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntity
|
|||||||
|
|
||||||
IntColumn get source => intEnum<TrashOrigin>()();
|
IntColumn get source => intEnum<TrashOrigin>()();
|
||||||
|
|
||||||
IntColumn get playbackStyle => intEnum<AssetPlaybackStyle>().withDefault(const Constant(0))();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id, albumId};
|
Set<Column> get primaryKey => {id, albumId};
|
||||||
}
|
}
|
||||||
@@ -47,7 +45,6 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
|
|||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.TrashedLocalAssetEntityCompanion Function({
|
i1.TrashedLocalAssetEntityCompanion Function({
|
||||||
@@ -40,7 +39,6 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
i0.Value<i3.TrashOrigin> source,
|
i0.Value<i3.TrashOrigin> source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableFilterComposer
|
class $$TrashedLocalAssetEntityTableFilterComposer
|
||||||
@@ -119,16 +117,6 @@ class $$TrashedLocalAssetEntityTableFilterComposer
|
|||||||
column: $table.source,
|
column: $table.source,
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnWithTypeConverterFilters<
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
i2.AssetPlaybackStyle,
|
|
||||||
int
|
|
||||||
>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableOrderingComposer
|
class $$TrashedLocalAssetEntityTableOrderingComposer
|
||||||
@@ -205,11 +193,6 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.source,
|
column: $table.source,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
i0.ColumnOrderings<int> get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
||||||
@@ -266,12 +249,6 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
|
|||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
|
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
|
||||||
$composableBuilder(column: $table.source, builder: (column) => column);
|
$composableBuilder(column: $table.source, builder: (column) => column);
|
||||||
|
|
||||||
i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
get playbackStyle => $composableBuilder(
|
|
||||||
column: $table.playbackStyle,
|
|
||||||
builder: (column) => column,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableTableManager
|
class $$TrashedLocalAssetEntityTableTableManager
|
||||||
@@ -333,8 +310,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
|
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion(
|
}) => i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -349,7 +324,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
source: source,
|
source: source,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -366,8 +340,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
i0.Value<i2.AssetPlaybackStyle> playbackStyle =
|
|
||||||
const i0.Value.absent(),
|
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -382,7 +354,6 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
source: source,
|
source: source,
|
||||||
playbackStyle: playbackStyle,
|
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -579,19 +550,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource,
|
i1.$TrashedLocalAssetEntityTable.$convertersource,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
late final i0.GeneratedColumnWithTypeConverter<i2.AssetPlaybackStyle, int>
|
|
||||||
playbackStyle =
|
|
||||||
i0.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.int,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultValue: const i4.Constant(0),
|
|
||||||
).withConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle,
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -606,7 +564,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
source,
|
source,
|
||||||
playbackStyle,
|
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -763,13 +720,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
data['${effectivePrefix}source'],
|
data['${effectivePrefix}source'],
|
||||||
)!,
|
)!,
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
|
|
||||||
.fromSql(
|
|
||||||
attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.int,
|
|
||||||
data['${effectivePrefix}playback_style'],
|
|
||||||
)!,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,10 +732,6 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
|
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
|
||||||
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
|
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
|
||||||
static i0.JsonTypeConverter2<i2.AssetPlaybackStyle, int, int>
|
|
||||||
$converterplaybackStyle = const i0.EnumIndexConverter<i2.AssetPlaybackStyle>(
|
|
||||||
i2.AssetPlaybackStyle.values,
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
bool get withoutRowId => true;
|
bool get withoutRowId => true;
|
||||||
@override
|
@override
|
||||||
@@ -807,7 +753,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
final i3.TrashOrigin source;
|
final i3.TrashOrigin source;
|
||||||
final i2.AssetPlaybackStyle playbackStyle;
|
|
||||||
const TrashedLocalAssetEntityData({
|
const TrashedLocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -822,7 +767,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
required this.source,
|
required this.source,
|
||||||
required this.playbackStyle,
|
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -856,13 +800,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,8 +826,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
|
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
|
||||||
serializer.fromJson<int>(json['source']),
|
serializer.fromJson<int>(json['source']),
|
||||||
),
|
),
|
||||||
playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle
|
|
||||||
.fromJson(serializer.fromJson<int>(json['playbackStyle'])),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -914,11 +849,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
'source': serializer.toJson<int>(
|
'source': serializer.toJson<int>(
|
||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
|
||||||
),
|
),
|
||||||
'playbackStyle': serializer.toJson<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toJson(
|
|
||||||
playbackStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -936,7 +866,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
i3.TrashOrigin? source,
|
i3.TrashOrigin? source,
|
||||||
i2.AssetPlaybackStyle? playbackStyle,
|
|
||||||
}) => i1.TrashedLocalAssetEntityData(
|
}) => i1.TrashedLocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -953,7 +882,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
source: source ?? this.source,
|
source: source ?? this.source,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
TrashedLocalAssetEntityData copyWithCompanion(
|
TrashedLocalAssetEntityData copyWithCompanion(
|
||||||
i1.TrashedLocalAssetEntityCompanion data,
|
i1.TrashedLocalAssetEntityCompanion data,
|
||||||
@@ -978,9 +906,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
? data.orientation.value
|
? data.orientation.value
|
||||||
: this.orientation,
|
: this.orientation,
|
||||||
source: data.source.present ? data.source.value : this.source,
|
source: data.source.present ? data.source.value : this.source,
|
||||||
playbackStyle: data.playbackStyle.present
|
|
||||||
? data.playbackStyle.value
|
|
||||||
: this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -999,8 +924,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
..write('source: $source, ')
|
..write('source: $source')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -1020,7 +944,6 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
source,
|
source,
|
||||||
playbackStyle,
|
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -1038,8 +961,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
other.checksum == this.checksum &&
|
other.checksum == this.checksum &&
|
||||||
other.isFavorite == this.isFavorite &&
|
other.isFavorite == this.isFavorite &&
|
||||||
other.orientation == this.orientation &&
|
other.orientation == this.orientation &&
|
||||||
other.source == this.source &&
|
other.source == this.source);
|
||||||
other.playbackStyle == this.playbackStyle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrashedLocalAssetEntityCompanion
|
class TrashedLocalAssetEntityCompanion
|
||||||
@@ -1057,7 +979,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
final i0.Value<bool> isFavorite;
|
final i0.Value<bool> isFavorite;
|
||||||
final i0.Value<int> orientation;
|
final i0.Value<int> orientation;
|
||||||
final i0.Value<i3.TrashOrigin> source;
|
final i0.Value<i3.TrashOrigin> source;
|
||||||
final i0.Value<i2.AssetPlaybackStyle> playbackStyle;
|
|
||||||
const TrashedLocalAssetEntityCompanion({
|
const TrashedLocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -1072,7 +993,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
this.source = const i0.Value.absent(),
|
this.source = const i0.Value.absent(),
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
});
|
});
|
||||||
TrashedLocalAssetEntityCompanion.insert({
|
TrashedLocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -1088,7 +1008,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
required i3.TrashOrigin source,
|
required i3.TrashOrigin source,
|
||||||
this.playbackStyle = const i0.Value.absent(),
|
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id),
|
id = i0.Value(id),
|
||||||
@@ -1108,7 +1027,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Expression<bool>? isFavorite,
|
i0.Expression<bool>? isFavorite,
|
||||||
i0.Expression<int>? orientation,
|
i0.Expression<int>? orientation,
|
||||||
i0.Expression<int>? source,
|
i0.Expression<int>? source,
|
||||||
i0.Expression<int>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -1124,7 +1042,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
if (orientation != null) 'orientation': orientation,
|
if (orientation != null) 'orientation': orientation,
|
||||||
if (source != null) 'source': source,
|
if (source != null) 'source': source,
|
||||||
if (playbackStyle != null) 'playback_style': playbackStyle,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1142,7 +1059,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Value<bool>? isFavorite,
|
i0.Value<bool>? isFavorite,
|
||||||
i0.Value<int>? orientation,
|
i0.Value<int>? orientation,
|
||||||
i0.Value<i3.TrashOrigin>? source,
|
i0.Value<i3.TrashOrigin>? source,
|
||||||
i0.Value<i2.AssetPlaybackStyle>? playbackStyle,
|
|
||||||
}) {
|
}) {
|
||||||
return i1.TrashedLocalAssetEntityCompanion(
|
return i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1158,7 +1074,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
source: source ?? this.source,
|
source: source ?? this.source,
|
||||||
playbackStyle: playbackStyle ?? this.playbackStyle,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1208,13 +1123,6 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (playbackStyle.present) {
|
|
||||||
map['playback_style'] = i0.Variable<int>(
|
|
||||||
i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql(
|
|
||||||
playbackStyle.value,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,8 +1141,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
..write('source: $source, ')
|
..write('source: $source')
|
||||||
..write('playbackStyle: $playbackStyle')
|
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 21;
|
int get schemaVersion => 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -230,10 +230,6 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible);
|
||||||
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt);
|
||||||
},
|
},
|
||||||
from20To21: (m, v21) async {
|
|
||||||
await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle);
|
|
||||||
await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8904,591 +8904,6 @@ i1.GeneratedColumn<bool> _column_102(String aliasedName) =>
|
|||||||
),
|
),
|
||||||
defaultValue: const CustomExpression('1'),
|
defaultValue: const CustomExpression('1'),
|
||||||
);
|
);
|
||||||
|
|
||||||
final class Schema21 extends i0.VersionedSchema {
|
|
||||||
Schema21({required super.database}) : super(version: 21);
|
|
||||||
@override
|
|
||||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
|
||||||
userEntity,
|
|
||||||
remoteAssetEntity,
|
|
||||||
stackEntity,
|
|
||||||
localAssetEntity,
|
|
||||||
remoteAlbumEntity,
|
|
||||||
localAlbumEntity,
|
|
||||||
localAlbumAssetEntity,
|
|
||||||
idxLocalAlbumAssetAlbumAsset,
|
|
||||||
idxRemoteAlbumOwnerId,
|
|
||||||
idxLocalAssetChecksum,
|
|
||||||
idxLocalAssetCloudId,
|
|
||||||
idxStackPrimaryAssetId,
|
|
||||||
idxRemoteAssetOwnerChecksum,
|
|
||||||
uQRemoteAssetsOwnerChecksum,
|
|
||||||
uQRemoteAssetsOwnerLibraryChecksum,
|
|
||||||
idxRemoteAssetChecksum,
|
|
||||||
idxRemoteAssetStackId,
|
|
||||||
idxRemoteAssetLocalDateTimeDay,
|
|
||||||
idxRemoteAssetLocalDateTimeMonth,
|
|
||||||
authUserEntity,
|
|
||||||
userMetadataEntity,
|
|
||||||
partnerEntity,
|
|
||||||
remoteExifEntity,
|
|
||||||
remoteAlbumAssetEntity,
|
|
||||||
remoteAlbumUserEntity,
|
|
||||||
remoteAssetCloudIdEntity,
|
|
||||||
memoryEntity,
|
|
||||||
memoryAssetEntity,
|
|
||||||
personEntity,
|
|
||||||
assetFaceEntity,
|
|
||||||
storeEntity,
|
|
||||||
trashedLocalAssetEntity,
|
|
||||||
idxPartnerSharedWithId,
|
|
||||||
idxLatLng,
|
|
||||||
idxRemoteAlbumAssetAlbumAsset,
|
|
||||||
idxRemoteAssetCloudId,
|
|
||||||
idxPersonOwnerId,
|
|
||||||
idxAssetFacePersonId,
|
|
||||||
idxAssetFaceAssetId,
|
|
||||||
idxTrashedLocalAssetChecksum,
|
|
||||||
idxTrashedLocalAssetAlbum,
|
|
||||||
];
|
|
||||||
late final Shape20 userEntity = Shape20(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_1,
|
|
||||||
_column_3,
|
|
||||||
_column_84,
|
|
||||||
_column_85,
|
|
||||||
_column_91,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape28 remoteAssetEntity = Shape28(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_1,
|
|
||||||
_column_8,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_10,
|
|
||||||
_column_11,
|
|
||||||
_column_12,
|
|
||||||
_column_0,
|
|
||||||
_column_13,
|
|
||||||
_column_14,
|
|
||||||
_column_15,
|
|
||||||
_column_16,
|
|
||||||
_column_17,
|
|
||||||
_column_18,
|
|
||||||
_column_19,
|
|
||||||
_column_20,
|
|
||||||
_column_21,
|
|
||||||
_column_86,
|
|
||||||
_column_101,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape3 stackEntity = Shape3(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'stack_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [_column_0, _column_9, _column_5, _column_15, _column_75],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape30 localAssetEntity = Shape30(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_1,
|
|
||||||
_column_8,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_10,
|
|
||||||
_column_11,
|
|
||||||
_column_12,
|
|
||||||
_column_0,
|
|
||||||
_column_22,
|
|
||||||
_column_14,
|
|
||||||
_column_23,
|
|
||||||
_column_98,
|
|
||||||
_column_96,
|
|
||||||
_column_46,
|
|
||||||
_column_47,
|
|
||||||
_column_103,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape9 remoteAlbumEntity = Shape9(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_1,
|
|
||||||
_column_56,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_15,
|
|
||||||
_column_57,
|
|
||||||
_column_58,
|
|
||||||
_column_59,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape19 localAlbumEntity = Shape19(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_album_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_1,
|
|
||||||
_column_5,
|
|
||||||
_column_31,
|
|
||||||
_column_32,
|
|
||||||
_column_90,
|
|
||||||
_column_33,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape22 localAlbumAssetEntity = Shape22(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_album_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
|
||||||
columns: [_column_34, _column_35, _column_33],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
|
||||||
'idx_local_album_asset_album_asset',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAlbumOwnerId = i1.Index(
|
|
||||||
'idx_remote_album_owner_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAssetChecksum = i1.Index(
|
|
||||||
'idx_local_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAssetCloudId = i1.Index(
|
|
||||||
'idx_local_asset_cloud_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
|
||||||
'idx_stack_primary_asset_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
|
||||||
'idx_remote_asset_owner_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
|
||||||
'UQ_remote_assets_owner_checksum',
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
|
||||||
);
|
|
||||||
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
|
||||||
'UQ_remote_assets_owner_library_checksum',
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
|
||||||
'idx_remote_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetStackId = i1.Index(
|
|
||||||
'idx_remote_asset_stack_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
|
|
||||||
'idx_remote_asset_local_date_time_day',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
|
|
||||||
'idx_remote_asset_local_date_time_month',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
|
|
||||||
);
|
|
||||||
late final Shape21 authUserEntity = Shape21(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'auth_user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_1,
|
|
||||||
_column_3,
|
|
||||||
_column_2,
|
|
||||||
_column_84,
|
|
||||||
_column_85,
|
|
||||||
_column_92,
|
|
||||||
_column_93,
|
|
||||||
_column_7,
|
|
||||||
_column_94,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape4 userMetadataEntity = Shape4(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'user_metadata_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
|
||||||
columns: [_column_25, _column_26, _column_27],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape5 partnerEntity = Shape5(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'partner_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
|
||||||
columns: [_column_28, _column_29, _column_30],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape8 remoteExifEntity = Shape8(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_exif_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_36,
|
|
||||||
_column_37,
|
|
||||||
_column_38,
|
|
||||||
_column_39,
|
|
||||||
_column_40,
|
|
||||||
_column_41,
|
|
||||||
_column_11,
|
|
||||||
_column_10,
|
|
||||||
_column_42,
|
|
||||||
_column_43,
|
|
||||||
_column_44,
|
|
||||||
_column_45,
|
|
||||||
_column_46,
|
|
||||||
_column_47,
|
|
||||||
_column_48,
|
|
||||||
_column_49,
|
|
||||||
_column_50,
|
|
||||||
_column_51,
|
|
||||||
_column_52,
|
|
||||||
_column_53,
|
|
||||||
_column_54,
|
|
||||||
_column_55,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
|
||||||
columns: [_column_36, _column_60],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape10 remoteAlbumUserEntity = Shape10(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
|
||||||
columns: [_column_60, _column_25, _column_61],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape27 remoteAssetCloudIdEntity = Shape27(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_asset_cloud_id_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_36,
|
|
||||||
_column_99,
|
|
||||||
_column_100,
|
|
||||||
_column_96,
|
|
||||||
_column_46,
|
|
||||||
_column_47,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape11 memoryEntity = Shape11(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'memory_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_18,
|
|
||||||
_column_15,
|
|
||||||
_column_8,
|
|
||||||
_column_62,
|
|
||||||
_column_63,
|
|
||||||
_column_64,
|
|
||||||
_column_65,
|
|
||||||
_column_66,
|
|
||||||
_column_67,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape12 memoryAssetEntity = Shape12(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'memory_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
|
||||||
columns: [_column_36, _column_68],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape14 personEntity = Shape14(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'person_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_15,
|
|
||||||
_column_1,
|
|
||||||
_column_69,
|
|
||||||
_column_71,
|
|
||||||
_column_72,
|
|
||||||
_column_73,
|
|
||||||
_column_74,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape29 assetFaceEntity = Shape29(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'asset_face_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_0,
|
|
||||||
_column_36,
|
|
||||||
_column_76,
|
|
||||||
_column_77,
|
|
||||||
_column_78,
|
|
||||||
_column_79,
|
|
||||||
_column_80,
|
|
||||||
_column_81,
|
|
||||||
_column_82,
|
|
||||||
_column_83,
|
|
||||||
_column_102,
|
|
||||||
_column_18,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape18 storeEntity = Shape18(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'store_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [_column_87, _column_88, _column_89],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape31 trashedLocalAssetEntity = Shape31(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'trashed_local_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_1,
|
|
||||||
_column_8,
|
|
||||||
_column_9,
|
|
||||||
_column_5,
|
|
||||||
_column_10,
|
|
||||||
_column_11,
|
|
||||||
_column_12,
|
|
||||||
_column_0,
|
|
||||||
_column_95,
|
|
||||||
_column_22,
|
|
||||||
_column_14,
|
|
||||||
_column_23,
|
|
||||||
_column_97,
|
|
||||||
_column_103,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
final i1.Index idxPartnerSharedWithId = i1.Index(
|
|
||||||
'idx_partner_shared_with_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLatLng = i1.Index(
|
|
||||||
'idx_lat_lng',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
|
||||||
'idx_remote_album_asset_album_asset',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
|
||||||
'idx_remote_asset_cloud_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxPersonOwnerId = i1.Index(
|
|
||||||
'idx_person_owner_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxAssetFacePersonId = i1.Index(
|
|
||||||
'idx_asset_face_person_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxAssetFaceAssetId = i1.Index(
|
|
||||||
'idx_asset_face_asset_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
|
||||||
'idx_trashed_local_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
|
||||||
'idx_trashed_local_asset_album',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Shape30 extends i0.VersionedTable {
|
|
||||||
Shape30({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get name =>
|
|
||||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<int> get type =>
|
|
||||||
columnsByName['type']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<DateTime> get createdAt =>
|
|
||||||
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<DateTime> get updatedAt =>
|
|
||||||
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<int> get width =>
|
|
||||||
columnsByName['width']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get height =>
|
|
||||||
columnsByName['height']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get durationInSeconds =>
|
|
||||||
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get id =>
|
|
||||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get checksum =>
|
|
||||||
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<bool> get isFavorite =>
|
|
||||||
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
|
||||||
i1.GeneratedColumn<int> get orientation =>
|
|
||||||
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get iCloudId =>
|
|
||||||
columnsByName['i_cloud_id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<DateTime> get adjustmentTime =>
|
|
||||||
columnsByName['adjustment_time']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<double> get latitude =>
|
|
||||||
columnsByName['latitude']! as i1.GeneratedColumn<double>;
|
|
||||||
i1.GeneratedColumn<double> get longitude =>
|
|
||||||
columnsByName['longitude']! as i1.GeneratedColumn<double>;
|
|
||||||
i1.GeneratedColumn<int> get playbackStyle =>
|
|
||||||
columnsByName['playback_style']! as i1.GeneratedColumn<int>;
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.GeneratedColumn<int> _column_103(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<int>(
|
|
||||||
'playback_style',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i1.DriftSqlType.int,
|
|
||||||
defaultValue: const CustomExpression('0'),
|
|
||||||
);
|
|
||||||
|
|
||||||
class Shape31 extends i0.VersionedTable {
|
|
||||||
Shape31({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get name =>
|
|
||||||
columnsByName['name']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<int> get type =>
|
|
||||||
columnsByName['type']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<DateTime> get createdAt =>
|
|
||||||
columnsByName['created_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<DateTime> get updatedAt =>
|
|
||||||
columnsByName['updated_at']! as i1.GeneratedColumn<DateTime>;
|
|
||||||
i1.GeneratedColumn<int> get width =>
|
|
||||||
columnsByName['width']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get height =>
|
|
||||||
columnsByName['height']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get durationInSeconds =>
|
|
||||||
columnsByName['duration_in_seconds']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<String> get id =>
|
|
||||||
columnsByName['id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get albumId =>
|
|
||||||
columnsByName['album_id']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get checksum =>
|
|
||||||
columnsByName['checksum']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<bool> get isFavorite =>
|
|
||||||
columnsByName['is_favorite']! as i1.GeneratedColumn<bool>;
|
|
||||||
i1.GeneratedColumn<int> get orientation =>
|
|
||||||
columnsByName['orientation']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get source =>
|
|
||||||
columnsByName['source']! as i1.GeneratedColumn<int>;
|
|
||||||
i1.GeneratedColumn<int> get playbackStyle =>
|
|
||||||
columnsByName['playback_style']! as i1.GeneratedColumn<int>;
|
|
||||||
}
|
|
||||||
|
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -9509,7 +8924,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
|
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
|
||||||
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
|
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
|
||||||
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -9608,11 +9022,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from19To20(migrator, schema);
|
await from19To20(migrator, schema);
|
||||||
return 20;
|
return 20;
|
||||||
case 20:
|
|
||||||
final schema = Schema21(database: database);
|
|
||||||
final migrator = i1.Migrator(database, schema);
|
|
||||||
await from20To21(migrator, schema);
|
|
||||||
return 21;
|
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -9639,7 +9048,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
|
required Future<void> Function(i1.Migrator m, Schema18 schema) from17To18,
|
||||||
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
|
required Future<void> Function(i1.Migrator m, Schema19 schema) from18To19,
|
||||||
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
required Future<void> Function(i1.Migrator m, Schema20 schema) from19To20,
|
||||||
required Future<void> Function(i1.Migrator m, Schema21 schema) from20To21,
|
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -9661,6 +9069,5 @@ i1.OnUpgrade stepByStep({
|
|||||||
from17To18: from17To18,
|
from17To18: from17To18,
|
||||||
from18To19: from18To19,
|
from18To19: from18To19,
|
||||||
from19To20: from19To20,
|
from19To20: from19To20,
|
||||||
from20To21: from20To21,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -301,7 +301,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
id: asset.id,
|
id: asset.id,
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
latitude: Value(asset.latitude),
|
latitude: Value(asset.latitude),
|
||||||
longitude: Value(asset.longitude),
|
longitude: Value(asset.longitude),
|
||||||
adjustmentTime: Value(asset.adjustmentTime),
|
adjustmentTime: Value(asset.adjustmentTime),
|
||||||
@@ -334,7 +333,6 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
checksum: const Value(null),
|
checksum: const Value(null),
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
);
|
);
|
||||||
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
batch.insert<$LocalAssetEntityTable, LocalAssetEntityData>(
|
||||||
_db.localAssetEntity,
|
_db.localAssetEntity,
|
||||||
|
|||||||
@@ -101,7 +101,6 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
isFavorite: row.isFavorite,
|
isFavorite: row.isFavorite,
|
||||||
durationInSeconds: row.durationInSeconds,
|
durationInSeconds: row.durationInSeconds,
|
||||||
orientation: row.orientation,
|
orientation: row.orientation,
|
||||||
playbackStyle: AssetPlaybackStyle.values[row.playbackStyle],
|
|
||||||
cloudId: row.iCloudId,
|
cloudId: row.iCloudId,
|
||||||
latitude: row.latitude,
|
latitude: row.latitude,
|
||||||
longitude: row.longitude,
|
longitude: row.longitude,
|
||||||
|
|||||||
@@ -85,7 +85,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: Value(item.asset.durationInSeconds),
|
durationInSeconds: Value(item.asset.durationInSeconds),
|
||||||
isFavorite: Value(item.asset.isFavorite),
|
isFavorite: Value(item.asset.isFavorite),
|
||||||
orientation: Value(item.asset.orientation),
|
orientation: Value(item.asset.orientation),
|
||||||
playbackStyle: Value(item.asset.playbackStyle),
|
|
||||||
source: TrashOrigin.localSync,
|
source: TrashOrigin.localSync,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -148,7 +147,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: Value(asset.durationInSeconds),
|
durationInSeconds: Value(asset.durationInSeconds),
|
||||||
isFavorite: Value(asset.isFavorite),
|
isFavorite: Value(asset.isFavorite),
|
||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
playbackStyle: Value(asset.playbackStyle),
|
|
||||||
createdAt: Value(asset.createdAt),
|
createdAt: Value(asset.createdAt),
|
||||||
updatedAt: Value(asset.updatedAt),
|
updatedAt: Value(asset.updatedAt),
|
||||||
source: const Value(TrashOrigin.remoteSync),
|
source: const Value(TrashOrigin.remoteSync),
|
||||||
@@ -197,7 +195,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
checksum: Value(e.checksum),
|
checksum: Value(e.checksum),
|
||||||
isFavorite: Value(e.isFavorite),
|
isFavorite: Value(e.isFavorite),
|
||||||
orientation: Value(e.orientation),
|
orientation: Value(e.orientation),
|
||||||
playbackStyle: Value(e.playbackStyle),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -248,7 +245,6 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
checksum: Value(e.asset.checksum),
|
checksum: Value(e.asset.checksum),
|
||||||
isFavorite: Value(e.asset.isFavorite),
|
isFavorite: Value(e.asset.isFavorite),
|
||||||
orientation: Value(e.asset.orientation),
|
orientation: Value(e.asset.orientation),
|
||||||
playbackStyle: Value(e.asset.playbackStyle),
|
|
||||||
source: TrashOrigin.localUser,
|
source: TrashOrigin.localUser,
|
||||||
albumId: e.albumId,
|
albumId: e.albumId,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class FileMediaRepository {
|
|||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
createdAt: entity.createDateTime,
|
createdAt: entity.createDateTime,
|
||||||
updatedAt: entity.modifiedDateTime,
|
updatedAt: entity.modifiedDateTime,
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'dart:io';
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/album.entity.dart';
|
import 'package:immich_mobile/entities/album.entity.dart';
|
||||||
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
|
import 'package:immich_mobile/entities/android_device_asset.entity.dart';
|
||||||
@@ -18,7 +17,6 @@ import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.dart';
|
||||||
@@ -35,7 +33,7 @@ import 'package:isar/isar.dart';
|
|||||||
// ignore: import_rule_photo_manager
|
// ignore: import_rule_photo_manager
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
const int targetVersion = 23;
|
const int targetVersion = 22;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
||||||
final hasVersion = Store.tryGet(StoreKey.version) != null;
|
final hasVersion = Store.tryGet(StoreKey.version) != null;
|
||||||
@@ -101,10 +99,6 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 23 && Store.isBetaTimelineEnabled) {
|
|
||||||
await _populateLocalAssetPlaybackStyle(drift);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (version < 22 && !Store.isBetaTimelineEnabled) {
|
if (version < 22 && !Store.isBetaTimelineEnabled) {
|
||||||
await Store.put(StoreKey.needBetaMigration, true);
|
await Store.put(StoreKey.needBetaMigration, true);
|
||||||
}
|
}
|
||||||
@@ -398,52 +392,6 @@ Future<void> migrateStoreToIsar(Isar db, Drift drift) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _populateLocalAssetPlaybackStyle(Drift db) async {
|
|
||||||
try {
|
|
||||||
final nativeApi = NativeSyncApi();
|
|
||||||
|
|
||||||
final albums = await nativeApi.getAlbums();
|
|
||||||
for (final album in albums) {
|
|
||||||
final assets = await nativeApi.getAssetsForAlbum(album.id);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final asset in assets) {
|
|
||||||
batch.update(
|
|
||||||
db.localAssetEntity,
|
|
||||||
LocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))),
|
|
||||||
where: (t) => t.id.equals(asset.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final trashedAssetMap = await nativeApi.getTrashedAssets();
|
|
||||||
for (final assets in trashedAssetMap.values) {
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final asset in assets) {
|
|
||||||
batch.update(
|
|
||||||
db.trashedLocalAssetEntity,
|
|
||||||
TrashedLocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))),
|
|
||||||
where: (t) => t.id.equals(asset.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dPrint(() => "[MIGRATION] Successfully populated playbackStyle for local and trashed assets");
|
|
||||||
} catch (error) {
|
|
||||||
dPrint(() => "[MIGRATION] Error while populating playbackStyle: $error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) {
|
|
||||||
PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown,
|
|
||||||
PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image,
|
|
||||||
PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video,
|
|
||||||
PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated,
|
|
||||||
PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto,
|
|
||||||
PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping,
|
|
||||||
};
|
|
||||||
|
|
||||||
class _DeviceAsset {
|
class _DeviceAsset {
|
||||||
final String assetId;
|
final String assetId;
|
||||||
final List<int>? hash;
|
final List<int>? hash;
|
||||||
|
|||||||
-4
@@ -23,7 +23,6 @@ import 'schema_v17.dart' as v17;
|
|||||||
import 'schema_v18.dart' as v18;
|
import 'schema_v18.dart' as v18;
|
||||||
import 'schema_v19.dart' as v19;
|
import 'schema_v19.dart' as v19;
|
||||||
import 'schema_v20.dart' as v20;
|
import 'schema_v20.dart' as v20;
|
||||||
import 'schema_v21.dart' as v21;
|
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -69,8 +68,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v19.DatabaseAtV19(db);
|
return v19.DatabaseAtV19(db);
|
||||||
case 20:
|
case 20:
|
||||||
return v20.DatabaseAtV20(db);
|
return v20.DatabaseAtV20(db);
|
||||||
case 21:
|
|
||||||
return v21.DatabaseAtV21(db);
|
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
@@ -97,6 +94,5 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
18,
|
18,
|
||||||
19,
|
19,
|
||||||
20,
|
20,
|
||||||
21,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
-8545
File diff suppressed because it is too large
Load Diff
Vendored
-2
@@ -64,7 +64,6 @@ abstract final class LocalAssetStub {
|
|||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
createdAt: DateTime(2025),
|
createdAt: DateTime(2025),
|
||||||
updatedAt: DateTime(2025, 2),
|
updatedAt: DateTime(2025, 2),
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -74,7 +73,6 @@ abstract final class LocalAssetStub {
|
|||||||
type: AssetType.image,
|
type: AssetType.image,
|
||||||
createdAt: DateTime(2000),
|
createdAt: DateTime(2000),
|
||||||
updatedAt: DateTime(20021),
|
updatedAt: DateTime(20021),
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ void main() {
|
|||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
longitude: -122.4194,
|
longitude: -122.4194,
|
||||||
adjustmentTime: DateTime(2026, 1, 2),
|
adjustmentTime: DateTime(2026, 1, 2),
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -244,7 +243,6 @@ void main() {
|
|||||||
cloudId: 'cloud-id-123',
|
cloudId: 'cloud-id-123',
|
||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
longitude: -122.4194,
|
longitude: -122.4194,
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -283,7 +281,6 @@ void main() {
|
|||||||
createdAt: DateTime(2025, 1, 1),
|
createdAt: DateTime(2025, 1, 1),
|
||||||
updatedAt: DateTime(2025, 1, 2),
|
updatedAt: DateTime(2025, 1, 2),
|
||||||
cloudId: null, // No cloudId
|
cloudId: null, // No cloudId
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -326,7 +323,6 @@ void main() {
|
|||||||
cloudId: 'cloud-id-livephoto',
|
cloudId: 'cloud-id-livephoto',
|
||||||
latitude: 37.7749,
|
latitude: 37.7749,
|
||||||
longitude: -122.4194,
|
longitude: -122.4194,
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ abstract final class TestUtils {
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
playbackStyle: domain.AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ class MediumFactory {
|
|||||||
type: type ?? AssetType.image,
|
type: type ?? AssetType.image,
|
||||||
createdAt: createdAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
|
createdAt: createdAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
|
||||||
updatedAt: updatedAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
|
updatedAt: updatedAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)),
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ LocalAsset createLocalAsset({
|
|||||||
createdAt: createdAt ?? DateTime.now(),
|
createdAt: createdAt ?? DateTime.now(),
|
||||||
updatedAt: updatedAt ?? DateTime.now(),
|
updatedAt: updatedAt ?? DateTime.now(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
playbackStyle: AssetPlaybackStyle.image,
|
|
||||||
isEdited: false,
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
-3
@@ -276,9 +276,6 @@ importers:
|
|||||||
supertest:
|
supertest:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.2.2
|
version: 7.2.2
|
||||||
tsx:
|
|
||||||
specifier: ^4.21.0
|
|
||||||
version: 4.21.0
|
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.3.3
|
specifier: ^5.3.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|||||||
@@ -102,30 +102,22 @@ order by
|
|||||||
"shared_link"."createdAt" desc
|
"shared_link"."createdAt" desc
|
||||||
|
|
||||||
-- SharedLinkRepository.getAll
|
-- SharedLinkRepository.getAll
|
||||||
select
|
select distinct
|
||||||
"shared_link".*,
|
on ("shared_link"."createdAt") "shared_link".*,
|
||||||
(
|
"assets"."assets",
|
||||||
select
|
|
||||||
coalesce(json_agg(agg), '[]')
|
|
||||||
from
|
|
||||||
(
|
|
||||||
select
|
|
||||||
"asset".*
|
|
||||||
from
|
|
||||||
"shared_link_asset"
|
|
||||||
inner join "asset" on "asset"."id" = "shared_link_asset"."assetId"
|
|
||||||
where
|
|
||||||
"shared_link"."id" = "shared_link_asset"."sharedLinkId"
|
|
||||||
and "asset"."deletedAt" is null
|
|
||||||
order by
|
|
||||||
"asset"."fileCreatedAt" asc
|
|
||||||
limit
|
|
||||||
$1
|
|
||||||
) as agg
|
|
||||||
) as "assets",
|
|
||||||
to_json("album") as "album"
|
to_json("album") as "album"
|
||||||
from
|
from
|
||||||
"shared_link"
|
"shared_link"
|
||||||
|
left join "shared_link_asset" on "shared_link_asset"."sharedLinkId" = "shared_link"."id"
|
||||||
|
left join lateral (
|
||||||
|
select
|
||||||
|
json_agg("asset") as "assets"
|
||||||
|
from
|
||||||
|
"asset"
|
||||||
|
where
|
||||||
|
"asset"."id" = "shared_link_asset"."assetId"
|
||||||
|
and "asset"."deletedAt" is null
|
||||||
|
) as "assets" on true
|
||||||
left join lateral (
|
left join lateral (
|
||||||
select
|
select
|
||||||
"album".*,
|
"album".*,
|
||||||
@@ -160,12 +152,12 @@ from
|
|||||||
and "album"."deletedAt" is null
|
and "album"."deletedAt" is null
|
||||||
) as "album" on true
|
) as "album" on true
|
||||||
where
|
where
|
||||||
"shared_link"."userId" = $2
|
"shared_link"."userId" = $1
|
||||||
and (
|
and (
|
||||||
"shared_link"."type" = $3
|
"shared_link"."type" = $2
|
||||||
or "album"."id" is not null
|
or "album"."id" is not null
|
||||||
)
|
)
|
||||||
and "shared_link"."albumId" = $4
|
and "shared_link"."albumId" = $3
|
||||||
order by
|
order by
|
||||||
"shared_link"."createdAt" desc
|
"shared_link"."createdAt" desc
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Insertable, Kysely, sql, Updateable } from 'kysely';
|
import { Insertable, Kysely, NotNull, sql, Updateable } from 'kysely';
|
||||||
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { Album, columns } from 'src/database';
|
import { Album, columns } from 'src/database';
|
||||||
@@ -124,20 +124,19 @@ export class SharedLinkRepository {
|
|||||||
.selectFrom('shared_link')
|
.selectFrom('shared_link')
|
||||||
.selectAll('shared_link')
|
.selectAll('shared_link')
|
||||||
.where('shared_link.userId', '=', userId)
|
.where('shared_link.userId', '=', userId)
|
||||||
.select((eb) =>
|
.leftJoin('shared_link_asset', 'shared_link_asset.sharedLinkId', 'shared_link.id')
|
||||||
jsonArrayFrom(
|
.leftJoinLateral(
|
||||||
|
(eb) =>
|
||||||
eb
|
eb
|
||||||
.selectFrom('shared_link_asset')
|
.selectFrom('asset')
|
||||||
.whereRef('shared_link.id', '=', 'shared_link_asset.sharedLinkId')
|
.select((eb) => eb.fn.jsonAgg('asset').as('assets'))
|
||||||
.innerJoin('asset', 'asset.id', 'shared_link_asset.assetId')
|
.whereRef('asset.id', '=', 'shared_link_asset.assetId')
|
||||||
.where('asset.deletedAt', 'is', null)
|
.where('asset.deletedAt', 'is', null)
|
||||||
.selectAll('asset')
|
.as('assets'),
|
||||||
.orderBy('asset.fileCreatedAt', 'asc')
|
(join) => join.onTrue(),
|
||||||
.limit(1),
|
|
||||||
)
|
|
||||||
.$castTo<MapAsset[]>()
|
|
||||||
.as('assets'),
|
|
||||||
)
|
)
|
||||||
|
.select('assets.assets')
|
||||||
|
.$narrowType<{ assets: NotNull }>()
|
||||||
.leftJoinLateral(
|
.leftJoinLateral(
|
||||||
(eb) =>
|
(eb) =>
|
||||||
eb
|
eb
|
||||||
@@ -180,6 +179,7 @@ export class SharedLinkRepository {
|
|||||||
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
|
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
|
||||||
.$if(!!id, (eb) => eb.where('shared_link.id', '=', id!))
|
.$if(!!id, (eb) => eb.where('shared_link.id', '=', id!))
|
||||||
.orderBy('shared_link.createdAt', 'desc')
|
.orderBy('shared_link.createdAt', 'desc')
|
||||||
|
.distinctOn(['shared_link.createdAt'])
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -233,14 +233,6 @@ export class MediumTestContext<S extends BaseService = BaseService> {
|
|||||||
return { albumUser: { albumId, userId, role }, result };
|
return { albumUser: { albumId, userId, role }, result };
|
||||||
}
|
}
|
||||||
|
|
||||||
async softDeleteAsset(assetId: string) {
|
|
||||||
await this.database.updateTable('asset').set({ deletedAt: new Date() }).where('id', '=', assetId).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
async softDeleteAlbum(albumId: string) {
|
|
||||||
await this.database.updateTable('album').set({ deletedAt: new Date() }).where('id', '=', albumId).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
async newJobStatus(dto: Partial<Insertable<AssetJobStatusTable>> & { assetId: string }) {
|
async newJobStatus(dto: Partial<Insertable<AssetJobStatusTable>> & { assetId: string }) {
|
||||||
const jobStatus = mediumFactory.assetJobStatusInsert({ assetId: dto.assetId });
|
const jobStatus = mediumFactory.assetJobStatusInsert({ assetId: dto.assetId });
|
||||||
const result = await this.get(AssetRepository).upsertJobStatus(jobStatus);
|
const result = await this.get(AssetRepository).upsertJobStatus(jobStatus);
|
||||||
|
|||||||
@@ -95,469 +95,6 @@ describe(SharedLinkService.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getAll', () => {
|
|
||||||
it('should return all shared links even when they share the same createdAt', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sameTimestamp = '2024-01-01T00:00:00.000Z';
|
|
||||||
|
|
||||||
const link1 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
createdAt: sameTimestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
const link2 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
createdAt: sameTimestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(2);
|
|
||||||
const ids = result.map((r) => r.id);
|
|
||||||
expect(ids).toContain(link1.id);
|
|
||||||
expect(ids).toContain(link2.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return shared links sorted by createdAt in descending order', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
const link1 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
createdAt: '2021-01-01T00:00:00.000Z',
|
|
||||||
});
|
|
||||||
|
|
||||||
const link2 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
createdAt: '2023-01-01T00:00:00.000Z',
|
|
||||||
});
|
|
||||||
|
|
||||||
const link3 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
createdAt: '2022-01-01T00:00:00.000Z',
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(3);
|
|
||||||
expect(result.map((r) => r.id)).toEqual([link2.id, link3.id, link1.id]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return shared links belonging to other users', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
|
|
||||||
const { user: userA } = await ctx.newUser();
|
|
||||||
const { user: userB } = await ctx.newUser();
|
|
||||||
const authA = factory.auth({ user: userA });
|
|
||||||
const authB = factory.auth({ user: userB });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
const linkA = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: userA.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
});
|
|
||||||
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: userB.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
});
|
|
||||||
|
|
||||||
const resultA = await sut.getAll(authA, {});
|
|
||||||
expect(resultA).toHaveLength(1);
|
|
||||||
expect(resultA[0].id).toBe(linkA.id);
|
|
||||||
|
|
||||||
const resultB = await sut.getAll(authB, {});
|
|
||||||
expect(resultB).toHaveLength(1);
|
|
||||||
expect(resultB[0].id).not.toBe(linkA.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should filter by albumId', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
const link1 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album1.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album2.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, { albumId: album1.id });
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].id).toBe(link1.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return album shared links with album data', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].album).toBeDefined();
|
|
||||||
expect(result[0].album!.id).toBe(album.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return multiple album shared links without sql error from json group by', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { album: album1 } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
const { album: album2 } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
const link1 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album1.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const link2 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album2.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(2);
|
|
||||||
const ids = result.map((r) => r.id);
|
|
||||||
expect(ids).toContain(link1.id);
|
|
||||||
expect(ids).toContain(link2.id);
|
|
||||||
expect(result[0].album).toBeDefined();
|
|
||||||
expect(result[1].album).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return mixed album and individual shared links together', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
const albumLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const albumLink2 = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
const individualLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [asset.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(3);
|
|
||||||
const ids = result.map((r) => r.id);
|
|
||||||
expect(ids).toContain(albumLink.id);
|
|
||||||
expect(ids).toContain(albumLink2.id);
|
|
||||||
expect(ids).toContain(individualLink.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return only the first asset as cover for an individual shared link', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const assets = await Promise.all([
|
|
||||||
ctx.newAsset({ ownerId: user.id, fileCreatedAt: '2021-01-01T00:00:00.000Z' }),
|
|
||||||
ctx.newAsset({ ownerId: user.id, fileCreatedAt: '2023-01-01T00:00:00.000Z' }),
|
|
||||||
ctx.newAsset({ ownerId: user.id, fileCreatedAt: '2022-01-01T00:00:00.000Z' }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: assets.map(({ asset }) => asset.id),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].assets).toHaveLength(1);
|
|
||||||
expect(result[0].assets[0].id).toBe(assets[0].asset.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get', () => {
|
|
||||||
it('should not return trashed assets for an individual shared link', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { asset: visibleAsset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: visibleAsset.id, make: 'Canon' });
|
|
||||||
|
|
||||||
const { asset: trashedAsset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: trashedAsset.id, make: 'Canon' });
|
|
||||||
await ctx.softDeleteAsset(trashedAsset.id);
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sharedLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [visibleAsset.id, trashedAsset.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.get(auth, sharedLink.id);
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
expect(result!.assets).toHaveLength(1);
|
|
||||||
expect(result!.assets[0].id).toBe(visibleAsset.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return empty assets when all individually shared assets are trashed', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: asset.id, make: 'Canon' });
|
|
||||||
await ctx.softDeleteAsset(asset.id);
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sharedLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [asset.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(sut.get(auth, sharedLink.id)).resolves.toMatchObject({
|
|
||||||
assets: [],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return trashed assets in a shared album', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const { asset: visibleAsset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: visibleAsset.id, make: 'Canon' });
|
|
||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: visibleAsset.id });
|
|
||||||
|
|
||||||
const { asset: trashedAsset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: trashedAsset.id, make: 'Canon' });
|
|
||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: trashedAsset.id });
|
|
||||||
await ctx.softDeleteAsset(trashedAsset.id);
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sharedLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: true,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(sut.get(auth, sharedLink.id)).resolves.toMatchObject({
|
|
||||||
album: expect.objectContaining({ assetCount: 1 }),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an empty asset count when all album assets are trashed', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
|
||||||
await ctx.newExif({ assetId: asset.id, make: 'Canon' });
|
|
||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
|
||||||
await ctx.softDeleteAsset(asset.id);
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sharedLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
await expect(sut.get(auth, sharedLink.id)).resolves.toMatchObject({
|
|
||||||
album: expect.objectContaining({ assetCount: 0 }),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return an album shared link when the album is trashed', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
const sharedLink = await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.softDeleteAlbum(album.id);
|
|
||||||
|
|
||||||
await expect(sut.get(auth, sharedLink.id)).rejects.toThrow('Shared link not found');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getAll', () => {
|
|
||||||
it('should not return trashed assets as cover for an individual shared link', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
|
|
||||||
const { asset: trashedAsset } = await ctx.newAsset({
|
|
||||||
ownerId: user.id,
|
|
||||||
fileCreatedAt: '2020-01-01T00:00:00.000Z',
|
|
||||||
});
|
|
||||||
await ctx.softDeleteAsset(trashedAsset.id);
|
|
||||||
|
|
||||||
const { asset: visibleAsset } = await ctx.newAsset({
|
|
||||||
ownerId: user.id,
|
|
||||||
fileCreatedAt: '2021-01-01T00:00:00.000Z',
|
|
||||||
});
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Individual,
|
|
||||||
assetIds: [trashedAsset.id, visibleAsset.id],
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(result[0].assets).toHaveLength(1);
|
|
||||||
expect(result[0].assets[0].id).toBe(visibleAsset.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not return an album shared link when the album is trashed', async () => {
|
|
||||||
const { sut, ctx } = setup();
|
|
||||||
const { user } = await ctx.newUser();
|
|
||||||
const auth = factory.auth({ user });
|
|
||||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
|
||||||
|
|
||||||
const sharedLinkRepo = ctx.get(SharedLinkRepository);
|
|
||||||
await sharedLinkRepo.create({
|
|
||||||
key: randomBytes(16),
|
|
||||||
id: factory.uuid(),
|
|
||||||
userId: user.id,
|
|
||||||
albumId: album.id,
|
|
||||||
allowUpload: false,
|
|
||||||
type: SharedLinkType.Album,
|
|
||||||
});
|
|
||||||
|
|
||||||
await ctx.softDeleteAlbum(album.id);
|
|
||||||
|
|
||||||
const result = await sut.getAll(auth, {});
|
|
||||||
expect(result).toHaveLength(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove individually shared asset', async () => {
|
it('should remove individually shared asset', async () => {
|
||||||
const { sut, ctx } = setup();
|
const { sut, ctx } = setup();
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,7 @@
|
|||||||
|
|
||||||
<svelte:window bind:innerWidth />
|
<svelte:window bind:innerWidth />
|
||||||
|
|
||||||
<nav
|
<nav id="dashboard-navbar" class="max-md:h-(--navbar-height-md) h-(--navbar-height) w-dvw text-sm">
|
||||||
id="dashboard-navbar"
|
|
||||||
class="max-md:h-(--navbar-height-md) h-(--navbar-height) w-dvw text-sm bg-red-50 dark:bg-red-950"
|
|
||||||
>
|
|
||||||
<SkipLink text={$t('skip_to_content')} />
|
<SkipLink text={$t('skip_to_content')} />
|
||||||
<div
|
<div
|
||||||
class="grid h-full grid-cols-[--spacing(32)_auto] items-center py-2 sidebar:grid-cols-[--spacing(64)_auto] {noBorder
|
class="grid h-full grid-cols-[--spacing(32)_auto] items-center py-2 sidebar:grid-cols-[--spacing(64)_auto] {noBorder
|
||||||
@@ -83,7 +80,6 @@
|
|||||||
<a data-sveltekit-preload-data="hover" href={Route.photos()}>
|
<a data-sveltekit-preload-data="hover" href={Route.photos()}>
|
||||||
<Logo variant={mediaQueryManager.isFullSidebar ? 'inline' : 'icon'} class="max-md:h-12" />
|
<Logo variant={mediaQueryManager.isFullSidebar ? 'inline' : 'icon'} class="max-md:h-12" />
|
||||||
</a>
|
</a>
|
||||||
<span class="text-xs font-bold text-red-500 ms-2">[VISUAL TEST]</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between gap-4 lg:gap-8 pe-6">
|
<div class="flex justify-between gap-4 lg:gap-8 pe-6">
|
||||||
<div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block">
|
<div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block">
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { scaleToFit } from '$lib/utils/layout-utils';
|
||||||
|
|
||||||
|
describe('scaleToFit', () => {
|
||||||
|
const tests = [
|
||||||
|
{
|
||||||
|
name: 'landscape image in square container',
|
||||||
|
dimensions: { width: 2000, height: 1000 },
|
||||||
|
container: { width: 500, height: 500 },
|
||||||
|
expected: { width: 500, height: 250 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'portrait image in square container',
|
||||||
|
dimensions: { width: 1000, height: 2000 },
|
||||||
|
container: { width: 500, height: 500 },
|
||||||
|
expected: { width: 250, height: 500 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'square image in square container',
|
||||||
|
dimensions: { width: 1000, height: 1000 },
|
||||||
|
container: { width: 500, height: 500 },
|
||||||
|
expected: { width: 500, height: 500 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'landscape image in landscape container',
|
||||||
|
dimensions: { width: 1600, height: 900 },
|
||||||
|
container: { width: 800, height: 600 },
|
||||||
|
expected: { width: 800, height: 450 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'portrait image in portrait container',
|
||||||
|
dimensions: { width: 900, height: 1600 },
|
||||||
|
container: { width: 600, height: 800 },
|
||||||
|
expected: { width: 450, height: 800 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image matches container exactly',
|
||||||
|
dimensions: { width: 500, height: 300 },
|
||||||
|
container: { width: 500, height: 300 },
|
||||||
|
expected: { width: 500, height: 300 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'image smaller than container scales up',
|
||||||
|
dimensions: { width: 100, height: 50 },
|
||||||
|
container: { width: 400, height: 400 },
|
||||||
|
expected: { width: 400, height: 200 },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { name, dimensions, container, expected } of tests) {
|
||||||
|
it(`should handle ${name}`, () => {
|
||||||
|
expect(scaleToFit(dimensions, container)).toEqual(expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -129,3 +129,19 @@ export type CommonPosition = {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Scales dimensions to fit within a container (like object-fit: contain)
|
||||||
|
export const scaleToFit = (
|
||||||
|
dimensions: { width: number; height: number },
|
||||||
|
container: { width: number; height: number },
|
||||||
|
) => {
|
||||||
|
const scaleX = container.width / dimensions.width;
|
||||||
|
const scaleY = container.height / dimensions.height;
|
||||||
|
|
||||||
|
const scale = Math.min(scaleX, scaleY);
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: dimensions.width * scale,
|
||||||
|
height: dimensions.height * scale,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user