Merge upstream v1.12.26 - reconnect the fork to Element Web (management #0099)
The repo had no upstream ancestry: a whole tree arrived in one commit in May, so
every update meant re-applying our patches by hand onto a fresh checkout, and a file
Element moved would take our lines with it silently.
The real base was found by measuring tree distance across develop rather than trusting
the changelog: deadd548, not the v1.12.17 tag. With that set as a temporary graft, this
merge computed as a proper three-way merge - 32 conflicts instead of 1757.
Resolutions, each decided rather than defaulted:
- 24 GitHub workflows stay deleted; we build on GitLab CI.
- MImageBody.tsx is gone upstream, migrated to MVVM. Our ClamAV error label moved into
ImageBodyViewModel.computeErrorLabel, ahead of the DecryptError branch, matching what
VideoBodyViewModel and FileBodyViewModel already do.
- Upstream extracted the room list item body into RoomListItemContent. Our call
participants list and its getInitials helper moved there; both sides' CSS classes and
both sides' props are kept.
- matrix-js-sdk follows upstream at 42.2.0 - our git ref pin was a workaround for a
stale ref, and following upstream is the point of this merge.
- Element Call stays ours. Checked before deciding: @element-hq/element-call-embedded
is referenced nowhere in the tree, while webpack.config.ts needs
@sorb/threadnet-call-embedded, so taking upstream's line would have deleted the noise
suppression from #0054 without a word.
The lockfile was regenerated with pnpm 11.20.0, which upstream now requires through
devEngines. CI already runs corepack enable, and onFail: download makes it fetch that
version by itself.
Not yet accepted: this needs a build and the ClamAV functional test - send an encrypted
file, receive a rejected one - before it goes near main.
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
plugins: ["matrix-org", "eslint-plugin-react-compiler"],
|
||||
extends: [
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:matrix-org/a11y",
|
||||
"plugin:matrix-org/typescript",
|
||||
"plugin:matrix-org/react",
|
||||
"plugin:storybook/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
rules: {
|
||||
// Bind or arrow functions in props causes performance issues (but we
|
||||
// currently use them in some places).
|
||||
// It's disabled here, but we should using it sparingly.
|
||||
"react/jsx-no-bind": "off",
|
||||
"react/jsx-key": ["error"],
|
||||
"matrix-org/require-copyright-header": "error",
|
||||
"react-compiler/react-compiler": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "react",
|
||||
importNames: ["act"],
|
||||
message: "Please use @test-utils instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
"@typescript-eslint/unbound-method": ["error", { ignoreStatic: true }],
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"error",
|
||||
{
|
||||
allowExpressions: true,
|
||||
},
|
||||
],
|
||||
|
||||
// We're okay being explicit at the moment
|
||||
// "@typescript-eslint/no-empty-interface": "off",
|
||||
// We'd rather not do this but we do
|
||||
// "@typescript-eslint/ban-ts-comment": "off",
|
||||
// We're okay with assertion errors when we ask for them
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-empty-object-type": [
|
||||
"error",
|
||||
{
|
||||
// We do this sometimes to brand interfaces
|
||||
allowInterfaces: "with-single-extends",
|
||||
},
|
||||
],
|
||||
"storybook/meta-satisfies-type": "error",
|
||||
|
||||
"react/forbid-elements": [
|
||||
"error",
|
||||
{
|
||||
forbid: [
|
||||
{ element: "h1", message: "Use Compound <Heading> instead" },
|
||||
{ element: "h2", message: "Use Compound <Heading> instead" },
|
||||
{ element: "h3", message: "Use Compound <Heading> instead" },
|
||||
{ element: "h4", message: "Use Compound <Heading> instead" },
|
||||
{ element: "h5", message: "Use Compound <Heading> instead" },
|
||||
{ element: "h6", message: "Use Compound <Heading> instead" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["src/**/*.test.{ts,tsx}", "src/**/*.stories.tsx"],
|
||||
rules: {
|
||||
"@typescript-eslint/unbound-method": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"react/forbid-elements": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
// Even though this (at time of writing) is identical Element Web's
|
||||
// .prettierrc.js, shared components needs its own because otherwise
|
||||
// this refers to element web's copy of eslint-plugin-matrix-org which
|
||||
// would require element-web's modules to be installed.
|
||||
module.exports = require("eslint-plugin-matrix-org/.prettierrc.js");
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Match the app/web shell baseline without importing app/web component styles.
|
||||
* Shared component stories should inherit the same root typography and font
|
||||
* feature settings as the real app, while still owning their component styling.
|
||||
*/
|
||||
@layer app-web {
|
||||
:root[data-storybook-root-css="app-web"] {
|
||||
[class^="cpd-theme"][class^="cpd-theme"] {
|
||||
--cpd-font-family-sans:
|
||||
"Inter", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Arial", "Helvetica", sans-serif,
|
||||
"Noto Color Emoji";
|
||||
}
|
||||
|
||||
body {
|
||||
font: var(--cpd-font-body-md-regular) !important;
|
||||
letter-spacing: var(--cpd-font-letter-spacing-body-md) !important;
|
||||
font-feature-settings:
|
||||
"kern" 1,
|
||||
"liga" 1,
|
||||
"calt" 1 !important;
|
||||
|
||||
background-color: var(--cpd-color-bg-canvas-default);
|
||||
color: var(--cpd-color-text-primary);
|
||||
border: 0;
|
||||
margin: 0;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
font-family:
|
||||
"Fira Code", "Twemoji", "Apple Color Emoji", "Segoe UI Emoji", "Courier", monospace, "Noto Color Emoji";
|
||||
font-size: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Username color classes - these are defined in the main app's _common.pcss
|
||||
but need to be available in Storybook for components that use colorClass */
|
||||
.mx_Username_color1 {
|
||||
color: var(--cpd-color-text-decorative-1);
|
||||
}
|
||||
|
||||
.mx_Username_color2 {
|
||||
color: var(--cpd-color-text-decorative-2);
|
||||
}
|
||||
|
||||
.mx_Username_color3 {
|
||||
color: var(--cpd-color-text-decorative-3);
|
||||
}
|
||||
|
||||
.mx_Username_color4 {
|
||||
color: var(--cpd-color-text-decorative-4);
|
||||
}
|
||||
|
||||
.mx_Username_color5 {
|
||||
color: var(--cpd-color-text-decorative-5);
|
||||
}
|
||||
|
||||
.mx_Username_color6 {
|
||||
color: var(--cpd-color-text-decorative-6);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Addon, types, useGlobals } from "storybook/manager-api";
|
||||
import { type Addon, types, useGlobals } from "storybook/manager-api";
|
||||
import { WithTooltip, IconButton, TooltipLinkList } from "storybook/internal/components";
|
||||
import React from "react";
|
||||
import { GlobeIcon } from "@storybook/icons";
|
||||
@@ -24,6 +24,7 @@ export const languageAddon: Addon = {
|
||||
title: "Language Selector",
|
||||
type: types.TOOL,
|
||||
render: ({ active }) => {
|
||||
// oxlint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [globals, updateGlobals] = useGlobals();
|
||||
const selectedLanguage = globals.language || "en";
|
||||
|
||||
|
||||
@@ -9,15 +9,15 @@ import type { StorybookConfig } from "@storybook/react-vite";
|
||||
import fs from "node:fs";
|
||||
import { nodePolyfills } from "vite-plugin-node-polyfills";
|
||||
import { mergeConfig, normalizePath, type Plugin } from "vite";
|
||||
import { dirname, join } from "node:path";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const srcRoot = normalizePath(join(__dirname, "..", "src"));
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const srcRoot = normalizePath(path.join(__dirname, "..", "src"));
|
||||
const sharedComponentsLayer = "shared-components";
|
||||
|
||||
// Get a list of available languages so the language selector can display them at runtime
|
||||
const languageFiles = fs.readdirSync(join(__dirname, "..", "src", "i18n", "strings")).map((f) => f.slice(0, -5));
|
||||
const languageFiles = fs.readdirSync(path.join(__dirname, "..", "src", "i18n", "strings")).map((f) => f.slice(0, -5));
|
||||
|
||||
const languages: Record<string, string> = {};
|
||||
for (const lang of languageFiles) {
|
||||
@@ -35,7 +35,7 @@ for (const lang of languageFiles) {
|
||||
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
|
||||
*/
|
||||
function getAbsolutePath(value: string): any {
|
||||
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
|
||||
return path.dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
|
||||
}
|
||||
|
||||
function layerSharedComponentCssModules(): Plugin {
|
||||
@@ -88,7 +88,7 @@ const config: StorybookConfig = {
|
||||
name: "language-middleware",
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
if (req.url === "/i18n/languages.json") {
|
||||
if (req.url?.split("?")[0] === "/i18n/languages.json") {
|
||||
// Dynamically generate a languages.json file based on what files are available
|
||||
res.setHeader("Content-Type", "application/json");
|
||||
res.end(JSON.stringify(languages));
|
||||
|
||||
@@ -9,28 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
||||
background: var(--cpd-color-bg-canvas-default);
|
||||
}
|
||||
|
||||
/* Username color classes - these are defined in the main app's _common.pcss
|
||||
but need to be available in Storybook for components that use colorClass */
|
||||
.mx_Username_color1 {
|
||||
color: var(--cpd-color-text-decorative-1);
|
||||
}
|
||||
|
||||
.mx_Username_color2 {
|
||||
color: var(--cpd-color-text-decorative-2);
|
||||
}
|
||||
|
||||
.mx_Username_color3 {
|
||||
color: var(--cpd-color-text-decorative-3);
|
||||
}
|
||||
|
||||
.mx_Username_color4 {
|
||||
color: var(--cpd-color-text-decorative-4);
|
||||
}
|
||||
|
||||
.mx_Username_color5 {
|
||||
color: var(--cpd-color-text-decorative-5);
|
||||
}
|
||||
|
||||
.mx_Username_color6 {
|
||||
color: var(--cpd-color-text-decorative-6);
|
||||
[class*="blurhash"] > canvas {
|
||||
animation: none !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ import "@fontsource/inter/600.css";
|
||||
import "@fontsource/inter/700.css";
|
||||
|
||||
import "./compound.css";
|
||||
import "./app-web-root.css";
|
||||
import "./preview.css";
|
||||
import React, { useLayoutEffect } from "react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import type { StoryContext } from "storybook/internal/csf";
|
||||
|
||||
import { EventPresentationProvider, type EventDensity, type EventLayout, I18nApi, I18nContext } from "../src";
|
||||
import { setLanguage } from "../src/core/i18n/i18n";
|
||||
import { type StoryContext } from "storybook/internal/csf";
|
||||
import { DragDropProvider } from "@dnd-kit/react";
|
||||
import { PointerActivationConstraints, PointerSensor } from "@dnd-kit/dom";
|
||||
|
||||
export const globalTypes = {
|
||||
theme: {
|
||||
@@ -47,7 +50,7 @@ export const globalTypes = {
|
||||
icon: "component",
|
||||
title: "Event layout",
|
||||
items: [
|
||||
{ title: "Group", value: "group" },
|
||||
{ title: "Modern", value: "group" },
|
||||
{ title: "Bubble", value: "bubble" },
|
||||
{ title: "IRC", value: "irc" },
|
||||
],
|
||||
@@ -65,30 +68,70 @@ export const globalTypes = {
|
||||
],
|
||||
},
|
||||
},
|
||||
rootCss: {
|
||||
name: "Root CSS",
|
||||
description: "Global root CSS for component previews",
|
||||
toolbar: {
|
||||
icon: "paintbrush",
|
||||
title: "Root CSS",
|
||||
items: [
|
||||
{ title: "Default", value: "storybook" },
|
||||
{ title: "Element Web", value: "app-web" },
|
||||
],
|
||||
},
|
||||
},
|
||||
initialGlobals: {
|
||||
theme: "system",
|
||||
theme: "light",
|
||||
language: "en",
|
||||
eventLayout: "group",
|
||||
eventDensity: "default",
|
||||
rootCss: "storybook",
|
||||
},
|
||||
} satisfies ArgTypes;
|
||||
|
||||
const allThemesClasses = globalTypes.theme.toolbar.items.map(({ value }) => `cpd-theme-${value}`);
|
||||
|
||||
const RootCssSwitcher: React.FC<{
|
||||
rootCss: string;
|
||||
}> = ({ rootCss }) => {
|
||||
useLayoutEffect(() => {
|
||||
if (rootCss === "app-web") {
|
||||
document.documentElement.dataset.storybookRootCss = rootCss;
|
||||
} else {
|
||||
delete document.documentElement.dataset.storybookRootCss;
|
||||
}
|
||||
|
||||
return () => {
|
||||
delete document.documentElement.dataset.storybookRootCss;
|
||||
};
|
||||
}, [rootCss]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const ThemeSwitcher: React.FC<{
|
||||
theme: string;
|
||||
}> = ({ theme }) => {
|
||||
useLayoutEffect(() => {
|
||||
document.documentElement.classList.remove(...allThemesClasses);
|
||||
document.body.classList.remove(...allThemesClasses);
|
||||
if (theme !== "system") {
|
||||
document.documentElement.classList.add(`cpd-theme-${theme}`);
|
||||
document.body.classList.add(`cpd-theme-${theme}`);
|
||||
}
|
||||
return () => document.documentElement.classList.remove(...allThemesClasses);
|
||||
return () => document.body.classList.remove(...allThemesClasses);
|
||||
}, [theme]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const withRootCss: Decorator = (Story, context) => {
|
||||
return (
|
||||
<>
|
||||
<RootCssSwitcher rootCss={context.globals.rootCss} />
|
||||
<Story />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const withThemeProvider: Decorator = (Story, context) => {
|
||||
return (
|
||||
<>
|
||||
@@ -131,15 +174,44 @@ const withEventPresentationProvider: Decorator = (Story, context) => {
|
||||
);
|
||||
};
|
||||
|
||||
const preview = {
|
||||
/**
|
||||
* Wrap all stories in a DragDropProvider that excludes the Accessibility plugin.
|
||||
* dnd-kit's Accessibility plugin adds aria attributes (tabindex, aria-pressed, etc.)
|
||||
* that conflict with the existing ARIA roles used in the room list components.
|
||||
*/
|
||||
const withDragDropProvider: Decorator = (Story) => {
|
||||
return (
|
||||
<DragDropProvider
|
||||
sensors={[
|
||||
// By default, the PointerSensor activates dragging immediately on pointer down, which interferes with keyboard navigation.
|
||||
// So we start dragging after the pointer has moved by 5 pixels, to allow for click without dragging
|
||||
PointerSensor.configure({
|
||||
activationConstraints: [new PointerActivationConstraints.Distance({ value: 5 })],
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Story />
|
||||
</DragDropProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
tags: ["autodocs", "snapshot"],
|
||||
initialGlobals: {
|
||||
theme: "system",
|
||||
rootCss: "storybook",
|
||||
theme: "light",
|
||||
language: "en",
|
||||
eventLayout: "group",
|
||||
eventDensity: "default",
|
||||
},
|
||||
decorators: [withThemeProvider, withEventPresentationProvider, withTooltipProvider, withI18nProvider],
|
||||
decorators: [
|
||||
withRootCss,
|
||||
withThemeProvider,
|
||||
withEventPresentationProvider,
|
||||
withTooltipProvider,
|
||||
withI18nProvider,
|
||||
withDragDropProvider,
|
||||
],
|
||||
parameters: {
|
||||
options: {
|
||||
storySort: {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Storybook `play` helper that waits for every CSS `background-image` inside the
|
||||
* rendered story to finish decoding before the visual-regression snapshot is taken.
|
||||
*
|
||||
* Components such as `LinkPreview` render their thumbnails as CSS `background-image`s
|
||||
* rather than `<img>` elements, so there is no load event for the snapshot machinery
|
||||
* to await. A larger image (e.g. the tall test image) can therefore still be decoding
|
||||
* when the screenshot is captured, producing a non-deterministic placeholder frame.
|
||||
* Decoding the images up-front populates the browser cache so the background paints
|
||||
* synchronously on the next frame.
|
||||
*/
|
||||
export async function waitForBackgroundImages(root: HTMLElement): Promise<void> {
|
||||
const urls = new Set<string>();
|
||||
for (const el of root.querySelectorAll<HTMLElement>("*")) {
|
||||
const match = /url\(["']?(.+?)["']?\)/.exec(getComputedStyle(el).backgroundImage);
|
||||
if (match) urls.add(match[1]);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
[...urls].map(async (src) => {
|
||||
const img = new Image();
|
||||
img.src = src;
|
||||
try {
|
||||
await img.decode();
|
||||
} catch {
|
||||
// Ignore images that fail to decode; the snapshot captures whatever renders.
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -40,6 +40,21 @@ or in CSS file:
|
||||
@import url("@element-hq/web-shared-components");
|
||||
```
|
||||
|
||||
### Sub-path Imports
|
||||
|
||||
Callers running outside the browser DOM (e.g. inside an `AudioWorkletGlobalScope`
|
||||
or a worker) can pull in the small standalone `numbers` utility bundle without
|
||||
loading the rest of the package bundle, which transitively imports React,
|
||||
dnd-kit, and other code that touches `window` / `document`:
|
||||
|
||||
```javascript
|
||||
import { percentageOf, percentageWithin } from "@element-hq/web-shared-components/numbers";
|
||||
```
|
||||
|
||||
The sub-path exposes the same functions listed under [Formatting](#formatting)
|
||||
and ships as its own ES/CJS bundle in `dist/numbers.{js,umd.cjs}`. Prefer the
|
||||
main package entry for everything else.
|
||||
|
||||
### Using Components
|
||||
|
||||
There are two kinds of components in this library:
|
||||
|
||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |