diff --git a/api/index.php b/api/index.php index 1c32c4d0..d211942f 100644 --- a/api/index.php +++ b/api/index.php @@ -532,13 +532,14 @@ function routeFiles( if ($method === 'DELETE') { $file = implode('/', array_slice($parts, 1)); - if (!$store->exists($file)) { - Response::error("{$noun} not found", 404); - } + // Asked before whether there is one: somebody else's folder tells a stranger nothing. $folder = HashedFileStore::folderOf($file); if (!$isAdmin && ($folder === '' || $folder !== $users->folderOf($user))) { Response::error('Only its owner may delete this file', 403); } + if (!$store->exists($file)) { + Response::error("{$noun} not found", 404); + } $uses = (new FileUsage(dirname(__DIR__) . '/data'))->of($file); if ($uses !== []) { Response::error(FileUsage::refusal($uses, $user, $isAdmin), 409); diff --git a/src/editor/graph/AnnotationView.ts b/src/editor/graph/AnnotationView.ts index ec4388d7..acface5b 100644 --- a/src/editor/graph/AnnotationView.ts +++ b/src/editor/graph/AnnotationView.ts @@ -18,6 +18,8 @@ const PAD = 8; export class AnnotationView extends Widget { private readonly box = new Graphics(); private readonly caption: Text; + /** Colour and width of the caption's style, see `redraw`. */ + private styleKey = ''; selected = false; constructor( @@ -105,7 +107,13 @@ export class AnnotationView extends Widget { .lineTo(this.w - 2, this.h - RESIZE_HANDLE / 2) .stroke({ width: 1, color: text, alpha: 0.5 }); - this.caption.style = this.captionStyle(text); + // A new style draws the caption again even when nothing changed, and the graph redraws on every + // edit of the project, a stroke on a tilemap included (#347). So only a new colour or width sets one. + const styleKey = `${text}|${this.w}`; + if (styleKey !== this.styleKey) { + this.styleKey = styleKey; + this.caption.style = this.captionStyle(text); + } this.caption.text = this.kind === 'group' ? truncate(this.annotation.text, this.caption.style, Math.max(this.w - PAD * 2, 1)) diff --git a/tests/editor/annotation-view.test.ts b/tests/editor/annotation-view.test.ts new file mode 100644 index 00000000..e749e330 --- /dev/null +++ b/tests/editor/annotation-view.test.ts @@ -0,0 +1,35 @@ +/** + * A graph draws its groups and comments again on every edit of the project, a stroke on a tilemap + * included. A caption that is given a new style is drawn again as text, so it keeps its style for as + * long as colour and width stay the same (#347). + */ +import type { Text } from 'pixi.js'; +import { describe, expect, it } from 'vitest'; +import { AnnotationView } from '../../src/editor/graph/AnnotationView'; + +const note = { id: 'n1', x: 0, y: 0, w: 200, h: 120, text: 'Comment' }; +const captionOf = (view: AnnotationView) => view.children[1] as Text; + +describe('the caption of a group or comment', () => { + // A comment only: a group's title is cut to its width by measuring it, which needs a canvas. The + // groups are seen in the browser, in e2e 40c-iii. + it('keeps its style through a redraw that changes nothing', () => { + const view = new AnnotationView({ ...note }, 'note'); + const style = captionOf(view).style; + view.redraw(); + view.sync({ ...note }); + expect(captionOf(view).style).toBe(style); + }); + + it('is given a new style for a new width or colour', () => { + const view = new AnnotationView({ ...note }, 'note'); + const style = captionOf(view).style; + view.sync({ ...note, w: 260 }); + const wider = captionOf(view).style; + expect(wider).not.toBe(style); + expect(wider.wordWrapWidth).toBe(260 - 16); + view.sync({ ...note, w: 260, color: 0xffffff }); + expect(captionOf(view).style).not.toBe(wider); + expect(captionOf(view).style.fill).not.toBe(wider.fill); + }); +});