revert: foto de perfil e BIMI, para restaurar o servico
O site ficou a responder 502 em todos os hostnames depois do deploy destas duas funcionalidades. A reversao vem primeiro; o diagnostico vem a seguir, com o servico de pe. As migracoes 0012 e 0013 ficam. Sao aditivas, nada no codigo revertido as usa, e apaga-las deixaria a base de dados numa versao sem ficheiro correspondente, o que parte o proprio migrate no arranque seguinte. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3e06162fa0
commit
e30d000e43
26 files changed
+21
-1899
No files matched your search
@@ -1,109 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
minimumScale,
|
||||
scaleRange,
|
||||
clampTransform,
|
||||
initialTransform,
|
||||
sourceRect,
|
||||
} from "./avatarCrop";
|
||||
|
||||
const VIEWPORT = 320;
|
||||
|
||||
describe("minimumScale", () => {
|
||||
// Cover, not contain. Scaling a wide photo to fit its width leaves the top
|
||||
// and bottom of the circle empty.
|
||||
it("covers the frame on the short side of a wide image", () => {
|
||||
expect(minimumScale({ width: 800, height: 400 }, VIEWPORT)).toBeCloseTo(320 / 400);
|
||||
});
|
||||
|
||||
it("covers the frame on the short side of a tall image", () => {
|
||||
expect(minimumScale({ width: 400, height: 800 }, VIEWPORT)).toBeCloseTo(320 / 400);
|
||||
});
|
||||
|
||||
it("scales a small image up rather than leaving it small", () => {
|
||||
expect(minimumScale({ width: 100, height: 100 }, VIEWPORT)).toBeCloseTo(3.2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[{ width: 0, height: 100 }],
|
||||
[{ width: 100, height: 0 }],
|
||||
[{ width: Number.NaN, height: 100 }],
|
||||
])("survives a degenerate image %o", (image) => {
|
||||
expect(Number.isFinite(minimumScale(image, VIEWPORT))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("clampTransform", () => {
|
||||
const image = { width: 800, height: 600 };
|
||||
|
||||
it("refuses to zoom below covering the frame", () => {
|
||||
const t = clampTransform(image, VIEWPORT, { scale: 0.01, offsetX: 0, offsetY: 0 });
|
||||
expect(t.scale).toBeCloseTo(minimumScale(image, VIEWPORT));
|
||||
});
|
||||
|
||||
// Without this, dragging far enough pulls the edge of the photo into the
|
||||
// circle and the export carries a transparent wedge.
|
||||
it("stops the image at its own edge", () => {
|
||||
const scale = minimumScale(image, VIEWPORT);
|
||||
const t = clampTransform(image, VIEWPORT, { scale, offsetX: 99999, offsetY: 99999 });
|
||||
|
||||
const overhangX = (image.width * scale - VIEWPORT) / 2;
|
||||
expect(t.offsetX).toBeCloseTo(overhangX);
|
||||
// At the covering scale the short side has no slack at all, so it cannot
|
||||
// move: any movement there would expose a gap.
|
||||
expect(t.offsetY).toBeCloseTo(0);
|
||||
});
|
||||
|
||||
it("allows movement on both axes once zoomed in", () => {
|
||||
const scale = minimumScale(image, VIEWPORT) * 2;
|
||||
const t = clampTransform(image, VIEWPORT, { scale, offsetX: 99999, offsetY: 99999 });
|
||||
expect(t.offsetX).toBeGreaterThan(0);
|
||||
expect(t.offsetY).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("caps the zoom so the photo cannot be magnified into mush", () => {
|
||||
const { max } = scaleRange(image, VIEWPORT);
|
||||
expect(clampTransform(image, VIEWPORT, { scale: 9999, offsetX: 0, offsetY: 0 }).scale).toBeCloseTo(max);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sourceRect", () => {
|
||||
const image = { width: 800, height: 600 };
|
||||
|
||||
it("is always square, because the frame is", () => {
|
||||
const r = sourceRect(image, VIEWPORT, { scale: 1.2, offsetX: 30, offsetY: -20 });
|
||||
expect(r.width).toBeCloseTo(r.height);
|
||||
});
|
||||
|
||||
// At the covering scale the whole short side is used: nothing of the height
|
||||
// is cropped away, and the crop is the full 600px of it.
|
||||
it("takes the whole short side at the covering scale", () => {
|
||||
const r = sourceRect(image, VIEWPORT, initialTransform(image, VIEWPORT));
|
||||
expect(r.height).toBeCloseTo(600);
|
||||
expect(r.y).toBeCloseTo(0);
|
||||
// Centred horizontally: (800 - 600) / 2.
|
||||
expect(r.x).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
it("never reads outside the image", () => {
|
||||
for (const wanted of [
|
||||
{ scale: 0.001, offsetX: -9999, offsetY: -9999 },
|
||||
{ scale: 9999, offsetX: 9999, offsetY: 9999 },
|
||||
{ scale: 1, offsetX: 0, offsetY: 9999 },
|
||||
]) {
|
||||
const r = sourceRect(image, VIEWPORT, wanted);
|
||||
expect(r.x).toBeGreaterThanOrEqual(-0.001);
|
||||
expect(r.y).toBeGreaterThanOrEqual(-0.001);
|
||||
expect(r.x + r.width).toBeLessThanOrEqual(image.width + 0.001);
|
||||
expect(r.y + r.height).toBeLessThanOrEqual(image.height + 0.001);
|
||||
}
|
||||
});
|
||||
|
||||
// Dragging the image right shows what is further left in the source.
|
||||
it("moves the crop opposite to the drag", () => {
|
||||
const scale = minimumScale(image, VIEWPORT) * 2;
|
||||
const left = sourceRect(image, VIEWPORT, { scale, offsetX: 40, offsetY: 0 });
|
||||
const centre = sourceRect(image, VIEWPORT, { scale, offsetX: 0, offsetY: 0 });
|
||||
expect(left.x).toBeLessThan(centre.x);
|
||||
});
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* The geometry behind the circular photo editor.
|
||||
*
|
||||
* The editor shows a fixed square viewport with a circular mask; the image sits
|
||||
* behind it, scaled and dragged. Everything here works in viewport pixels and
|
||||
* is deliberately free of DOM and canvas, so the awkward parts - the smallest
|
||||
* scale that still covers the circle, and clamping so no gap can be dragged
|
||||
* into view - are testable on their own.
|
||||
*/
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** Where the image sits behind the viewport, and how big it is drawn. */
|
||||
export interface Transform {
|
||||
/** Multiplier applied to the image's natural size. */
|
||||
scale: number;
|
||||
/** Image centre, relative to the viewport centre, in viewport pixels. */
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The smallest scale at which the image still covers the whole viewport.
|
||||
*
|
||||
* Below this a corner of the frame is empty, and the exported photo would carry
|
||||
* a transparent wedge - so this is the floor for the zoom control, not merely a
|
||||
* suggestion.
|
||||
*/
|
||||
export function minimumScale(image: Size, viewport: number): number {
|
||||
// Finite, not merely positive: NaN fails every comparison, so `<= 0` lets it
|
||||
// through and it then propagates into the transform and blanks the editor.
|
||||
// A NaN dimension is what an image that failed to decode reports.
|
||||
if (
|
||||
!Number.isFinite(image.width) || !Number.isFinite(image.height) || !Number.isFinite(viewport) ||
|
||||
image.width <= 0 || image.height <= 0 || viewport <= 0
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
// Cover, not contain: the larger of the two ratios.
|
||||
return Math.max(viewport / image.width, viewport / image.height);
|
||||
}
|
||||
|
||||
/** The zoom control's range: from covering the frame to four times that. */
|
||||
export function scaleRange(image: Size, viewport: number): { min: number; max: number } {
|
||||
const min = minimumScale(image, viewport);
|
||||
return { min, max: min * 4 };
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the image over the viewport, whatever was asked for.
|
||||
*
|
||||
* A drag or a zoom out can otherwise pull an edge inside the frame. Clamping
|
||||
* the offset to the overhang keeps the frame covered without fighting the
|
||||
* gesture: the image stops at its edge instead of springing back.
|
||||
*/
|
||||
export function clampTransform(image: Size, viewport: number, wanted: Transform): Transform {
|
||||
const { min, max } = scaleRange(image, viewport);
|
||||
const scale = Math.min(Math.max(wanted.scale, min), max);
|
||||
|
||||
// Half of what sticks out past the viewport on each axis.
|
||||
const overhangX = Math.max(0, (image.width * scale - viewport) / 2);
|
||||
const overhangY = Math.max(0, (image.height * scale - viewport) / 2);
|
||||
|
||||
return {
|
||||
scale,
|
||||
offsetX: Math.min(Math.max(wanted.offsetX, -overhangX), overhangX),
|
||||
offsetY: Math.min(Math.max(wanted.offsetY, -overhangY), overhangY),
|
||||
};
|
||||
}
|
||||
|
||||
/** The transform that centres the image at the smallest covering scale. */
|
||||
export function initialTransform(image: Size, viewport: number): Transform {
|
||||
return { scale: minimumScale(image, viewport), offsetX: 0, offsetY: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* The rectangle of the *source* image that the viewport is showing.
|
||||
*
|
||||
* This is what the export draws. Working back to source pixels rather than
|
||||
* scaling the preview means the saved photo is as sharp as the original
|
||||
* allows, instead of being limited to the size of the editor on screen.
|
||||
*/
|
||||
export interface SourceRect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export function sourceRect(image: Size, viewport: number, transform: Transform): SourceRect {
|
||||
const t = clampTransform(image, viewport, transform);
|
||||
// One viewport pixel is this many source pixels.
|
||||
const perPixel = 1 / t.scale;
|
||||
const size = viewport * perPixel;
|
||||
|
||||
// The viewport centre, expressed in source coordinates.
|
||||
const centreX = image.width / 2 - t.offsetX * perPixel;
|
||||
const centreY = image.height / 2 - t.offsetY * perPixel;
|
||||
|
||||
return {
|
||||
x: centreX - size / 2,
|
||||
y: centreY - size / 2,
|
||||
width: size,
|
||||
height: size,
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user