Merge branch 'master' into madlittlemods/686-682-local-friendly-development-and-commonjs

Conflicts:
	package.json
	scripts/sdk/base-manifest.json
	scripts/sdk/build.sh
This commit is contained in:
Eric Eastwood 2022-04-19 17:19:13 -05:00
commit 12d6447b06
73 changed files with 1263 additions and 363 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ target
lib
*.tar.gz
.eslintcache
.tmp

View file

@ -47,7 +47,8 @@ const assetPaths = {
wasmBundle: olmJsPath
}
};
import "hydrogen-view-sdk/style.css";
import "hydrogen-view-sdk/theme-element-light.css";
// OR import "hydrogen-view-sdk/theme-element-dark.css";
async function main() {
const app = document.querySelector<HTMLDivElement>('#app')!

View file

@ -1,6 +1,6 @@
{
"name": "hydrogen-web",
"version": "0.2.26",
"version": "0.2.28",
"description": "A javascript matrix client prototype, trying to minize RAM usage by offloading as much as possible to IndexedDB",
"directories": {
"doc": "doc"
@ -10,7 +10,7 @@
"lint-ts": "eslint src/ -c .ts-eslintrc.js --ext .ts",
"lint-ci": "eslint src/",
"test": "impunity --entry-point src/platform/web/main.js src/platform/web/Platform.js --force-esm-dirs lib/ src/ --root-dir src/",
"test:postcss": "impunity --entry-point scripts/postcss/test.js ",
"test:postcss": "impunity --entry-point scripts/postcss/tests/css-compile-variables.test.js scripts/postcss/tests/css-url-to-variables.test.js",
"test:sdk": "cd ./scripts/sdk/test/ && yarn --no-lockfile && node test-sdk-in-esm-vite-build-env.js && node test-sdk-in-commonjs-env.js",
"start": "vite --port 3000",
"build": "vite build",
@ -45,6 +45,7 @@
"node-html-parser": "^4.0.0",
"postcss-css-variables": "^0.18.0",
"postcss-flexbugs-fixes": "^5.0.2",
"postcss-value-parser": "^4.2.0",
"regenerator-runtime": "^0.13.7",
"text-encoding": "^0.7.0",
"typescript": "^4.3.5",
@ -56,7 +57,6 @@
"another-json": "^0.2.0",
"base64-arraybuffer": "^0.2.0",
"dompurify": "^2.3.0",
"off-color": "^2.0.0",
"postcss-value-parser": "^4.2.0"
"off-color": "^2.0.0"
}
}

View file

@ -0,0 +1,254 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const path = require('path');
async function readCSSSource(location) {
const fs = require("fs").promises;
const path = require("path");
const resolvedLocation = path.resolve(__dirname, "../../", `${location}/theme.css`);
const data = await fs.readFile(resolvedLocation);
return data;
}
function getRootSectionWithVariables(variables) {
return `:root{\n${Object.entries(variables).reduce((acc, [key, value]) => acc + `--${key}: ${value};\n`, "")} }\n\n`;
}
function appendVariablesToCSS(variables, cssSource) {
return cssSource + getRootSectionWithVariables(variables);
}
function parseBundle(bundle) {
const chunkMap = new Map();
const assetMap = new Map();
let runtimeThemeChunk;
for (const [fileName, info] of Object.entries(bundle)) {
if (!fileName.endsWith(".css")) {
continue;
}
if (info.type === "asset") {
/**
* So this is the css assetInfo that contains the asset hashed file name.
* We'll store it in a separate map indexed via fileName (unhashed) to avoid
* searching through the bundle array later.
*/
assetMap.set(info.name, info);
continue;
}
if (info.facadeModuleId?.includes("type=runtime")) {
/**
* We have a separate field in manifest.source just for the runtime theme,
* so store this separately.
*/
runtimeThemeChunk = info;
continue;
}
const location = info.facadeModuleId?.match(/(.+)\/.+\.css/)?.[1];
if (!location) {
throw new Error("Cannot find location of css chunk!");
}
const array = chunkMap.get(location);
if (!array) {
chunkMap.set(location, [info]);
}
else {
array.push(info);
}
}
return { chunkMap, assetMap, runtimeThemeChunk };
}
module.exports = function buildThemes(options) {
let manifest, variants, defaultDark, defaultLight;
let isDevelopment = false;
const virtualModuleId = '@theme/'
const resolvedVirtualModuleId = '\0' + virtualModuleId;
return {
name: "build-themes",
enforce: "pre",
configResolved(config) {
if (config.command === "serve") {
isDevelopment = true;
}
},
async buildStart() {
if (isDevelopment) { return; }
const { themeConfig } = options;
for (const [name, location] of Object.entries(themeConfig.themes)) {
manifest = require(`${location}/manifest.json`);
variants = manifest.values.variants;
for (const [variant, details] of Object.entries(variants)) {
const fileName = `theme-${name}-${variant}.css`;
if (name === themeConfig.default && details.default) {
// This is the default theme, stash the file name for later
if (details.dark) {
defaultDark = fileName;
}
else {
defaultLight = fileName;
}
}
// emit the css as built theme bundle
this.emitFile({
type: "chunk",
id: `${location}/theme.css?variant=${variant}${details.dark? "&dark=true": ""}`,
fileName,
});
}
// emit the css as runtime theme bundle
this.emitFile({
type: "chunk",
id: `${location}/theme.css?type=runtime`,
fileName: `theme-${name}-runtime.css`,
});
}
},
resolveId(id) {
if (id.startsWith(virtualModuleId)) {
return '\0' + id;
}
},
async load(id) {
if (isDevelopment) {
/**
* To load the theme during dev, we need to take a different approach because emitFile is not supported in dev.
* We solve this by resolving virtual file "@theme/name/variant" into the necessary css import.
* This virtual file import is removed when hydrogen is built (see transform hook).
*/
if (id.startsWith(resolvedVirtualModuleId)) {
let [theme, variant, file] = id.substr(resolvedVirtualModuleId.length).split("/");
if (theme === "default") {
theme = options.themeConfig.default;
}
const location = options.themeConfig.themes[theme];
const manifest = require(`${location}/manifest.json`);
const variants = manifest.values.variants;
if (!variant || variant === "default") {
// choose the first default variant for now
// this will need to support light/dark variants as well
variant = Object.keys(variants).find(variantName => variants[variantName].default);
}
if (!file) {
file = "index.js";
}
switch (file) {
case "index.js": {
const isDark = variants[variant].dark;
return `import "${path.resolve(`${location}/theme.css`)}${isDark? "?dark=true": ""}";` +
`import "@theme/${theme}/${variant}/variables.css"`;
}
case "variables.css": {
const variables = variants[variant].variables;
const css = getRootSectionWithVariables(variables);
return css;
}
}
}
}
else {
const result = id.match(/(.+)\/theme.css\?variant=([^&]+)/);
if (result) {
const [, location, variant] = result;
const cssSource = await readCSSSource(location);
const config = variants[variant];
return appendVariablesToCSS(config.variables, cssSource);
}
return null;
}
},
transform(code, id) {
if (isDevelopment) {
return;
}
/**
* Removes develop-only script tag; this cannot be done in transformIndexHtml hook because
* by the time that hook runs, the import is added to the bundled js file which would
* result in a runtime error.
*/
const devScriptTag =
/<script type="module"> import "@theme\/.+"; <\/script>/;
if (id.endsWith("index.html")) {
const htmlWithoutDevScript = code.replace(devScriptTag, "");
return htmlWithoutDevScript;
}
},
transformIndexHtml(_, ctx) {
if (isDevelopment) {
// Don't add default stylesheets to index.html on dev
return;
}
let darkThemeLocation, lightThemeLocation;
for (const [, bundle] of Object.entries(ctx.bundle)) {
if (bundle.name === defaultDark) {
darkThemeLocation = bundle.fileName;
}
if (bundle.name === defaultLight) {
lightThemeLocation = bundle.fileName;
}
}
return [
{
tag: "link",
attrs: {
rel: "stylesheet",
type: "text/css",
media: "(prefers-color-scheme: dark)",
href: `./${darkThemeLocation}`,
}
},
{
tag: "link",
attrs: {
rel: "stylesheet",
type: "text/css",
media: "(prefers-color-scheme: light)",
href: `./${lightThemeLocation}`,
}
},
];
},
generateBundle(_, bundle) {
const { assetMap, chunkMap, runtimeThemeChunk } = parseBundle(bundle);
for (const [location, chunkArray] of chunkMap) {
const manifest = require(`${location}/manifest.json`);
const compiledVariables = options.compiledVariables.get(location);
const derivedVariables = compiledVariables["derived-variables"];
const icon = compiledVariables["icon"];
manifest.source = {
"built-asset": chunkArray.map(chunk => assetMap.get(chunk.fileName).fileName),
"runtime-asset": assetMap.get(runtimeThemeChunk.fileName).fileName,
"derived-variables": derivedVariables,
"icon": icon
};
const name = `theme-${manifest.name}.json`;
this.emitFile({
type: "asset",
name,
source: JSON.stringify(manifest),
});
}
},
}
}

View file

@ -16,8 +16,17 @@ limitations under the License.
const offColor = require("off-color").offColor;
module.exports.derive = function (value, operation, argument) {
module.exports.derive = function (value, operation, argument, isDark) {
const argumentAsNumber = parseInt(argument);
if (isDark) {
// For dark themes, invert the operation
if (operation === 'darker') {
operation = "lighter";
}
else if (operation === 'lighter') {
operation = "darker";
}
}
switch (operation) {
case "darker": {
const newColorString = offColor(value).darken(argumentAsNumber / 100).hex();

View file

@ -33,6 +33,7 @@ const valueParser = require("postcss-value-parser");
let aliasMap;
let resolvedMap;
let baseVariables;
let isDark;
function getValueFromAlias(alias) {
const derivedVariable = aliasMap.get(alias);
@ -43,17 +44,32 @@ function parseDeclarationValue(value) {
const parsed = valueParser(value);
const variables = [];
parsed.walk(node => {
if (node.type !== "function" && node.value !== "var") {
if (node.type !== "function") {
return;
}
const variable = node.nodes[0];
variables.push(variable.value);
switch (node.value) {
case "var": {
const variable = node.nodes[0];
variables.push(variable.value);
break;
}
case "url": {
const url = node.nodes[0].value;
// resolve url with some absolute url so that we get the query params without using regex
const params = new URL(url, "file://foo/bar/").searchParams;
const primary = params.get("primary");
const secondary = params.get("secondary");
if (primary) { variables.push(primary); }
if (secondary) { variables.push(secondary); }
break;
}
}
});
return variables;
}
function resolveDerivedVariable(decl, derive) {
const RE_VARIABLE_VALUE = /--((.+)--(.+)-(.+))/;
const RE_VARIABLE_VALUE = /(?:--)?((.+)--(.+)-(.+))/;
const variableCollection = parseDeclarationValue(decl.value);
for (const variable of variableCollection) {
const matches = variable.match(RE_VARIABLE_VALUE);
@ -63,7 +79,7 @@ function resolveDerivedVariable(decl, derive) {
if (!value) {
throw new Error(`Cannot derive from ${baseVariable} because it is neither defined in config nor is it an alias!`);
}
const derivedValue = derive(value, operation, argument);
const derivedValue = derive(value, operation, argument, isDark);
resolvedMap.set(wholeVariable, derivedValue);
}
}
@ -94,25 +110,44 @@ function addResolvedVariablesToRootSelector(root, {Rule, Declaration}) {
root.append(newRule);
}
function populateMapWithDerivedVariables(map, cssFileLocation) {
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
const derivedVariables = [
...([...resolvedMap.keys()].filter(v => !aliasMap.has(v))),
...([...aliasMap.entries()].map(([alias, variable]) => `${alias}=${variable}`))
];
map.set(location, { "derived-variables": derivedVariables });
}
/**
* @callback derive
* @param {string} value - The base value on which an operation is applied
* @param {string} operation - The operation to be applied (eg: darker, lighter...)
* @param {string} argument - The argument for this operation
* @param {boolean} isDark - Indicates whether this theme is dark
*/
/**
*
* @param {Object} opts - Options for the plugin
* @param {derive} opts.derive - The callback which contains the logic for resolving derived variables
* @param {Map} opts.compiledVariables - A map that stores derived variables so that manifest source sections can be produced
*/
module.exports = (opts = {}) => {
aliasMap = new Map();
resolvedMap = new Map();
baseVariables = new Map();
isDark = false;
return {
postcssPlugin: "postcss-compile-variables",
Once(root, {Rule, Declaration}) {
Once(root, {Rule, Declaration, result}) {
const cssFileLocation = root.source.input.from;
if (cssFileLocation.includes("type=runtime")) {
// If this is a runtime theme, don't derive variables.
return;
}
isDark = cssFileLocation.includes("dark=true");
/*
Go through the CSS file once to extract all aliases and base variables.
We use these when resolving derived variables later.
@ -120,6 +155,21 @@ module.exports = (opts = {}) => {
root.walkDecls(decl => extract(decl));
root.walkDecls(decl => resolveDerivedVariable(decl, opts.derive));
addResolvedVariablesToRootSelector(root, {Rule, Declaration});
if (opts.compiledVariables){
populateMapWithDerivedVariables(opts.compiledVariables, cssFileLocation);
}
// Also produce a mapping from alias to completely resolved color
const resolvedAliasMap = new Map();
aliasMap.forEach((value, key) => {
resolvedAliasMap.set(key, resolvedMap.get(value));
});
// Publish the base-variables, derived-variables and resolved aliases to the other postcss-plugins
const combinedMap = new Map([...baseVariables, ...resolvedMap, ...resolvedAliasMap]);
result.messages.push({
type: "resolved-variable-map",
plugin: "postcss-compile-variables",
colorMap: combinedMap,
});
},
};
};

View file

@ -0,0 +1,93 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const valueParser = require("postcss-value-parser");
const resolve = require("path").resolve;
let cssPath;
function colorsFromURL(url, colorMap) {
const params = new URL(`file://${url}`).searchParams;
const primary = params.get("primary");
if (!primary) {
return null;
}
const secondary = params.get("secondary");
const primaryColor = colorMap.get(primary);
const secondaryColor = colorMap.get(secondary);
if (!primaryColor) {
throw new Error(`Variable ${primary} not found in resolved color variables!`);
}
if (secondary && !secondaryColor) {
throw new Error(`Variable ${secondary} not found in resolved color variables!`);
}
return [primaryColor, secondaryColor];
}
function processURL(decl, replacer, colorMap) {
const value = decl.value;
const parsed = valueParser(value);
parsed.walk(node => {
if (node.type !== "function" || node.value !== "url") {
return;
}
const urlStringNode = node.nodes[0];
const oldURL = urlStringNode.value;
const oldURLAbsolute = resolve(cssPath, oldURL);
const colors = colorsFromURL(oldURLAbsolute, colorMap);
if (!colors) {
// If no primary color is provided via url params, then this url need not be handled.
return;
}
const newURL = replacer(oldURLAbsolute.replace(/\?.+/, ""), ...colors);
if (!newURL) {
throw new Error("Replacer failed to produce a replacement URL!");
}
urlStringNode.value = newURL;
});
decl.assign({prop: decl.prop, value: parsed.toString()})
}
/* *
* @type {import('postcss').PluginCreator}
*/
module.exports = (opts = {}) => {
return {
postcssPlugin: "postcss-url-to-variable",
Once(root, {result}) {
const cssFileLocation = root.source.input.from;
if (cssFileLocation.includes("type=runtime")) {
// If this is a runtime theme, don't process urls.
return;
}
/*
postcss-compile-variables should have sent the list of resolved colours down via results
*/
const {colorMap} = result.messages.find(m => m.type === "resolved-variable-map");
if (!colorMap) {
throw new Error("Postcss results do not contain resolved colors!");
}
/*
Go through each declaration and if it contains an URL, replace the url with the result
of running replacer(url)
*/
cssPath = root.source?.input.file.replace(/[^/]*$/, "");
root.walkDecls(decl => processURL(decl, opts.replacer, colorMap));
},
};
};
module.exports.postcss = true;

View file

@ -0,0 +1,85 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const valueParser = require("postcss-value-parser");
/**
* This plugin extracts content inside url() into css variables and adds the variables to the root section.
* This plugin is used in conjunction with css-url-processor plugin to colorize svg icons.
*/
let counter;
let urlVariables;
const idToPrepend = "icon-url";
function findAndReplaceUrl(decl) {
const value = decl.value;
const parsed = valueParser(value);
parsed.walk(node => {
if (node.type !== "function" || node.value !== "url") {
return;
}
const url = node.nodes[0].value;
if (!url.match(/\.svg\?primary=.+/)) {
return;
}
const variableName = `${idToPrepend}-${counter++}`;
urlVariables.set(variableName, url);
node.value = "var";
node.nodes = [{ type: "word", value: `--${variableName}` }];
});
decl.assign({prop: decl.prop, value: parsed.toString()})
}
function addResolvedVariablesToRootSelector(root, { Rule, Declaration }) {
const newRule = new Rule({ selector: ":root", source: root.source });
// Add derived css variables to :root
urlVariables.forEach((value, key) => {
const declaration = new Declaration({ prop: `--${key}`, value: `url("${value}")`});
newRule.append(declaration);
});
root.append(newRule);
}
function populateMapWithIcons(map, cssFileLocation) {
const location = cssFileLocation.match(/(.+)\/.+\.css/)?.[1];
const sharedObject = map.get(location);
sharedObject["icon"] = Object.fromEntries(urlVariables);
}
/* *
* @type {import('postcss').PluginCreator}
*/
module.exports = (opts = {}) => {
urlVariables = new Map();
counter = 0;
return {
postcssPlugin: "postcss-url-to-variable",
Once(root, { Rule, Declaration }) {
root.walkDecls(decl => findAndReplaceUrl(decl));
if (urlVariables.size) {
addResolvedVariablesToRootSelector(root, { Rule, Declaration });
}
if (opts.compiledVariables){
const cssFileLocation = root.source.input.from;
populateMapWithIcons(opts.compiledVariables, cssFileLocation);
}
},
};
};
module.exports.postcss = true;

View file

@ -0,0 +1,54 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const fs = require("fs");
const path = require("path");
const xxhash = require('xxhashjs');
function createHash(content) {
const hasher = new xxhash.h32(0);
hasher.update(content);
return hasher.digest();
}
/**
* Builds a new svg with the colors replaced and returns its location.
* @param {string} svgLocation The location of the input svg file
* @param {string} primaryColor Primary color for the new svg
* @param {string} secondaryColor Secondary color for the new svg
*/
module.exports.buildColorizedSVG = function (svgLocation, primaryColor, secondaryColor) {
const svgCode = fs.readFileSync(svgLocation, { encoding: "utf8"});
let coloredSVGCode = svgCode.replaceAll("#ff00ff", primaryColor);
coloredSVGCode = coloredSVGCode.replaceAll("#00ffff", secondaryColor);
if (svgCode === coloredSVGCode) {
throw new Error("svg-colorizer made no color replacements! The input svg should only contain colors #ff00ff (primary, case-sensitive) and #00ffff (secondary, case-sensitive).");
}
const fileName = svgLocation.match(/.+[/\\](.+\.svg)/)[1];
const outputName = `${fileName.substring(0, fileName.length - 4)}-${createHash(coloredSVGCode)}.svg`;
const outputPath = path.resolve(__dirname, "../../.tmp");
try {
fs.mkdirSync(outputPath);
}
catch (e) {
if (e.code !== "EEXIST") {
throw e;
}
}
const outputFile = `${outputPath}/${outputName}`;
fs.writeFileSync(outputFile, coloredSVGCode);
return outputFile;
}

View file

@ -0,0 +1,30 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const postcss = require("postcss");
module.exports.createTestRunner = function (plugin) {
return async function run(input, output, opts = {}, assert) {
let result = await postcss([plugin(opts)]).process(input, { from: undefined, });
assert.strictEqual(
result.css.replaceAll(/\s/g, ""),
output.replaceAll(/\s/g, "")
);
assert.strictEqual(result.warnings().length, 0);
};
}

View file

@ -16,17 +16,9 @@ limitations under the License.
const offColor = require("off-color").offColor;
const postcss = require("postcss");
const plugin = require("./css-compile-variables");
const derive = require("./color").derive;
async function run(input, output, opts = {}, assert) {
let result = await postcss([plugin({ ...opts, derive })]).process(input, { from: undefined, });
assert.strictEqual(
result.css.replaceAll(/\s/g, ""),
output.replaceAll(/\s/g, "")
);
assert.strictEqual(result.warnings().length, 0);
}
const plugin = require("../css-compile-variables");
const derive = require("../color").derive;
const run = require("./common").createTestRunner(plugin);
module.exports.tests = function tests() {
return {
@ -46,7 +38,7 @@ module.exports.tests = function tests() {
--foo-color--lighter-50: ${transformedColor.hex()};
}
`;
await run( inputCSS, outputCSS, {}, assert);
await run( inputCSS, outputCSS, {derive}, assert);
},
"derived variables work with alias": async (assert) => {
@ -66,7 +58,7 @@ module.exports.tests = function tests() {
--my-alias--lighter-15: ${aliasLighter};
}
`;
await run(inputCSS, outputCSS, { }, assert);
await run(inputCSS, outputCSS, {derive}, assert);
},
"derived variable throws if base not present in config": async (assert) => {
@ -94,8 +86,9 @@ module.exports.tests = function tests() {
--foo-color--darker-20: ${transformedColor2.hex()};
}
`;
await run( inputCSS, outputCSS, { }, assert);
await run( inputCSS, outputCSS, {derive}, assert);
},
"multiple aliased-derived variable in single declaration is parsed correctly": async (assert) => {
const inputCSS = `
:root {
@ -115,7 +108,49 @@ module.exports.tests = function tests() {
--my-alias--darker-20: ${transformedColor2.hex()};
}
`;
await run( inputCSS, outputCSS, { }, assert);
await run( inputCSS, outputCSS, {derive}, assert);
},
"compiledVariables map is populated": async (assert) => {
const compiledVariables = new Map();
const inputCSS = `
:root {
--icon-color: #fff;
}
div {
background: var(--icon-color--darker-20);
--my-alias: var(--icon-color--darker-20);
color: var(--my-alias--lighter-15);
}`;
await postcss([plugin({ derive, compiledVariables })]).process(inputCSS, { from: "/foo/bar/test.css", });
const actualArray = compiledVariables.get("/foo/bar")["derived-variables"];
const expectedArray = ["icon-color--darker-20", "my-alias=icon-color--darker-20", "my-alias--lighter-15"];
assert.deepStrictEqual(actualArray.sort(), expectedArray.sort());
},
"derived variable are supported in urls": async (assert) => {
const inputCSS = `
:root {
--foo-color: #ff0;
}
div {
background-color: var(--foo-color--lighter-50);
background: url("./foo/bar/icon.svg?primary=foo-color--darker-5");
}
a {
background: url("foo/bar/icon.svg");
}`;
const transformedColorLighter = offColor("#ff0").lighten(0.5);
const transformedColorDarker = offColor("#ff0").darken(0.05);
const outputCSS =
inputCSS +
`
:root {
--foo-color--lighter-50: ${transformedColorLighter.hex()};
--foo-color--darker-5: ${transformedColorDarker.hex()};
}
`;
await run( inputCSS, outputCSS, {derive}, assert);
}
};
};

View file

@ -0,0 +1,71 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const plugin = require("../css-url-to-variables");
const run = require("./common").createTestRunner(plugin);
const postcss = require("postcss");
module.exports.tests = function tests() {
return {
"url is replaced with variable": async (assert) => {
const inputCSS = `div {
background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20");
}
button {
background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}`;
const outputCSS =
`div {
background: no-repeat center/80% var(--icon-url-0);
}
button {
background: var(--icon-url-1);
}`+
`
:root {
--icon-url-0: url("../img/image.svg?primary=main-color--darker-20");
--icon-url-1: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}
`;
await run(inputCSS, outputCSS, { }, assert);
},
"non svg urls without query params are not replaced": async (assert) => {
const inputCSS = `div {
background: no-repeat url("./img/foo/bar/image.png");
}`;
await run(inputCSS, inputCSS, {}, assert);
},
"map is populated with icons": async (assert) => {
const compiledVariables = new Map();
compiledVariables.set("/foo/bar", { "derived-variables": ["background-color--darker-20", "accent-color--lighter-15"] });
const inputCSS = `div {
background: no-repeat center/80% url("../img/image.svg?primary=main-color--darker-20");
}
button {
background: url("/home/foo/bar/cool.svg?primary=blue&secondary=green");
}`;
const expectedObject = {
"icon-url-0": "../img/image.svg?primary=main-color--darker-20",
"icon-url-1": "/home/foo/bar/cool.svg?primary=blue&secondary=green",
};
await postcss([plugin({compiledVariables})]).process(inputCSS, { from: "/foo/bar/test.css", });
const sharedVariable = compiledVariables.get("/foo/bar");
assert.deepEqual(["background-color--darker-20", "accent-color--lighter-15"], sharedVariable["derived-variables"]);
assert.deepEqual(expectedObject, sharedVariable["icon"]);
}
};
};

View file

@ -1,7 +1,7 @@
{
"name": "hydrogen-view-sdk",
"description": "Embeddable matrix client library, including view components",
"version": "0.0.9",
"version": "0.0.10",
"main": "./lib-build/hydrogen.cjs.js",
"exports": {
".": {
@ -9,7 +9,9 @@
"require": "./lib-build/hydrogen.cjs.js"
},
"./paths/vite": "./paths/vite.js",
"./style.css": "./asset-build/assets/index.css",
"./style.css": "./asset-build/assets/theme-element-light.css",
"./theme-element-light.css": "./asset-build/assets/theme-element-light.css",
"./theme-element-dark.css": "./asset-build/assets/theme-element-dark.css",
"./main.js": "./asset-build/assets/main.js",
"./download-sandbox.html": "./asset-build/assets/download-sandbox.html",
"./assets/*": "./asset-build/assets/*"

View file

@ -58,6 +58,14 @@ export class ViewModel<O extends Options = Options> extends EventEmitter<{change
return this._options[name];
}
observeNavigation(type: string, onChange: (value: string | true | undefined, type: string) => void) {
const segmentObservable = this.navigation.observe(type);
const unsubscribe = segmentObservable.subscribe((value: string | true | undefined) => {
onChange(value, type);
})
this.track(unsubscribe);
}
track<D extends Disposable>(disposable: D): D {
if (!this.disposables) {
this.disposables = new Disposables();

View file

@ -18,17 +18,20 @@ limitations under the License.
import {TimelineViewModel} from "./timeline/TimelineViewModel.js";
import {ComposerViewModel} from "./ComposerViewModel.js"
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
import {tilesCreator} from "./timeline/tilesCreator.js";
import {ViewModel} from "../../ViewModel";
import {imageToInfo} from "../common.js";
// TODO: remove fallback so default isn't included in bundle for SDK users that have their custom tileClassForEntry
// this is a breaking SDK change though to make this option mandatory
import {tileClassForEntry as defaultTileClassForEntry} from "./timeline/tiles/index";
export class RoomViewModel extends ViewModel {
constructor(options) {
super(options);
const {room} = options;
const {room, tileClassForEntry} = options;
this._room = room;
this._timelineVM = null;
this._tilesCreator = null;
this._tileClassForEntry = tileClassForEntry ?? defaultTileClassForEntry;
this._tileOptions = undefined;
this._onRoomChange = this._onRoomChange.bind(this);
this._timelineError = null;
this._sendError = null;
@ -46,12 +49,13 @@ export class RoomViewModel extends ViewModel {
this._room.on("change", this._onRoomChange);
try {
const timeline = await this._room.openTimeline();
this._tilesCreator = tilesCreator(this.childOptions({
this._tileOptions = this.childOptions({
roomVM: this,
timeline,
}));
tileClassForEntry: this._tileClassForEntry,
});
this._timelineVM = this.track(new TimelineViewModel(this.childOptions({
tilesCreator: this._tilesCreator,
tileOptions: this._tileOptions,
timeline,
})));
this.emitChange("timelineViewModel");
@ -161,7 +165,12 @@ export class RoomViewModel extends ViewModel {
}
_createTile(entry) {
return this._tilesCreator(entry);
if (this._tileOptions) {
const Tile = this._tileOptions.tileClassForEntry(entry);
if (Tile) {
return new Tile(entry, this._tileOptions);
}
}
}
async _sendMessage(message, replyingTo) {

View file

@ -222,7 +222,7 @@ export function tests() {
};
const tiles = new MappedList(timeline.entries, entry => {
if (entry.eventType === "m.room.message") {
return new BaseMessageTile({entry, roomVM: {room}, timeline, platform: {logger}});
return new BaseMessageTile(entry, {roomVM: {room}, timeline, platform: {logger}});
}
return null;
}, (tile, params, entry) => tile?.updateEntry(entry, params, function () {}));

View file

@ -18,20 +18,27 @@ import {BaseObservableList} from "../../../../observable/list/BaseObservableList
import {sortedIndex} from "../../../../utils/sortedIndex";
// maps 1..n entries to 0..1 tile. Entries are what is stored in the timeline, either an event or fragmentboundary
// for now, tileCreator should be stable in whether it returns a tile or not.
// for now, tileClassForEntry should be stable in whether it returns a tile or not.
// e.g. the decision to create a tile or not should be based on properties
// not updated later on (e.g. event type)
// also see big comment in onUpdate
export class TilesCollection extends BaseObservableList {
constructor(entries, tileCreator) {
constructor(entries, tileOptions) {
super();
this._entries = entries;
this._tiles = null;
this._entrySubscription = null;
this._tileCreator = tileCreator;
this._tileOptions = tileOptions;
this._emitSpontanousUpdate = this._emitSpontanousUpdate.bind(this);
}
_createTile(entry) {
const Tile = this._tileOptions.tileClassForEntry(entry);
if (Tile) {
return new Tile(entry, this._tileOptions);
}
}
_emitSpontanousUpdate(tile, params) {
const entry = tile.lowerEntry;
const tileIdx = this._findTileIdx(entry);
@ -48,7 +55,7 @@ export class TilesCollection extends BaseObservableList {
let currentTile = null;
for (let entry of this._entries) {
if (!currentTile || !currentTile.tryIncludeEntry(entry)) {
currentTile = this._tileCreator(entry);
currentTile = this._createTile(entry);
if (currentTile) {
this._tiles.push(currentTile);
}
@ -121,7 +128,7 @@ export class TilesCollection extends BaseObservableList {
return;
}
const newTile = this._tileCreator(entry);
const newTile = this._createTile(entry);
if (newTile) {
if (prevTile) {
prevTile.updateNextSibling(newTile);
@ -150,9 +157,9 @@ export class TilesCollection extends BaseObservableList {
const tileIdx = this._findTileIdx(entry);
const tile = this._findTileAtIdx(entry, tileIdx);
if (tile) {
const action = tile.updateEntry(entry, params, this._tileCreator);
const action = tile.updateEntry(entry, params);
if (action.shouldReplace) {
const newTile = this._tileCreator(entry);
const newTile = this._createTile(entry);
if (newTile) {
this._replaceTile(tileIdx, tile, newTile, action.updateParams);
newTile.setUpdateEmit(this._emitSpontanousUpdate);
@ -303,7 +310,10 @@ export function tests() {
}
}
const entries = new ObservableArray([{n: 5}, {n: 10}]);
const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry));
const tileOptions = {
tileClassForEntry: () => UpdateOnSiblingTile,
};
const tiles = new TilesCollection(entries, tileOptions);
let receivedAdd = false;
tiles.subscribe({
onAdd(idx, tile) {
@ -326,7 +336,10 @@ export function tests() {
}
}
const entries = new ObservableArray([{n: 5}, {n: 10}, {n: 15}]);
const tiles = new TilesCollection(entries, entry => new UpdateOnSiblingTile(entry));
const tileOptions = {
tileClassForEntry: () => UpdateOnSiblingTile,
};
const tiles = new TilesCollection(entries, tileOptions);
const events = [];
tiles.subscribe({
onUpdate(idx, tile) {

View file

@ -37,9 +37,9 @@ import {ViewModel} from "../../../ViewModel";
export class TimelineViewModel extends ViewModel {
constructor(options) {
super(options);
const {timeline, tilesCreator} = options;
const {timeline, tileOptions} = options;
this._timeline = this.track(timeline);
this._tiles = new TilesCollection(timeline.entries, tilesCreator);
this._tiles = new TilesCollection(timeline.entries, tileOptions);
this._startTile = null;
this._endTile = null;
this._topLoadingPromise = null;

View file

@ -21,8 +21,8 @@ const MAX_HEIGHT = 300;
const MAX_WIDTH = 400;
export class BaseMediaTile extends BaseMessageTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._decryptedThumbnail = null;
this._decryptedFile = null;
this._isVisible = false;

View file

@ -19,8 +19,8 @@ import {ReactionsViewModel} from "../ReactionsViewModel.js";
import {getIdentifierColorNumber, avatarInitials, getAvatarHttpUrl} from "../../../../avatar";
export class BaseMessageTile extends SimpleTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
this._isContinuation = false;
this._reactions = null;
@ -28,7 +28,7 @@ export class BaseMessageTile extends SimpleTile {
if (this._entry.annotations || this._entry.pendingAnnotations) {
this._updateReactions();
}
this._updateReplyTileIfNeeded(options.tilesCreator, undefined);
this._updateReplyTileIfNeeded(undefined);
}
notifyVisible() {
@ -122,23 +122,27 @@ export class BaseMessageTile extends SimpleTile {
}
}
updateEntry(entry, param, tilesCreator) {
const action = super.updateEntry(entry, param, tilesCreator);
updateEntry(entry, param) {
const action = super.updateEntry(entry, param);
if (action.shouldUpdate) {
this._updateReactions();
}
this._updateReplyTileIfNeeded(tilesCreator, param);
this._updateReplyTileIfNeeded(param);
return action;
}
_updateReplyTileIfNeeded(tilesCreator, param) {
_updateReplyTileIfNeeded(param) {
const replyEntry = this._entry.contextEntry;
if (replyEntry) {
// this is an update to contextEntry used for replyPreview
const action = this._replyTile?.updateEntry(replyEntry, param, tilesCreator);
const action = this._replyTile?.updateEntry(replyEntry, param);
if (action?.shouldReplace || !this._replyTile) {
this.disposeTracked(this._replyTile);
this._replyTile = tilesCreator(replyEntry);
const tileClassForEntry = this._options.tileClassForEntry;
const ReplyTile = tileClassForEntry(replyEntry);
if (ReplyTile) {
this._replyTile = new ReplyTile(replyEntry, this._options);
}
}
if(action?.shouldUpdate) {
this._replyTile?.emitChange();

View file

@ -21,8 +21,8 @@ import {createEnum} from "../../../../../utils/enum";
export const BodyFormat = createEnum("Plain", "Html");
export class BaseTextTile extends BaseMessageTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._messageBody = null;
this._format = null
}

View file

@ -18,8 +18,8 @@ import {BaseTextTile} from "./BaseTextTile.js";
import {UpdateAction} from "../UpdateAction.js";
export class EncryptedEventTile extends BaseTextTile {
updateEntry(entry, params, tilesCreator) {
const parentResult = super.updateEntry(entry, params, tilesCreator);
updateEntry(entry, params) {
const parentResult = super.updateEntry(entry, params);
// event got decrypted, recreate the tile and replace this one with it
if (entry.eventType !== "m.room.encrypted") {
// the "shape" parameter trigger tile recreation in TimelineView

View file

@ -20,8 +20,8 @@ import {formatSize} from "../../../../../utils/formatSize";
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
export class FileTile extends BaseMessageTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._downloadError = null;
this._downloading = false;
}

View file

@ -18,8 +18,8 @@ import {SimpleTile} from "./SimpleTile.js";
import {UpdateAction} from "../UpdateAction.js";
export class GapTile extends SimpleTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._loading = false;
this._error = null;
this._isAtTop = true;
@ -81,8 +81,8 @@ export class GapTile extends SimpleTile {
this._siblingChanged = true;
}
updateEntry(entry, params, tilesCreator) {
super.updateEntry(entry, params, tilesCreator);
updateEntry(entry, params) {
super.updateEntry(entry, params);
if (!entry.isGap) {
return UpdateAction.Remove();
} else {
@ -125,7 +125,7 @@ export function tests() {
tile.updateEntry(newEntry);
}
};
const tile = new GapTile({entry: new FragmentBoundaryEntry(fragment, true), roomVM: {room}});
const tile = new GapTile(new FragmentBoundaryEntry(fragment, true), {roomVM: {room}});
await tile.fill();
await tile.fill();
await tile.fill();

View file

@ -18,8 +18,8 @@ limitations under the License.
import {BaseMediaTile} from "./BaseMediaTile.js";
export class ImageTile extends BaseMediaTile {
constructor(options) {
super(options);
constructor(entry, options) {
super(entry, options);
this._lightboxUrl = this.urlCreator.urlForSegments([
// ensure the right room is active if in grid view
this.navigation.segment("room", this._room.id),

View file

@ -66,23 +66,25 @@ export class RoomMemberTile extends SimpleTile {
export function tests() {
return {
"user removes display name": (assert) => {
const tile = new RoomMemberTile({
entry: {
const tile = new RoomMemberTile(
{
prevContent: {displayname: "foo", membership: "join"},
content: {membership: "join"},
stateKey: "foo@bar.com",
},
});
{}
);
assert.strictEqual(tile.announcement, "foo@bar.com removed their name (foo)");
},
"user without display name sets a new display name": (assert) => {
const tile = new RoomMemberTile({
entry: {
const tile = new RoomMemberTile(
{
prevContent: {membership: "join"},
content: {displayname: "foo", membership: "join" },
stateKey: "foo@bar.com",
},
});
{}
);
assert.strictEqual(tile.announcement, "foo@bar.com changed their name to foo");
},
};

View file

@ -19,9 +19,9 @@ import {ViewModel} from "../../../../ViewModel";
import {SendStatus} from "../../../../../matrix/room/sending/PendingEvent.js";
export class SimpleTile extends ViewModel {
constructor(options) {
constructor(entry, options) {
super(options);
this._entry = options.entry;
this._entry = entry;
}
// view model props for all subclasses
// hmmm, could also do instanceof ... ?

View file

@ -0,0 +1,94 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {GapTile} from "./GapTile.js";
import {TextTile} from "./TextTile.js";
import {RedactedTile} from "./RedactedTile.js";
import {ImageTile} from "./ImageTile.js";
import {VideoTile} from "./VideoTile.js";
import {FileTile} from "./FileTile.js";
import {LocationTile} from "./LocationTile.js";
import {RoomNameTile} from "./RoomNameTile.js";
import {RoomMemberTile} from "./RoomMemberTile.js";
import {EncryptedEventTile} from "./EncryptedEventTile.js";
import {EncryptionEnabledTile} from "./EncryptionEnabledTile.js";
import {MissingAttachmentTile} from "./MissingAttachmentTile.js";
import type {SimpleTile} from "./SimpleTile.js";
import type {Room} from "../../../../../matrix/room/Room";
import type {Timeline} from "../../../../../matrix/room/timeline/Timeline";
import type {FragmentBoundaryEntry} from "../../../../../matrix/room/timeline/entries/FragmentBoundaryEntry";
import type {EventEntry} from "../../../../../matrix/room/timeline/entries/EventEntry";
import type {PendingEventEntry} from "../../../../../matrix/room/timeline/entries/PendingEventEntry";
import type {Options as ViewModelOptions} from "../../../../ViewModel";
export type TimelineEntry = FragmentBoundaryEntry | EventEntry | PendingEventEntry;
export type TileClassForEntryFn = (entry: TimelineEntry) => TileConstructor | undefined;
export type Options = ViewModelOptions & {
room: Room,
timeline: Timeline
tileClassForEntry: TileClassForEntryFn;
};
export type TileConstructor = new (entry: TimelineEntry, options: Options) => SimpleTile;
export function tileClassForEntry(entry: TimelineEntry): TileConstructor | undefined {
if (entry.isGap) {
return GapTile;
} else if (entry.isPending && entry.pendingEvent.isMissingAttachments) {
return MissingAttachmentTile;
} else if (entry.eventType) {
switch (entry.eventType) {
case "m.room.message": {
if (entry.isRedacted) {
return RedactedTile;
}
const content = entry.content;
const msgtype = content && content.msgtype;
switch (msgtype) {
case "m.text":
case "m.notice":
case "m.emote":
return TextTile;
case "m.image":
return ImageTile;
case "m.video":
return VideoTile;
case "m.file":
return FileTile;
case "m.location":
return LocationTile;
default:
// unknown msgtype not rendered
return undefined;
}
}
case "m.room.name":
return RoomNameTile;
case "m.room.member":
return RoomMemberTile;
case "m.room.encrypted":
if (entry.isRedacted) {
return RedactedTile;
}
return EncryptedEventTile;
case "m.room.encryption":
return EncryptionEnabledTile;
default:
// unknown type not rendered
return undefined;
}
}
}

View file

@ -1,81 +0,0 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {GapTile} from "./tiles/GapTile.js";
import {TextTile} from "./tiles/TextTile.js";
import {RedactedTile} from "./tiles/RedactedTile.js";
import {ImageTile} from "./tiles/ImageTile.js";
import {VideoTile} from "./tiles/VideoTile.js";
import {FileTile} from "./tiles/FileTile.js";
import {LocationTile} from "./tiles/LocationTile.js";
import {RoomNameTile} from "./tiles/RoomNameTile.js";
import {RoomMemberTile} from "./tiles/RoomMemberTile.js";
import {EncryptedEventTile} from "./tiles/EncryptedEventTile.js";
import {EncryptionEnabledTile} from "./tiles/EncryptionEnabledTile.js";
import {MissingAttachmentTile} from "./tiles/MissingAttachmentTile.js";
export function tilesCreator(baseOptions) {
const tilesCreator = function tilesCreator(entry, emitUpdate) {
const options = Object.assign({entry, emitUpdate, tilesCreator}, baseOptions);
if (entry.isGap) {
return new GapTile(options);
} else if (entry.isPending && entry.pendingEvent.isMissingAttachments) {
return new MissingAttachmentTile(options);
} else if (entry.eventType) {
switch (entry.eventType) {
case "m.room.message": {
if (entry.isRedacted) {
return new RedactedTile(options);
}
const content = entry.content;
const msgtype = content && content.msgtype;
switch (msgtype) {
case "m.text":
case "m.notice":
case "m.emote":
return new TextTile(options);
case "m.image":
return new ImageTile(options);
case "m.video":
return new VideoTile(options);
case "m.file":
return new FileTile(options);
case "m.location":
return new LocationTile(options);
default:
// unknown msgtype not rendered
return null;
}
}
case "m.room.name":
return new RoomNameTile(options);
case "m.room.member":
return new RoomMemberTile(options);
case "m.room.encrypted":
if (entry.isRedacted) {
return new RedactedTile(options);
}
return new EncryptedEventTile(options);
case "m.room.encryption":
return new EncryptionEnabledTile(options);
default:
// unknown type not rendered
return null;
}
}
};
return tilesCreator;
}

View file

@ -2,8 +2,6 @@
<!-- this file contains all references to include in the SDK asset build (using vite.sdk-assets-config.js) -->
<html>
<head>
<link rel="stylesheet" type="text/css" href="./platform/web/ui/css/main.css">
<link rel="stylesheet" type="text/css" href="./platform/web/ui/css/themes/element/theme.css">
</head>
<body>
<script type="module">

View file

@ -26,7 +26,41 @@ export {SessionView} from "./platform/web/ui/session/SessionView.js";
export {RoomViewModel} from "./domain/session/room/RoomViewModel.js";
export {RoomView} from "./platform/web/ui/session/room/RoomView.js";
export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js";
export {tileClassForEntry} from "./domain/session/room/timeline/tiles/index";
export type {TimelineEntry, TileClassForEntryFn, Options, TileConstructor} from "./domain/session/room/timeline/tiles/index";
// export timeline tile view models
export {GapTile} from "./domain/session/room/timeline/tiles/GapTile.js";
export {TextTile} from "./domain/session/room/timeline/tiles/TextTile.js";
export {RedactedTile} from "./domain/session/room/timeline/tiles/RedactedTile.js";
export {ImageTile} from "./domain/session/room/timeline/tiles/ImageTile.js";
export {VideoTile} from "./domain/session/room/timeline/tiles/VideoTile.js";
export {FileTile} from "./domain/session/room/timeline/tiles/FileTile.js";
export {LocationTile} from "./domain/session/room/timeline/tiles/LocationTile.js";
export {RoomNameTile} from "./domain/session/room/timeline/tiles/RoomNameTile.js";
export {RoomMemberTile} from "./domain/session/room/timeline/tiles/RoomMemberTile.js";
export {EncryptedEventTile} from "./domain/session/room/timeline/tiles/EncryptedEventTile.js";
export {EncryptionEnabledTile} from "./domain/session/room/timeline/tiles/EncryptionEnabledTile.js";
export {MissingAttachmentTile} from "./domain/session/room/timeline/tiles/MissingAttachmentTile.js";
export {SimpleTile} from "./domain/session/room/timeline/tiles/SimpleTile.js";
export {TimelineView} from "./platform/web/ui/session/room/TimelineView";
export {viewClassForTile} from "./platform/web/ui/session/room/common";
export type {TileViewConstructor, ViewClassForEntryFn} from "./platform/web/ui/session/room/TimelineView";
// export timeline tile views
export {AnnouncementView} from "./platform/web/ui/session/room/timeline/AnnouncementView.js";
export {BaseMediaView} from "./platform/web/ui/session/room/timeline/BaseMediaView.js";
export {BaseMessageView} from "./platform/web/ui/session/room/timeline/BaseMessageView.js";
export {FileView} from "./platform/web/ui/session/room/timeline/FileView.js";
export {GapView} from "./platform/web/ui/session/room/timeline/GapView.js";
export {ImageView} from "./platform/web/ui/session/room/timeline/ImageView.js";
export {LocationView} from "./platform/web/ui/session/room/timeline/LocationView.js";
export {MissingAttachmentView} from "./platform/web/ui/session/room/timeline/MissingAttachmentView.js";
export {ReactionsView} from "./platform/web/ui/session/room/timeline/ReactionsView.js";
export {RedactedView} from "./platform/web/ui/session/room/timeline/RedactedView.js";
export {ReplyPreviewView} from "./platform/web/ui/session/room/timeline/ReplyPreviewView.js";
export {TextMessageView} from "./platform/web/ui/session/room/timeline/TextMessageView.js";
export {VideoView} from "./platform/web/ui/session/room/timeline/VideoView.js";
export {Navigation} from "./domain/navigation/Navigation.js";
export {ComposerViewModel} from "./domain/session/room/ComposerViewModel.js";
export {MessageComposer} from "./platform/web/ui/session/room/MessageComposer.js";

View file

@ -27,8 +27,8 @@ class Request implements IHomeServerRequest {
public readonly args: any[];
private responseResolve: (result: any) => void;
public responseReject: (error: Error) => void;
private responseCodeResolve: (result: any) => void;
private responseCodeReject: (result: any) => void;
private responseCodeResolve?: (result: any) => void;
private responseCodeReject?: (result: any) => void;
private _requestResult?: IHomeServerRequest;
private readonly _responsePromise: Promise<any>;
private _responseCodePromise: Promise<any>;
@ -73,7 +73,7 @@ class Request implements IHomeServerRequest {
const response = await this._requestResult?.response();
this.responseResolve(response);
const responseCode = await this._requestResult?.responseCode();
this.responseCodeResolve(responseCode);
this.responseCodeResolve?.(responseCode);
}
get requestResult() {

View file

@ -11,8 +11,7 @@
<meta name="description" content="A matrix chat application">
<link rel="apple-touch-icon" href="./assets/icon-maskable.png">
<link rel="icon" type="image/png" href="assets/icon-maskable.png">
<link rel="stylesheet" type="text/css" href="./ui/css/main.css">
<link rel="stylesheet" type="text/css" href="./ui/css/themes/element/theme.css">
<script type="module"> import "@theme/default"; </script>
</head>
<body class="hydrogen">
<script id="main" type="module">

View file

@ -22,6 +22,7 @@
.RoomDetailsView_label, .RoomDetailsView_row, .RoomDetailsView, .MemberDetailsView, .EncryptionIconView {
display: flex;
align-items: center;
color: var(--text-color);
}
.RoomDetailsView_value {

View file

@ -1,6 +1,6 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.28 2.88C17.28 1.28942 18.5694 0 20.16 0C30.7639 0 39.36 8.59613 39.36 19.2C39.36 20.7906 38.0706 22.08 36.48 22.08C34.8894 22.08 33.6 20.7906 33.6 19.2C33.6 11.7773 27.5827 5.76 20.16 5.76C18.5694 5.76 17.28 4.47058 17.28 2.88Z" fill="#0DBD8B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.72 45.12C30.72 46.7106 29.4306 48 27.84 48C17.2361 48 8.64 39.4039 8.64 28.8C8.64 27.2094 9.92942 25.92 11.52 25.92C13.1106 25.92 14.4 27.2094 14.4 28.8C14.4 36.2227 20.4173 42.24 27.84 42.24C29.4306 42.24 30.72 43.5294 30.72 45.12Z" fill="#0DBD8B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.88 30.72C1.28942 30.72 -5.63623e-08 29.4306 -1.25889e-07 27.84C-5.89399e-07 17.2361 8.59613 8.63997 19.2 8.63997C20.7906 8.63997 22.08 9.92939 22.08 11.52C22.08 13.1106 20.7906 14.4 19.2 14.4C11.7773 14.4 5.76 20.4173 5.76 27.84C5.76 29.4306 4.47058 30.72 2.88 30.72Z" fill="#0DBD8B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.12 17.28C46.7106 17.28 48 18.5694 48 20.16C48 30.7639 39.4039 39.36 28.8 39.36C27.2094 39.36 25.92 38.0706 25.92 36.48C25.92 34.8894 27.2094 33.6 28.8 33.6C36.2227 33.6 42.24 27.5827 42.24 20.16C42.24 18.5694 43.5294 17.28 45.12 17.28Z" fill="#0DBD8B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.28 2.88C17.28 1.28942 18.5694 0 20.16 0C30.7639 0 39.36 8.59613 39.36 19.2C39.36 20.7906 38.0706 22.08 36.48 22.08C34.8894 22.08 33.6 20.7906 33.6 19.2C33.6 11.7773 27.5827 5.76 20.16 5.76C18.5694 5.76 17.28 4.47058 17.28 2.88Z" fill="#ff00ff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M30.72 45.12C30.72 46.7106 29.4306 48 27.84 48C17.2361 48 8.64 39.4039 8.64 28.8C8.64 27.2094 9.92942 25.92 11.52 25.92C13.1106 25.92 14.4 27.2094 14.4 28.8C14.4 36.2227 20.4173 42.24 27.84 42.24C29.4306 42.24 30.72 43.5294 30.72 45.12Z" fill="#ff00ff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.88 30.72C1.28942 30.72 -5.63623e-08 29.4306 -1.25889e-07 27.84C-5.89399e-07 17.2361 8.59613 8.63997 19.2 8.63997C20.7906 8.63997 22.08 9.92939 22.08 11.52C22.08 13.1106 20.7906 14.4 19.2 14.4C11.7773 14.4 5.76 20.4173 5.76 27.84C5.76 29.4306 4.47058 30.72 2.88 30.72Z" fill="#ff00ff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M45.12 17.28C46.7106 17.28 48 18.5694 48 20.16C48 30.7639 39.4039 39.36 28.8 39.36C27.2094 39.36 25.92 38.0706 25.92 36.48C25.92 34.8894 27.2094 33.6 28.8 33.6C36.2227 33.6 42.24 27.5827 42.24 20.16C42.24 18.5694 43.5294 17.28 45.12 17.28Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -16,7 +16,7 @@
fill-rule="evenodd"
clip-rule="evenodd"
d="M 8.20723,2.70711 C 8.59775,3.09763 8.59878,3.73182 8.20952,4.1236 L 3.27581,9.08934 8.22556,14.0391 c 0.39052,0.3905 0.39155,1.0247 0.00229,1.4165 -0.38926,0.3918 -1.0214,0.3928 -1.41192,0.0023 L 1.15907,9.80101 C 0.768549,9.41049 0.767523,8.7763 1.15678,8.38452 L 6.79531,2.70939 C 7.18457,2.31761 7.8167,2.31658 8.20723,2.70711 Z"
fill="#8d99a5"
fill="#ff00ff"
id="path830" />
</g>
<defs

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,6 +1,6 @@
<svg width="9" height="17" viewBox="0 0 9 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.20723 2.70711C8.59775 3.09763 8.59878 3.73182 8.20952 4.1236L3.27581 9.08934L8.22556 14.0391C8.61608 14.4296 8.61711 15.0638 8.22785 15.4556C7.83859 15.8474 7.20645 15.8484 6.81593 15.4579L1.15907 9.80101C0.768549 9.41049 0.767523 8.7763 1.15678 8.38452L6.79531 2.70939C7.18457 2.31761 7.8167 2.31658 8.20723 2.70711Z" fill="#8D99A5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.20723 2.70711C8.59775 3.09763 8.59878 3.73182 8.20952 4.1236L3.27581 9.08934L8.22556 14.0391C8.61608 14.4296 8.61711 15.0638 8.22785 15.4556C7.83859 15.8474 7.20645 15.8484 6.81593 15.4579L1.15907 9.80101C0.768549 9.41049 0.767523 8.7763 1.15678 8.38452L6.79531 2.70939C7.18457 2.31761 7.8167 2.31658 8.20723 2.70711Z" fill="#ff00ff"/>
</g>
<defs>
<clipPath id="clip0">

Before

Width:  |  Height:  |  Size: 657 B

After

Width:  |  Height:  |  Size: 657 B

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 18L15 12L9 6" stroke="#8D99A5" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 18L15 12L9 6" stroke="#ff00ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

View file

@ -1,3 +1,3 @@
<svg width="7" height="11" viewBox="0 0 7 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.96967 10.7197C0.676777 10.4268 0.676007 9.95117 0.967952 9.65733L4.66823 5.93303L0.955922 2.22072C0.663029 1.92782 0.662259 1.45218 0.954204 1.15834C1.24615 0.864504 1.72025 0.863737 2.01315 1.15663L6.25579 5.39927C6.54868 5.69216 6.54945 6.1678 6.2575 6.46164L2.02861 10.718C1.73667 11.0118 1.26256 11.0126 0.96967 10.7197Z" fill="#737D8C"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.96967 10.7197C0.676777 10.4268 0.676007 9.95117 0.967952 9.65733L4.66823 5.93303L0.955922 2.22072C0.663029 1.92782 0.662259 1.45218 0.954204 1.15834C1.24615 0.864504 1.72025 0.863737 2.01315 1.15663L6.25579 5.39927C6.54868 5.69216 6.54945 6.1678 6.2575 6.46164L2.02861 10.718C1.73667 11.0118 1.26256 11.0126 0.96967 10.7197Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 496 B

After

Width:  |  Height:  |  Size: 496 B

View file

@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.5 6L7.5 9L10.5 12" stroke="#8D99A5" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 6L7.5 9L10.5 12" stroke="#ff00ff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 220 B

After

Width:  |  Height:  |  Size: 220 B

View file

@ -1,4 +1,4 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.33313 1.33313L6.66646 6.66646" stroke="#8D99A5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.66699 1.33313L1.33366 6.66646" stroke="#8D99A5" stroke-width="1.5" stroke-linecap="round"/>
<path d="M1.33313 1.33313L6.66646 6.66646" stroke="#ff00ff" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.66699 1.33313L1.33366 6.66646" stroke="#ff00ff" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4C0 1.79086 1.79086 0 4 0H12C14.2091 0 16 1.79086 16 4V12C16 14.2091 14.2091 16 12 16H4C1.79086 16 0 14.2091 0 12V4ZM1.5 4.75C1.5 4.19772 1.94772 3.75 2.5 3.75H3.5C4.05228 3.75 4.5 4.19772 4.5 4.75V11.25C4.5 11.8023 4.05228 12.25 3.5 12.25H2.5C1.94772 12.25 1.5 11.8023 1.5 11.25V4.75ZM7 3.75C6.44772 3.75 6 4.19772 6 4.75V11.25C6 11.8023 6.44772 12.25 7 12.25H13.5C14.0523 12.25 14.5 11.8023 14.5 11.25V4.75C14.5 4.19772 14.0523 3.75 13.5 3.75H7Z" fill="#8D99A5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 4C0 1.79086 1.79086 0 4 0H12C14.2091 0 16 1.79086 16 4V12C16 14.2091 14.2091 16 12 16H4C1.79086 16 0 14.2091 0 12V4ZM1.5 4.75C1.5 4.19772 1.94772 3.75 2.5 3.75H3.5C4.05228 3.75 4.5 4.19772 4.5 4.75V11.25C4.5 11.8023 4.05228 12.25 3.5 12.25H2.5C1.94772 12.25 1.5 11.8023 1.5 11.25V4.75ZM7 3.75C6.44772 3.75 6 4.19772 6 4.75V11.25C6 11.8023 6.44772 12.25 7 12.25H13.5C14.0523 12.25 14.5 11.8023 14.5 11.25V4.75C14.5 4.19772 14.0523 3.75 13.5 3.75H7Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 621 B

View file

@ -1,4 +1,4 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.33313 1.33313L6.66646 6.66646" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.66699 1.33313L1.33366 6.66646" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"/>
<path d="M1.33313 1.33313L6.66646 6.66646" stroke="#ff00ff" stroke-width="1.5" stroke-linecap="round"/>
<path d="M6.66699 1.33313L1.33366 6.66646" stroke="#ff00ff" stroke-width="1.5" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 307 B

View file

@ -1,5 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.38 12.27C15.76 11.42 16 10.43 16 9.27V3.05L8.99997 1L5.21997 2.11L15.38 12.27Z" fill="white"/>
<path d="M2.21 2.98999L2 3.04999V9.26999C2 15.63 9 17 9 17C9 17 11.71 16.47 13.76 14.53L2.21 2.98999Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.46967 0.46967C0.762563 0.176777 1.23744 0.176777 1.53033 0.46967L16.7203 15.6597C17.0132 15.9526 17.0132 16.4274 16.7203 16.7203C16.4274 17.0132 15.9526 17.0132 15.6597 16.7203L0.46967 1.53033C0.176777 1.23744 0.176777 0.762563 0.46967 0.46967Z" fill="white"/>
<path d="M15.38 12.27C15.76 11.42 16 10.43 16 9.27V3.05L8.99997 1L5.21997 2.11L15.38 12.27Z" fill="#ff00ff"/>
<path d="M2.21 2.98999L2 3.04999V9.26999C2 15.63 9 17 9 17C9 17 11.71 16.47 13.76 14.53L2.21 2.98999Z" fill="#ff00ff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.46967 0.46967C0.762563 0.176777 1.23744 0.176777 1.53033 0.46967L16.7203 15.6597C17.0132 15.9526 17.0132 16.4274 16.7203 16.7203C16.4274 17.0132 15.9526 17.0132 15.6597 16.7203L0.46967 1.53033C0.176777 1.23744 0.176777 0.762563 0.46967 0.46967Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 648 B

View file

@ -1,3 +1,3 @@
<svg viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 9.27V3.05L9 1L16 3.05V9.27C16 15.63 9 17 9 17C9 17 2 15.63 2 9.27Z" fill="white"/>
<path d="M2 9.27V3.05L9 1L16 3.05V9.27C16 15.63 9 17 9 17C9 17 2 15.63 2 9.27Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 181 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 0C1.79086 0 0 1.79086 0 4V12C0 14.2091 1.79086 16 4 16H12C14.2091 16 16 14.2091 16 12V4C16 1.79086 14.2091 0 12 0H4ZM2.5 3.75C1.94772 3.75 1.5 4.19772 1.5 4.75V11.25C1.5 11.8023 1.94772 12.25 2.5 12.25H3.5C4.05228 12.25 4.5 11.8023 4.5 11.25V4.75C4.5 4.19772 4.05228 3.75 3.5 3.75H2.5ZM11 9.75C11 9.19771 11.4477 8.75 12 8.75H13.5C14.0523 8.75 14.5 9.19772 14.5 9.75V11.25C14.5 11.8023 14.0523 12.25 13.5 12.25H12C11.4477 12.25 11 11.8023 11 11.25V9.75ZM7 8.75C6.44772 8.75 6 9.19771 6 9.75V11.25C6 11.8023 6.44772 12.25 7 12.25H8.5C9.05228 12.25 9.5 11.8023 9.5 11.25V9.75C9.5 9.19772 9.05229 8.75 8.5 8.75H7ZM11 4.75C11 4.19772 11.4477 3.75 12 3.75H13.5C14.0523 3.75 14.5 4.19772 14.5 4.75V6.25C14.5 6.80228 14.0523 7.25 13.5 7.25H12C11.4477 7.25 11 6.80228 11 6.25V4.75ZM7 3.75C6.44772 3.75 6 4.19772 6 4.75V6.25C6 6.80228 6.44772 7.25 7 7.25H8.5C9.05228 7.25 9.5 6.80228 9.5 6.25V4.75C9.5 4.19772 9.05229 3.75 8.5 3.75H7Z" fill="#8D99A5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4 0C1.79086 0 0 1.79086 0 4V12C0 14.2091 1.79086 16 4 16H12C14.2091 16 16 14.2091 16 12V4C16 1.79086 14.2091 0 12 0H4ZM2.5 3.75C1.94772 3.75 1.5 4.19772 1.5 4.75V11.25C1.5 11.8023 1.94772 12.25 2.5 12.25H3.5C4.05228 12.25 4.5 11.8023 4.5 11.25V4.75C4.5 4.19772 4.05228 3.75 3.5 3.75H2.5ZM11 9.75C11 9.19771 11.4477 8.75 12 8.75H13.5C14.0523 8.75 14.5 9.19772 14.5 9.75V11.25C14.5 11.8023 14.0523 12.25 13.5 12.25H12C11.4477 12.25 11 11.8023 11 11.25V9.75ZM7 8.75C6.44772 8.75 6 9.19771 6 9.75V11.25C6 11.8023 6.44772 12.25 7 12.25H8.5C9.05228 12.25 9.5 11.8023 9.5 11.25V9.75C9.5 9.19772 9.05229 8.75 8.5 8.75H7ZM11 4.75C11 4.19772 11.4477 3.75 12 3.75H13.5C14.0523 3.75 14.5 4.19772 14.5 4.75V6.25C14.5 6.80228 14.0523 7.25 13.5 7.25H12C11.4477 7.25 11 6.80228 11 6.25V4.75ZM7 3.75C6.44772 3.75 6 4.19772 6 4.75V6.25C6 6.80228 6.44772 7.25 7 7.25H8.5C9.05228 7.25 9.5 6.80228 9.5 6.25V4.75C9.5 4.19772 9.05229 3.75 8.5 3.75H7Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,3 +1,3 @@
<svg width="25" height="24" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 19C9 19 18 15.2 18 9.50002V2.85001L9 1.52588e-05L0 2.85001L0 9.50002C0 15.2 9 19 9 19Z" fill="#C1C6CD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 19C9 19 18 15.2 18 9.50002V2.85001L9 1.52588e-05L0 2.85001L0 9.50002C0 15.2 9 19 9 19Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 260 B

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C12.8284 10 13.5 9.32843 13.5 8.5C13.5 7.67157 12.8284 7 12 7C11.1716 7 10.5 7.67157 10.5 8.5C10.5 9.32843 11.1716 10 12 10ZM11 13C10.4477 13 10 12.5523 10 12C10 11.4477 10.4477 11 11 11H12C12.5523 11 13 11.4477 13 12V15.5H13.5C14.0523 15.5 14.5 15.9477 14.5 16.5C14.5 17.0523 14.0523 17.5 13.5 17.5H12C11.4477 17.5 11 17.0523 11 16.5L11 13Z" fill="#8d99a5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 10C12.8284 10 13.5 9.32843 13.5 8.5C13.5 7.67157 12.8284 7 12 7C11.1716 7 10.5 7.67157 10.5 8.5C10.5 9.32843 11.1716 10 12 10ZM11 13C10.4477 13 10 12.5523 10 12C10 11.4477 10.4477 11 11 11H12C12.5523 11 13 11.4477 13 12V15.5H13.5C14.0523 15.5 14.5 15.9477 14.5 16.5C14.5 17.0523 14.0523 17.5 13.5 17.5H12C11.4477 17.5 11 17.0523 11 16.5L11 13Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 518 B

View file

@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.099 9.51084L10.4407 17.1692C8.48696 19.1229 5.31938 19.1229 3.36567 17.1692C1.41196 15.2155 1.41196 12.0479 3.36567 10.0942L11.024 2.43584C12.3265 1.13337 14.4382 1.13337 15.7407 2.43584C17.0431 3.73831 17.0431 5.85003 15.7407 7.1525L8.074 14.8108C7.42277 15.4621 6.36691 15.4621 5.71567 14.8108C5.06444 14.1596 5.06444 13.1037 5.71567 12.4525L12.7907 5.38584" stroke="#61708B" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.099 9.51084L10.4407 17.1692C8.48696 19.1229 5.31938 19.1229 3.36567 17.1692C1.41196 15.2155 1.41196 12.0479 3.36567 10.0942L11.024 2.43584C12.3265 1.13337 14.4382 1.13337 15.7407 2.43584C17.0431 3.73831 17.0431 5.85003 15.7407 7.1525L8.074 14.8108C7.42277 15.4621 6.36691 15.4621 5.71567 14.8108C5.06444 14.1596 5.06444 13.1037 5.71567 12.4525L12.7907 5.38584" stroke="#ff00ff" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 543 B

View file

@ -1,3 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.74986 3.55554C8.74986 3.14133 8.41408 2.80554 7.99986 2.80554C7.58565 2.80554 7.24986 3.14133 7.24986 3.55554V7.24999L3.55542 7.24999C3.14121 7.24999 2.80542 7.58577 2.80542 7.99999C2.80542 8.4142 3.14121 8.74999 3.55542 8.74999L7.24987 8.74999V12.4444C7.24987 12.8586 7.58565 13.1944 7.99987 13.1944C8.41408 13.1944 8.74987 12.8586 8.74987 12.4444V8.74999L12.4443 8.74999C12.8585 8.74999 13.1943 8.4142 13.1943 7.99999C13.1943 7.58577 12.8585 7.24999 12.4443 7.24999L8.74986 7.24999V3.55554Z" fill="#8E99A4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.74986 3.55554C8.74986 3.14133 8.41408 2.80554 7.99986 2.80554C7.58565 2.80554 7.24986 3.14133 7.24986 3.55554V7.24999L3.55542 7.24999C3.14121 7.24999 2.80542 7.58577 2.80542 7.99999C2.80542 8.4142 3.14121 8.74999 3.55542 8.74999L7.24987 8.74999V12.4444C7.24987 12.8586 7.58565 13.1944 7.99987 13.1944C8.41408 13.1944 8.74987 12.8586 8.74987 12.4444V8.74999L12.4443 8.74999C12.8585 8.74999 13.1943 8.4142 13.1943 7.99999C13.1943 7.58577 12.8585 7.24999 12.4443 7.24999L8.74986 7.24999V3.55554Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 670 B

View file

@ -2,6 +2,6 @@
<mask id="path-1-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1502 21.1214C16.3946 22.3074 14.2782 23 12 23C9.52367 23 7.23845 22.1817 5.4 20.8008C2.72821 18.794 1 15.5988 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 15.797 21.0762 19.1446 18.1502 21.1214ZM12 12.55C13.8225 12.55 15.3 10.9494 15.3 8.975C15.3 7.00058 13.8225 5.4 12 5.4C10.1775 5.4 8.7 7.00058 8.7 8.975C8.7 10.9494 10.1775 12.55 12 12.55ZM12 20.8C14.3782 20.8 16.536 19.8566 18.1197 18.3237C17.1403 15.9056 14.7693 14.2 12 14.2C9.23066 14.2 6.85969 15.9056 5.88028 18.3237C7.46399 19.8566 9.62183 20.8 12 20.8Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1502 21.1214C16.3946 22.3074 14.2782 23 12 23C9.52367 23 7.23845 22.1817 5.4 20.8008C2.72821 18.794 1 15.5988 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 15.797 21.0762 19.1446 18.1502 21.1214ZM12 12.55C13.8225 12.55 15.3 10.9494 15.3 8.975C15.3 7.00058 13.8225 5.4 12 5.4C10.1775 5.4 8.7 7.00058 8.7 8.975C8.7 10.9494 10.1775 12.55 12 12.55ZM12 20.8C14.3782 20.8 16.536 19.8566 18.1197 18.3237C17.1403 15.9056 14.7693 14.2 12 14.2C9.23066 14.2 6.85969 15.9056 5.88028 18.3237C7.46399 19.8566 9.62183 20.8 12 20.8Z" fill="#C1C6CD"/>
<path d="M18.1502 21.1214L18.9339 22.2814L18.1502 21.1214ZM5.4 20.8008L4.55919 21.9202H4.55919L5.4 20.8008ZM18.1197 18.3237L19.0934 19.3296L19.7717 18.6731L19.4173 17.7981L18.1197 18.3237ZM5.88028 18.3237L4.58268 17.7981L4.22829 18.6731L4.90659 19.3296L5.88028 18.3237ZM12 24.4C14.5662 24.4 16.9541 23.619 18.9339 22.2814L17.3665 19.9613C15.835 20.9959 13.9902 21.6 12 21.6V24.4ZM4.55919 21.9202C6.63176 23.477 9.21011 24.4 12 24.4V21.6C9.83723 21.6 7.84514 20.8865 6.24081 19.6814L4.55919 21.9202ZM-0.399998 12C-0.399998 16.0577 1.55052 19.6603 4.55919 21.9202L6.24081 19.6814C3.90591 17.9276 2.4 15.1399 2.4 12H-0.399998ZM12 -0.399998C5.15167 -0.399998 -0.399998 5.15167 -0.399998 12H2.4C2.4 6.69807 6.69807 2.4 12 2.4V-0.399998ZM24.4 12C24.4 5.15167 18.8483 -0.399998 12 -0.399998V2.4C17.3019 2.4 21.6 6.69807 21.6 12H24.4ZM18.9339 22.2814C22.2288 20.0554 24.4 16.2815 24.4 12H21.6C21.6 15.3124 19.9236 18.2337 17.3665 19.9613L18.9339 22.2814ZM13.9 8.975C13.9 10.2838 12.9459 11.15 12 11.15V13.95C14.6991 13.95 16.7 11.615 16.7 8.975H13.9ZM12 6.8C12.9459 6.8 13.9 7.66616 13.9 8.975H16.7C16.7 6.335 14.6991 4 12 4V6.8ZM10.1 8.975C10.1 7.66616 11.0541 6.8 12 6.8V4C9.30086 4 7.3 6.335 7.3 8.975H10.1ZM12 11.15C11.0541 11.15 10.1 10.2838 10.1 8.975H7.3C7.3 11.615 9.30086 13.95 12 13.95V11.15ZM17.146 17.3178C15.8129 18.6081 14.0004 19.4 12 19.4V22.2C14.756 22.2 17.2591 21.1051 19.0934 19.3296L17.146 17.3178ZM12 15.6C14.1797 15.6 16.0494 16.9415 16.8221 18.8493L19.4173 17.7981C18.2312 14.8697 15.359 12.8 12 12.8V15.6ZM7.17788 18.8493C7.95058 16.9415 9.8203 15.6 12 15.6V12.8C8.64102 12.8 5.7688 14.8697 4.58268 17.7981L7.17788 18.8493ZM12 19.4C9.99963 19.4 8.18709 18.6081 6.85397 17.3178L4.90659 19.3296C6.74088 21.1051 9.24402 22.2 12 22.2V19.4Z" fill="#C1C6CD" mask="url(#path-1-inside-1)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M18.1502 21.1214C16.3946 22.3074 14.2782 23 12 23C9.52367 23 7.23845 22.1817 5.4 20.8008C2.72821 18.794 1 15.5988 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 15.797 21.0762 19.1446 18.1502 21.1214ZM12 12.55C13.8225 12.55 15.3 10.9494 15.3 8.975C15.3 7.00058 13.8225 5.4 12 5.4C10.1775 5.4 8.7 7.00058 8.7 8.975C8.7 10.9494 10.1775 12.55 12 12.55ZM12 20.8C14.3782 20.8 16.536 19.8566 18.1197 18.3237C17.1403 15.9056 14.7693 14.2 12 14.2C9.23066 14.2 6.85969 15.9056 5.88028 18.3237C7.46399 19.8566 9.62183 20.8 12 20.8Z" fill="#ff00ff"/>
<path d="M18.1502 21.1214L18.9339 22.2814L18.1502 21.1214ZM5.4 20.8008L4.55919 21.9202H4.55919L5.4 20.8008ZM18.1197 18.3237L19.0934 19.3296L19.7717 18.6731L19.4173 17.7981L18.1197 18.3237ZM5.88028 18.3237L4.58268 17.7981L4.22829 18.6731L4.90659 19.3296L5.88028 18.3237ZM12 24.4C14.5662 24.4 16.9541 23.619 18.9339 22.2814L17.3665 19.9613C15.835 20.9959 13.9902 21.6 12 21.6V24.4ZM4.55919 21.9202C6.63176 23.477 9.21011 24.4 12 24.4V21.6C9.83723 21.6 7.84514 20.8865 6.24081 19.6814L4.55919 21.9202ZM-0.399998 12C-0.399998 16.0577 1.55052 19.6603 4.55919 21.9202L6.24081 19.6814C3.90591 17.9276 2.4 15.1399 2.4 12H-0.399998ZM12 -0.399998C5.15167 -0.399998 -0.399998 5.15167 -0.399998 12H2.4C2.4 6.69807 6.69807 2.4 12 2.4V-0.399998ZM24.4 12C24.4 5.15167 18.8483 -0.399998 12 -0.399998V2.4C17.3019 2.4 21.6 6.69807 21.6 12H24.4ZM18.9339 22.2814C22.2288 20.0554 24.4 16.2815 24.4 12H21.6C21.6 15.3124 19.9236 18.2337 17.3665 19.9613L18.9339 22.2814ZM13.9 8.975C13.9 10.2838 12.9459 11.15 12 11.15V13.95C14.6991 13.95 16.7 11.615 16.7 8.975H13.9ZM12 6.8C12.9459 6.8 13.9 7.66616 13.9 8.975H16.7C16.7 6.335 14.6991 4 12 4V6.8ZM10.1 8.975C10.1 7.66616 11.0541 6.8 12 6.8V4C9.30086 4 7.3 6.335 7.3 8.975H10.1ZM12 11.15C11.0541 11.15 10.1 10.2838 10.1 8.975H7.3C7.3 11.615 9.30086 13.95 12 13.95V11.15ZM17.146 17.3178C15.8129 18.6081 14.0004 19.4 12 19.4V22.2C14.756 22.2 17.2591 21.1051 19.0934 19.3296L17.146 17.3178ZM12 15.6C14.1797 15.6 16.0494 16.9415 16.8221 18.8493L19.4173 17.7981C18.2312 14.8697 15.359 12.8 12 12.8V15.6ZM7.17788 18.8493C7.95058 16.9415 9.8203 15.6 12 15.6V12.8C8.64102 12.8 5.7688 14.8697 4.58268 17.7981L7.17788 18.8493ZM12 19.4C9.99963 19.4 8.18709 18.6081 6.85397 17.3178L4.90659 19.3296C6.74088 21.1051 9.24402 22.2 12 22.2V19.4Z" fill="#ff00ff" mask="url(#path-1-inside-1)"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -1,3 +1,3 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1333 6.06667C10.1333 8.31262 8.31262 10.1333 6.06667 10.1333C3.82071 10.1333 2 8.31262 2 6.06667C2 3.82071 3.82071 2 6.06667 2C8.31262 2 10.1333 3.82071 10.1333 6.06667ZM10.9992 9.59936C11.7131 8.60443 12.1333 7.38463 12.1333 6.06667C12.1333 2.71614 9.41719 0 6.06667 0C2.71614 0 0 2.71614 0 6.06667C0 9.41719 2.71614 12.1333 6.06667 12.1333C7.38457 12.1333 8.60431 11.7131 9.59922 10.9993C9.62742 11.0369 9.65861 11.0729 9.6928 11.1071L12.2928 13.7071C12.6833 14.0977 13.3165 14.0977 13.707 13.7071C14.0975 13.3166 14.0975 12.6834 13.707 12.2929L11.107 9.69292C11.0728 9.65874 11.0368 9.62756 10.9992 9.59936Z" fill="#8D99A5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1333 6.06667C10.1333 8.31262 8.31262 10.1333 6.06667 10.1333C3.82071 10.1333 2 8.31262 2 6.06667C2 3.82071 3.82071 2 6.06667 2C8.31262 2 10.1333 3.82071 10.1333 6.06667ZM10.9992 9.59936C11.7131 8.60443 12.1333 7.38463 12.1333 6.06667C12.1333 2.71614 9.41719 0 6.06667 0C2.71614 0 0 2.71614 0 6.06667C0 9.41719 2.71614 12.1333 6.06667 12.1333C7.38457 12.1333 8.60431 11.7131 9.59922 10.9993C9.62742 11.0369 9.65861 11.0729 9.6928 11.1071L12.2928 13.7071C12.6833 14.0977 13.3165 14.0977 13.707 13.7071C14.0975 13.3166 14.0975 12.6834 13.707 12.2929L11.107 9.69292C11.0728 9.65874 11.0368 9.62756 10.9992 9.59936Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 785 B

View file

@ -1,3 +1,3 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7372 8.96458L1.89656 15.8816C0.963879 16.3478 -0.00645849 15.3477 0.449387 14.4353C0.449387 14.4353 2.16481 10.9711 2.63665 10.0637C3.10849 9.15633 3.64864 8.99926 8.6646 8.35098C8.85021 8.32697 9.00215 8.1869 9.00215 7.99982C9.00215 7.81307 8.85021 7.67301 8.6646 7.649C3.64864 7.00071 3.10849 6.84364 2.63665 5.93624C2.16481 5.02918 0.449387 1.56465 0.449387 1.56465C-0.00645849 0.65258 0.963879 -0.347862 1.89656 0.118344L15.7372 7.03573C16.5319 7.43257 16.5319 8.5674 15.7372 8.96458Z" fill="white"/>
<path d="M15.7372 8.96458L1.89656 15.8816C0.963879 16.3478 -0.00645849 15.3477 0.449387 14.4353C0.449387 14.4353 2.16481 10.9711 2.63665 10.0637C3.10849 9.15633 3.64864 8.99926 8.6646 8.35098C8.85021 8.32697 9.00215 8.1869 9.00215 7.99982C9.00215 7.81307 8.85021 7.67301 8.6646 7.649C3.64864 7.00071 3.10849 6.84364 2.63665 5.93624C2.16481 5.02918 0.449387 1.56465 0.449387 1.56465C-0.00645849 0.65258 0.963879 -0.347862 1.89656 0.118344L15.7372 7.03573C16.5319 7.43257 16.5319 8.5674 15.7372 8.96458Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 621 B

After

Width:  |  Height:  |  Size: 623 B

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3625 9.2875C19.5625 9.8125 20.075 10.1625 20.6375 10.1625C21.3875 10.1625 22 10.775 22 11.525V12.475C22 13.225 21.3875 13.8375 20.6375 13.8375C20.075 13.8375 19.5625 14.1875 19.3625 14.7125C19.346 14.7538 19.3294 14.7958 19.3128 14.838C19.2538 14.9876 19.1932 15.1413 19.125 15.2875C18.8875 15.8 19 16.4 19.4 16.8C19.9375 17.325 19.9375 18.1875 19.4 18.725L18.725 19.4C18.2 19.9375 17.3375 19.9375 16.8 19.4C16.4125 19 15.8 18.8875 15.2875 19.125C15.1 19.2125 14.9125 19.2875 14.7125 19.3625C14.1875 19.5625 13.8375 20.075 13.8375 20.6375C13.8375 21.3875 13.225 22 12.475 22H11.525C10.775 22 10.1625 21.3875 10.1625 20.6375C10.1625 20.075 9.8125 19.5625 9.2875 19.3625C9.24617 19.346 9.20423 19.3294 9.16195 19.3128C9.01243 19.2538 8.85867 19.1932 8.7125 19.125C8.2 18.8875 7.6 19 7.2 19.4C6.675 19.9375 5.8125 19.9375 5.275 19.4L4.6 18.725C4.0625 18.2 4.0625 17.3375 4.6 16.8C5 16.4125 5.1125 15.8 4.875 15.2875C4.7875 15.1 4.7125 14.9125 4.6375 14.7125C4.4375 14.1875 3.925 13.8375 3.3625 13.8375C2.6125 13.8375 2 13.225 2 12.475V11.525C2 10.775 2.6125 10.1625 3.3625 10.1625C3.925 10.1625 4.4375 9.8125 4.6375 9.2875C4.67694 9.16129 4.72634 9.04005 4.77627 8.91751C4.80546 8.84587 4.83483 8.77379 4.8625 8.7C5.1 8.1875 4.9875 7.5875 4.5875 7.1875C4.05 6.6625 4.05 5.8 4.5875 5.2625L5.275 4.6C5.8 4.0625 6.6625 4.0625 7.2 4.6C7.5875 5 8.2 5.1125 8.7125 4.875C8.9 4.7875 9.0875 4.7 9.2875 4.6375C9.8125 4.4375 10.1625 3.925 10.1625 3.3625C10.1625 2.6125 10.775 2 11.525 2H12.475C13.225 2 13.8375 2.6125 13.8375 3.3625C13.8375 3.9375 14.1875 4.4375 14.7125 4.6375C14.7538 4.65403 14.7958 4.67056 14.838 4.68723C14.9876 4.74617 15.1413 4.80679 15.2875 4.875C15.8 5.1125 16.4 5 16.8 4.6C17.325 4.0625 18.1875 4.0625 18.725 4.6L19.4 5.275C19.9375 5.8 19.9375 6.6625 19.4 7.2C19 7.5875 18.8875 8.2 19.125 8.7125C19.2125 8.9 19.2875 9.0875 19.3625 9.2875ZM12 17C9.2375 17 7 14.7625 7 12C7 9.2375 9.2375 7 12 7C14.7625 7 17 9.2375 17 12C17 14.7625 14.7625 17 12 17Z" fill="#8D99A5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.3625 9.2875C19.5625 9.8125 20.075 10.1625 20.6375 10.1625C21.3875 10.1625 22 10.775 22 11.525V12.475C22 13.225 21.3875 13.8375 20.6375 13.8375C20.075 13.8375 19.5625 14.1875 19.3625 14.7125C19.346 14.7538 19.3294 14.7958 19.3128 14.838C19.2538 14.9876 19.1932 15.1413 19.125 15.2875C18.8875 15.8 19 16.4 19.4 16.8C19.9375 17.325 19.9375 18.1875 19.4 18.725L18.725 19.4C18.2 19.9375 17.3375 19.9375 16.8 19.4C16.4125 19 15.8 18.8875 15.2875 19.125C15.1 19.2125 14.9125 19.2875 14.7125 19.3625C14.1875 19.5625 13.8375 20.075 13.8375 20.6375C13.8375 21.3875 13.225 22 12.475 22H11.525C10.775 22 10.1625 21.3875 10.1625 20.6375C10.1625 20.075 9.8125 19.5625 9.2875 19.3625C9.24617 19.346 9.20423 19.3294 9.16195 19.3128C9.01243 19.2538 8.85867 19.1932 8.7125 19.125C8.2 18.8875 7.6 19 7.2 19.4C6.675 19.9375 5.8125 19.9375 5.275 19.4L4.6 18.725C4.0625 18.2 4.0625 17.3375 4.6 16.8C5 16.4125 5.1125 15.8 4.875 15.2875C4.7875 15.1 4.7125 14.9125 4.6375 14.7125C4.4375 14.1875 3.925 13.8375 3.3625 13.8375C2.6125 13.8375 2 13.225 2 12.475V11.525C2 10.775 2.6125 10.1625 3.3625 10.1625C3.925 10.1625 4.4375 9.8125 4.6375 9.2875C4.67694 9.16129 4.72634 9.04005 4.77627 8.91751C4.80546 8.84587 4.83483 8.77379 4.8625 8.7C5.1 8.1875 4.9875 7.5875 4.5875 7.1875C4.05 6.6625 4.05 5.8 4.5875 5.2625L5.275 4.6C5.8 4.0625 6.6625 4.0625 7.2 4.6C7.5875 5 8.2 5.1125 8.7125 4.875C8.9 4.7875 9.0875 4.7 9.2875 4.6375C9.8125 4.4375 10.1625 3.925 10.1625 3.3625C10.1625 2.6125 10.775 2 11.525 2H12.475C13.225 2 13.8375 2.6125 13.8375 3.3625C13.8375 3.9375 14.1875 4.4375 14.7125 4.6375C14.7538 4.65403 14.7958 4.67056 14.838 4.68723C14.9876 4.74617 15.1413 4.80679 15.2875 4.875C15.8 5.1125 16.4 5 16.8 4.6C17.325 4.0625 18.1875 4.0625 18.725 4.6L19.4 5.275C19.9375 5.8 19.9375 6.6625 19.4 7.2C19 7.5875 18.8875 8.2 19.125 8.7125C19.2125 8.9 19.2875 9.0875 19.3625 9.2875ZM12 17C9.2375 17 7 14.7625 7 12C7 9.2375 9.2375 7 12 7C14.7625 7 17 9.2375 17 12C17 14.7625 14.7625 17 12 17Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.154 16.662C13.154 16.158 12.776 15.836 12.272 15.836C11.754 15.836 11.39 16.158 11.39 16.662C11.39 17.152 11.754 17.488 12.272 17.488C12.776 17.502 13.154 17.152 13.154 16.662ZM13.154 12C13.154 11.496 12.776 11.16 12.272 11.16C11.754 11.174 11.39 11.496 11.39 11.986C11.39 12.476 11.754 12.826 12.272 12.826C12.776 12.84 13.154 12.49 13.154 12ZM13.154 7.338C13.154 6.82 12.776 6.498 12.272 6.498C11.754 6.498 11.39 6.834 11.39 7.324C11.39 7.814 11.754 8.164 12.272 8.164C12.776 8.164 13.154 7.828 13.154 7.338Z" fill="#454545"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.154 16.662C13.154 16.158 12.776 15.836 12.272 15.836C11.754 15.836 11.39 16.158 11.39 16.662C11.39 17.152 11.754 17.488 12.272 17.488C12.776 17.502 13.154 17.152 13.154 16.662ZM13.154 12C13.154 11.496 12.776 11.16 12.272 11.16C11.754 11.174 11.39 11.496 11.39 11.986C11.39 12.476 11.754 12.826 12.272 12.826C12.776 12.84 13.154 12.49 13.154 12ZM13.154 7.338C13.154 6.82 12.776 6.498 12.272 6.498C11.754 6.498 11.39 6.834 11.39 7.324C11.39 7.814 11.754 8.164 12.272 8.164C12.776 8.164 13.154 7.828 13.154 7.338Z" fill="#ff00ff"/>
</svg>

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 685 B

View file

@ -0,0 +1,45 @@
{
"version": 1,
"name": "element",
"values": {
"font-faces": [
{
"font-family": "Inter",
"src": [{"asset": "/fonts/Inter.ttf", "format": "ttf"}]
}
],
"variants": {
"light": {
"base": true,
"default": true,
"name": "Light",
"variables": {
"background-color-primary": "#fff",
"background-color-secondary": "#f6f6f6",
"text-color": "#2E2F32",
"accent-color": "#03b381",
"error-color": "#FF4B55",
"fixed-white": "#fff",
"room-badge": "#61708b",
"link-color": "#238cf5"
}
},
"dark": {
"dark": true,
"default": true,
"name": "Dark",
"variables": {
"background-color-primary": "#21262b",
"background-color-secondary": "#2D3239",
"text-color": "#fff",
"accent-color": "#03B381",
"error-color": "#FF4B55",
"fixed-white": "#fff",
"room-badge": "#61708b",
"link-color": "#238cf5"
}
}
}
}
}

View file

@ -15,17 +15,25 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
@import url('../../main.css');
@import url('inter.css');
@import url('timeline.css');
:root {
font-size: 10px;
/* Theme aliases */
--icon-color: var(--background-color-secondary--darker-40);
--light-border: var(--background-color-secondary--darker-5);
--light-text-color: var(--background-color-secondary--darker-55);
--timeline-time-text-color: var(--background-color-secondary--darker-35);
--icon-background: var(--background-color-secondary--darker-7);
--right-panel-text-color: var(--background-color-secondary--darker-35);
}
.hydrogen {
font-family: 'Inter', sans-serif, 'emoji';
background-color: white;
color: #2e2f32;
background-color: var(--background-color-primary);
color: var(--text-color);
font-size: 1.4rem;
--usercolor1: #368BD6;
--usercolor2: #AC3BA8;
@ -43,8 +51,8 @@ limitations under the License.
.avatar {
border-radius: 100%;
background: #fff;
color: white;
background: var(--background-color-primary);
color: var(--fixed-white);
}
.hydrogen .avatar.usercolor1 { background-color: var(--usercolor1); }
@ -59,7 +67,7 @@ limitations under the License.
.logo {
height: 48px;
min-width: 48px;
background-image: url('element-logo.svg');
background-image: url('element-logo.svg?primary=accent-color');
background-repeat: no-repeat;
background-position: center;
}
@ -82,6 +90,8 @@ limitations under the License.
.form-row.text textarea {
font-family: "Inter", sans-serif;
background-color: var(--background-color-secondary);
color: inherit;
}
.form-group {
@ -94,11 +104,13 @@ limitations under the License.
.form-row.text > input, .form-row.text > textarea {
padding: 12px;
border: 1px solid rgba(141, 151, 165, 0.15);
border: 1px solid var(--light-border);
border-radius: 8px;
margin-top: 5px;
font-size: 1em;
resize: vertical;
background-color: var(--background-color-secondary);
color: inherit;
}
.form-row.check {
@ -113,7 +125,7 @@ limitations under the License.
.form-row .form-row-description {
font-size: 1rem;
color: #777;
color: var(--light-text-color);
margin: 8px 0 0 0;
}
@ -129,26 +141,26 @@ a.button-action {
.button-action.secondary {
color: #03B381;
color: var(--accent-color);
}
.button-action.primary {
background-color: #03B381;
background-color: var(--accent-color);
border-radius: 8px;
color: white;
color: var(--fixed-white);
font-weight: bold;
}
.button-action.primary:disabled {
color: #fffa;
color: var(--fixed-white--darker-10);
}
.button-action.primary.destructive {
background-color: #FF4B55;
background-color: var(--error-color);
}
.button-action.secondary.destructive {
color: #FF4B55;
color: var(--error-color);
}
.button-action {
@ -163,7 +175,7 @@ a.button-action {
width: 32px;
height: 32px;
background-position: center;
background-color: #e1e3e6;
background-color: var(--icon-background);
background-repeat: no-repeat;
border: none;
border-radius: 100%;
@ -171,26 +183,26 @@ a.button-action {
}
.button-utility.grid {
background-image: url('icons/enable-grid.svg');
background-image: url('icons/enable-grid.svg?primary=icon-color');
}
.button-utility.settings {
background-image: url('icons/settings.svg');
background-image: url('icons/settings.svg?primary=icon-color');
}
.button-utility.create {
background-image: url('icons/plus.svg');
background-image: url('icons/plus.svg?primary=icon-color');
}
.button-utility.grid.on {
background-image: url('icons/disable-grid.svg');
background-image: url('icons/disable-grid.svg?primary=icon-color');
}
.FilterField {
background-image: url('icons/search.svg');
background-image: url('icons/search.svg?primary=icon-color');
background-repeat: no-repeat;
background-position: 8px center;
background-color: #e1e3e6;
background-color: var(--icon-background);
/* to prevent jumps when adding a border on focus */
border: 1px solid transparent;
border-radius: 16px;
@ -201,11 +213,12 @@ a.button-action {
}
.FilterField:focus-within {
border: 1px #e1e3e6 solid;
background-color: white;
border: 1px var(--icon-background) solid;
background-color: var(--background-color-primary);
}
.FilterField:focus-within button {
border-color: white;
border-color: var(--background-color-primary);
}
/*.FilterField:not(:focus-within) button {
@ -221,15 +234,16 @@ a.button-action {
border: none;
background-color: transparent;
height: 100%;
color: var(--text-color);
}
.FilterField button {
width: 30px; /* 32 - 1 (top) - 1 (bottom) */
height: 30px; /* 32 - 1 (top) - 1 (bottom) */
background-position: center;
background-color: #e1e3e6;
background-color: var(--icon-background);
background-repeat: no-repeat;
background-image: url('icons/clear.svg');
background-image: url('icons/clear.svg?primary=icon-color');
border: 7px solid transparent; /* 8 - 1 */
border-radius: 100%;
box-sizing: border-box;
@ -249,12 +263,12 @@ a.button-action {
}
.StartSSOLoginView_button {
border: 1px solid #03B381;
border: 1px solid var(--accent-color);
border-radius: 8px;
}
.LoginView_back {
background-image: url("./icons/chevron-left.svg");
background-image: url("./icons/chevron-left.svg?primary=icon-color");
background-color: transparent;
}
@ -266,7 +280,7 @@ a.button-action {
.LoginView_forwardInfo {
font-size: 0.9em;
margin-left: 1em;
color: #777;
color: var(--light-text-color);
}
.CompleteSSOView_title {
@ -275,6 +289,7 @@ a.button-action {
@media screen and (min-width: 600px) {
.PreSessionScreen {
/* needs transparency support */
box-shadow: 0px 6px 32px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
@ -286,7 +301,7 @@ a.button-action {
}
.LeftPanel {
background: rgba(245, 245, 245, 0.90);
background: var(--background-color-secondary);
font-size: 1.5rem;
padding: 12px 8px 0 8px;
}
@ -300,7 +315,7 @@ a.button-action {
}
.LeftPanel .filter {
border-bottom: 1px solid rgba(245, 245, 245, 0.90);
border-bottom: 1px solid var(--background-color-secondary);
}
.LeftPanel .filter input {
@ -335,7 +350,7 @@ a.button-action {
}
.RoomList li.active {
background: rgba(141, 151, 165, 0.1);
background: var(--background-color-secondary--darker-7);
border-radius: 5px;
}
@ -357,8 +372,8 @@ a.button-action {
border-radius: 1.6rem;
box-sizing: border-box;
padding: 0.1rem 0.3rem;
background-color: #61708b;
color: white;
background-color: var(--room-badge);
color: var(--fixed-white);
font-weight: bold;
font-size: 1rem;
line-height: 1.4rem;
@ -366,7 +381,7 @@ a.button-action {
}
.RoomList .badge.highlighted {
background-color: #ff4b55;
background-color: var(--error-color);
}
a {
@ -376,8 +391,8 @@ a {
.SessionStatusView {
padding: 4px;
min-height: 22px;
background-color: #03B381;
color: white;
background-color: var(--accent-color);
color: var(--fixed-white);
align-items: center;
}
@ -386,7 +401,7 @@ a {
text-align: left;
}
.SessionStatusView > .end {
.SessionStatusView>.end {
flex: 1;
display: flex;
justify-content: flex-end;
@ -397,7 +412,7 @@ a {
.SessionStatusView .dismiss {
border: none;
background: none;
background-image: url('icons/dismiss.svg');
background-image: url('icons/dismiss.svg?primary=background-color-primary');
background-position: center;
background-repeat: no-repeat;
width: 32px;
@ -418,9 +433,9 @@ a {
.SessionPickerView .session-info {
text-decoration: none;
padding: 12px;
border: 1px solid rgba(141, 151, 165, 0.15);
border: 1px solid var(--light-border);
border-radius: 8px;
background-image: url('icons/chevron-right.svg');
background-image: url('./icons/chevron-right.svg?primary=icon-color');
background-position: center right 30px;
background-repeat: no-repeat;
font-weight: 500;
@ -445,12 +460,12 @@ a {
}
.SessionPickerView button.destructive {
color: #FF4B55;
color: var(--error-color);
}
.RoomGridView > div.container {
border-right: 1px solid rgba(245, 245, 245, 0.90);
border-bottom: 1px solid rgba(245, 245, 245, 0.90);
border-right: 1px solid var(--background-color-secondary);
border-bottom: 1px solid var(--background-color-secondary);
}
.RoomGridView > .focused > .room-placeholder .unfocused {
@ -462,21 +477,21 @@ a {
}
.room-placeholder .unfocused {
color: #8D99A5;
color: var(--right-panel-text-color);
}
.RoomGridView > div.focus-ring {
border: 2px solid rgba(134, 193, 165, 1);
border: 2px solid var(--accent-color--darker-5);
border-radius: 12px;
}
.middle-header {
box-sizing: border-box;
flex: 0 0 56px; /* 12 + 32 + 12 to align with filter field + margin */
background: white;
background: var(--background-color-primary);
padding: 0 16px;
border-bottom: 1px solid rgba(245, 245, 245, 0.90);
border-bottom: 1px solid var(--background-color-secondary);
}
.middle-header h2 {
@ -492,7 +507,7 @@ a {
}
.close-middle, .close-session {
background-image: url('icons/chevron-left.svg');
background-image: url('icons/chevron-left.svg?primary=icon-color');
background-position-x: 10px;
}
@ -501,15 +516,11 @@ a {
}
.RoomHeader .room-options {
background-image: url("./icons/vertical-ellipsis.svg");
}
.RoomHeader .room-info {
background-image: url("./icons/info.svg");
background-image: url("./icons/vertical-ellipsis.svg?primary=icon-color");
}
.RoomView_error {
color: red;
color: var(--error-color);
}
.MessageComposer_replyPreview .Timeline_message {
@ -520,9 +531,9 @@ a {
}
.MessageComposer_replyPreview {
background: rgba(245, 245, 245, 0.90);
background: var(--background-color-secondary);
margin: 0px 10px 10px 10px;
box-shadow: 0px 0px 5px #91919169;
box-shadow: 0px 0px 5px var(--background-color-secondary--darker-15);
border-radius: 5px;
}
@ -546,7 +557,7 @@ a {
white-space: nowrap;
overflow: hidden;
background-color: transparent;
background-image: url('icons/clear.svg');
background-image: url('icons/clear.svg?primary=icon-color');
background-repeat: no-repeat;
background-position: center;
background-size: 18px;
@ -554,7 +565,7 @@ a {
}
.MessageComposer_input:first-child {
border-top: 1px solid rgba(245, 245, 245, 0.90);
border-top: 1px solid var(--background-color-secondary);
}
.MessageComposer_input > :not(:first-child) {
@ -564,7 +575,8 @@ a {
.MessageComposer_input > textarea {
border: none;
border-radius: 24px;
background: #F6F6F6;
background: var(--background-color-secondary);
color: inherit;
font-size: 14px;
font-family: "Inter", sans-serif;
resize: none;
@ -586,8 +598,8 @@ a {
text-indent: 200%;
overflow: hidden;
background-color: #03B381;
background-image: url('icons/send.svg');
background-color: var(--accent-color);
background-image: url('icons/send.svg?primary=background-color-primary');
background-repeat: no-repeat;
background-position: center;
align-self: end;
@ -603,7 +615,7 @@ a {
white-space: nowrap;
overflow: hidden;
background-color: transparent;
background-image: url('icons/paperclip.svg');
background-image: url('icons/paperclip.svg?primary=icon-color');
background-repeat: no-repeat;
background-position: center;
}
@ -682,7 +694,7 @@ a {
}
.error {
color: red;
color: var(--error-color);
font-weight: 600;
}
@ -694,21 +706,23 @@ button.link {
cursor: pointer;
margin: -12px;
padding: 12px;
color: inherit;
}
.Settings a, .Settings .link {
color: #03B381;
color: var(--accent-color);
font-weight: 600;
}
.lightbox {
background-color: rgba(0,0,0,0.75);
/* needs transparency support */
background-color: rgba(0, 0, 0, 0.75);
display: grid;
grid-template:
"content close" auto
"content details" 1fr /
1fr auto;
color: white;
color: var(--background-color-primary);
padding: 4px;
}
@ -752,7 +766,7 @@ button.link {
display: block;
grid-area: close;
justify-self: end;
background-image: url('icons/dismiss.svg');
background-image: url('icons/dismiss.svg?primary=fixed-white');
background-position: center;
background-size: 16px;
background-repeat: no-repeat;
@ -770,9 +784,10 @@ button.link {
.menu {
border-radius: 8px;
/* needs transparency support */
box-shadow: 2px 2px 10px rgba(0,0,0,0.5);
padding: 4px;
background-color: white;
background-color: var(--background-color-primary);
list-style: none;
margin: 0;
}
@ -781,6 +796,10 @@ button.link {
margin-bottom: 10px;
}
.menu .menu-item {
color: var(--text-color);
}
.menu button {
border-radius: 4px;
border: none;
@ -793,7 +812,7 @@ button.link {
}
.menu .destructive button {
color: #FF4B55;
color: var(--error-color);
}
.menu .quick-reactions {
@ -842,7 +861,7 @@ button.link {
grid-area: description;
font-size: 1.2rem;
margin: 0;
color: #777;
color: var(--light-text-color);
}
.InviteView_roomAvatar {
@ -877,7 +896,7 @@ button.link {
.RoomArchivedView {
padding: 12px;
background-color: rgba(245, 245, 245, 0.90);
background-color: var(--background-color-secondary);
}
.RoomArchivedView h3 {
@ -914,7 +933,7 @@ button.link {
/* Right Panel */
.RightPanelView {
background: rgba(245, 245, 245, 0.90);
background: var(--background-color-secondary);
}
.RoomDetailsView {
@ -923,7 +942,7 @@ button.link {
}
.RoomDetailsView_id, .MemberDetailsView_id {
color: #737D8C;
color: var(--right-panel-text-color);
font-size: 12px;
}
@ -952,7 +971,7 @@ button.RoomDetailsView_row {
}
button.RoomDetailsView_row::after {
content: url("./icons/chevron-small.svg");
content: url("./icons/chevron-small.svg?primary=icon-color");
margin-left: 12px;
}
@ -972,16 +991,16 @@ button.RoomDetailsView_row::after {
}
.RoomDetailsView_value {
color: #737D8C;
color: var(--right-panel-text-color);
flex: 1;
}
.MemberCount::before {
content: url("./icons/room-members.svg");
content: url("./icons/room-members.svg?primary=icon-color");
}
.EncryptionStatus::before {
content: url("./icons/encryption-status.svg");
content: url("./icons/encryption-status.svg?primary=icon-color");
}
/* Encryption icon next to avatar */
@ -990,8 +1009,8 @@ button.RoomDetailsView_row::after {
width: 52px;
height: 52px;
border-radius: 100%;
background: #737D8C;
border: 3px solid #F2F5F8;
background: var(--right-panel-text-color);
border: 3px solid var(--background-color-secondary);
margin-left: -16px;
}
@ -1001,11 +1020,11 @@ button.RoomDetailsView_row::after {
}
.EncryptionIconView_encrypted {
content: url("./icons/e2ee-normal.svg");
content: url("./icons/e2ee-normal.svg?primary=fixed-white");
}
.EncryptionIconView_unencrypted {
content: url("./icons/e2ee-disabled.svg");
content: url("./icons/e2ee-disabled.svg?primary=fixed-white");
}
.RightPanelView_buttons .button-utility {
@ -1014,11 +1033,11 @@ button.RoomDetailsView_row::after {
}
.RightPanelView_buttons .close {
background-image: url("./icons/clear.svg");
background-image: url("./icons/clear.svg?primary=icon-color");
}
.RightPanelView_buttons .back {
background-image: url("./icons/chevron-thin-left.svg");
background-image: url("./icons/chevron-thin-left.svg?primary=icon-color");
}
/* Memberlist Panel */
@ -1059,7 +1078,7 @@ button.RoomDetailsView_row::after {
.MemberDetailsView_label {
font-size: 12px;
font-weight: 600;
color: #8d99a5;
color: var(--right-panel-text-color);
text-transform: uppercase;
}
@ -1075,7 +1094,7 @@ button.RoomDetailsView_row::after {
}
.MemberDetailsView_options a, .MemberDetailsView_options button {
color: #0dbd8b;
color: var(--accent-color);
text-decoration: none;
margin: 0 0 3px 0;
padding: 0;
@ -1130,8 +1149,8 @@ button.RoomDetailsView_row::after {
width: 64px;
height: 64px;
border-radius: 100%;
background-color: #e1e3e6;
background-image: url('icons/plus.svg');
background-color: var(--icon-background);
background-image: url('icons/plus.svg?primary=icon-color');
background-repeat: no-repeat;
background-position: center;
background-size: 36px;

View file

@ -21,10 +21,10 @@ limitations under the License.
bottom: 16px;
right: 32px;
border-radius: 100%;
border: 1px solid #8d99a5;
background-image: url("./icons/chevron-down.svg");
border: 1px solid var(--background-color-secondary--darker-7);
background-image: url("./icons/chevron-down.svg?primary=icon-color");
background-position: center;
background-color: white;
background-color: var(--background-color-primary--darker-10);
background-repeat: no-repeat;
cursor: pointer;
}
@ -77,6 +77,7 @@ limitations under the License.
}
.Timeline_message:hover:not(.disabled), .Timeline_message.selected, .Timeline_message.menuOpen {
/* needs transparency support */
background-color: rgba(141, 151, 165, 0.1);
border-radius: 4px;
}
@ -119,10 +120,11 @@ limitations under the License.
margin-top: -12px;
margin-right: 4px;
/* button visuals */
border: #ccc 1px solid;
border: var(--background-color-primary--darker-10) 1px solid;
height: 24px;
width: 24px;
background-color: #fff;
background-color: var(--background-color-primary);
color: inherit;
border-radius: 4px;
padding: 0;
text-align: center;
@ -142,12 +144,12 @@ limitations under the License.
.Timeline_messageBody time, .Timeline_messageTime {
font-size: 0.8em;
line-height: normal;
color: #aaa;
color: var(--timeline-time-text-color);
}
.Timeline_messageBody.statusMessage {
font-style: italic;
color: #777;
color: var(--light-text-color);
}
.Timeline_messageBody {
@ -190,7 +192,7 @@ limitations under the License.
}
.Timeline_messageBody a.link {
color: #238cf5;
color: var(--link-color);
text-decoration: none;
}
@ -221,6 +223,7 @@ so the timeline doesn't jump when the image loads */
border-radius: 4px;
display: block;
}
/* stretch the image (to the spacer) on platforms
where we can trust the spacer to always have the correct height,
otherwise the image starts with height 0 and with loading=lazy
@ -249,21 +252,24 @@ only loads when the top comes into view*/
.Timeline_messageBody .media > time,
.Timeline_messageBody .media > .sendStatus {
color: #2e2f32;
color: var(--text-color);
display: block;
padding: 2px;
margin: 4px;
/* needs transparency support */
background-color: rgba(255, 255, 255, 0.75);
border-radius: 4px;
}
.Timeline_messageBody .media > .spacer {
/* TODO: can we implement this with a pseudo element? or perhaps they are not grid items? */
width: 100%;
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
align-self: start;
}
.Timeline_messageBody code, .Timeline_messageBody pre {
background-color: #f8f8f8;
background-color: var(--background-color-secondary);
font-family: monospace;
font-size: 0.9em;
}
@ -275,13 +281,13 @@ only loads when the top comes into view*/
}
.Timeline_messageBody pre {
border: 1px solid rgb(229, 229, 229);
border: 1px solid var(--light-border);
padding: 0.5em;
max-height: 30em;
overflow: auto;
}
.Timeline_messageBody pre > code {
.Timeline_messageBody pre>code {
background-color: unset;
border-radius: unset;
display: block;
@ -291,17 +297,17 @@ only loads when the top comes into view*/
.Timeline_messageBody blockquote {
margin-left: 0;
padding-left: 20px;
border-left: 4px solid rgb(229, 229, 229);
border-left: 4px solid var(--light-border);
}
.Timeline_messageBody table {
border: 1px solid rgb(206, 206, 206);
border: 1px solid var(--background-color-secondary--darker-15);
border-radius: 2px;
border-spacing: 0;
}
.Timeline_messageBody thead th {
border-bottom: 1px solid rgb(206, 206, 206);
border-bottom: 1px solid var(--background-color-secondary--darker-15);
}
.Timeline_messageBody td, .Timeline_messageBody th {
@ -309,14 +315,14 @@ only loads when the top comes into view*/
}
.Timeline_messageBody tbody tr:nth-child(2n) {
background-color: #f6f6f6;
background-color: var(--background-color-secondary);
}
.Timeline_messageBody .pill {
padding: 0px 5px;
border-radius: 15px;
background-color: #f6f6f6;
border: 1px solid rgb(206, 206, 206);
background-color: var(--background-color-secondary);
border: 1px solid var(--background-color-secondary--darker-10);
text-decoration: none;
display: inline-flex;
align-items: center;
@ -331,11 +337,11 @@ only loads when the top comes into view*/
}
.Timeline_message.unsent .Timeline_messageBody {
color: #ccc;
color: var(--light-text-color);
}
.Timeline_message.unverified .Timeline_messageBody {
color: #ff4b55;
color: var(--error-color);
}
.Timeline_messageReactions {
@ -348,22 +354,28 @@ only loads when the top comes into view*/
line-height: 2.0rem;
margin-right: 6px;
padding: 1px 6px;
border: 1px solid #e9edf1;
border: 1px solid var(--light-border);
border-radius: 10px;
background-color: #f3f8fd;
background-color: var(--background-color-secondary);
color: inherit;
cursor: pointer;
user-select: none;
vertical-align: middle;
}
.Timeline_messageReactions button.active {
background-color: #e9fff9;
border-color: #0DBD8B;
background-color: var(--background-color-secondary);
border-color: var(--accent-color);
}
@keyframes glow-reaction-border {
0% { border-color: #e9edf1; }
100% { border-color: #0DBD8B; }
0% {
border-color: var(--background-color-secondary);
}
100% {
border-color: var(--accent-color);
}
}
.Timeline_messageReactions button.active.pending {
@ -377,8 +389,8 @@ only loads when the top comes into view*/
.Timeline_locationLink {
padding: 0px 8px;
border-radius: 16px;
border: 1px solid #e9edf1;
background-color: #f3f8fd;
border: 1px solid var(--light-border);
background-color: var(--background-color-secondary);
text-decoration: none;
display: inline-block;
line-height: 2rem;
@ -394,7 +406,7 @@ only loads when the top comes into view*/
.AnnouncementView > div {
margin: 0 auto;
padding: 10px 20px;
background-color: rgba(245, 245, 245, 0.90);
background-color: var(--background-color-secondary);
text-align: center;
border-radius: 10px;
}

View file

@ -21,6 +21,11 @@ import {TemplateView} from "../general/TemplateView";
import {StaticView} from "../general/StaticView.js";
export class RoomGridView extends TemplateView {
constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
}
render(t, vm) {
const children = [];
for (let i = 0; i < (vm.height * vm.width); i+=1) {
@ -39,7 +44,7 @@ export class RoomGridView extends TemplateView {
} else if (roomVM.kind === "invite") {
return new InviteView(roomVM);
} else {
return new RoomView(roomVM);
return new RoomView(roomVM, this._viewClassForTile);
}
} else {
return new StaticView(t => t.div({className: "room-placeholder"}, [

View file

@ -28,6 +28,7 @@ import {RoomGridView} from "./RoomGridView.js";
import {SettingsView} from "./settings/SettingsView.js";
import {CreateRoomView} from "./CreateRoomView.js";
import {RightPanelView} from "./rightpanel/RightPanelView.js";
import {viewClassForTile} from "./room/common";
export class SessionView extends TemplateView {
render(t, vm) {
@ -42,7 +43,7 @@ export class SessionView extends TemplateView {
t.view(new LeftPanelView(vm.leftPanelViewModel)),
t.mapView(vm => vm.activeMiddleViewModel, () => {
if (vm.roomGridViewModel) {
return new RoomGridView(vm.roomGridViewModel);
return new RoomGridView(vm.roomGridViewModel, viewClassForTile);
} else if (vm.settingsViewModel) {
return new SettingsView(vm.settingsViewModel);
} else if (vm.createRoomViewModel) {
@ -51,7 +52,7 @@ export class SessionView extends TemplateView {
if (vm.currentRoomViewModel.kind === "invite") {
return new InviteView(vm.currentRoomViewModel);
} else if (vm.currentRoomViewModel.kind === "room") {
return new RoomView(vm.currentRoomViewModel);
return new RoomView(vm.currentRoomViewModel, viewClassForTile);
} else if (vm.currentRoomViewModel.kind === "roomBeingCreated") {
return new RoomBeingCreatedView(vm.currentRoomViewModel);
} else {

View file

@ -17,11 +17,11 @@ limitations under the License.
import {TemplateView} from "../../general/TemplateView";
import {Popup} from "../../general/Popup.js";
import {Menu} from "../../general/Menu.js";
import {viewClassForEntry} from "./common"
export class MessageComposer extends TemplateView {
constructor(viewModel) {
constructor(viewModel, viewClassForTile) {
super(viewModel);
this._viewClassForTile = viewClassForTile;
this._input = null;
this._attachmentPopup = null;
this._focusInput = null;
@ -45,8 +45,8 @@ export class MessageComposer extends TemplateView {
this._focusInput = () => this._input.focus();
this.value.on("focus", this._focusInput);
const replyPreview = t.map(vm => vm.replyViewModel, (rvm, t) => {
const View = rvm && viewClassForEntry(rvm);
if (!View) { return null; }
const TileView = rvm && this._viewClassForTile(rvm);
if (!TileView) { return null; }
return t.div({
className: "MessageComposer_replyPreview"
}, [
@ -55,8 +55,8 @@ export class MessageComposer extends TemplateView {
className: "cancel",
onClick: () => this._clearReplyingTo()
}, "Close"),
t.view(new View(rvm, { interactive: false }, "div"))
])
t.view(new TileView(rvm, this._viewClassForTile, { interactive: false }, "div"))
]);
});
const input = t.div({className: "MessageComposer_input"}, [
this._input,

View file

@ -25,15 +25,16 @@ import {RoomArchivedView} from "./RoomArchivedView.js";
import {AvatarView} from "../../AvatarView.js";
export class RoomView extends TemplateView {
constructor(options) {
super(options);
constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
this._optionsPopup = null;
}
render(t, vm) {
let bottomView;
if (vm.composerViewModel.kind === "composer") {
bottomView = new MessageComposer(vm.composerViewModel);
bottomView = new MessageComposer(vm.composerViewModel, this._viewClassForTile);
} else if (vm.composerViewModel.kind === "archived") {
bottomView = new RoomArchivedView(vm.composerViewModel);
}
@ -54,7 +55,7 @@ export class RoomView extends TemplateView {
t.div({className: "RoomView_error"}, vm => vm.error),
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
return timelineViewModel ?
new TimelineView(timelineViewModel) :
new TimelineView(timelineViewModel, this._viewClassForTile) :
new TimelineLoadingView(vm); // vm is just needed for i18n
}),
t.view(bottomView),

View file

@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import type {TileView} from "./common";
import {viewClassForEntry} from "./common";
import {ListView} from "../../general/ListView";
import type {IView} from "../../general/types";
import {TemplateView, Builder} from "../../general/TemplateView";
import {IObservableValue} from "../../general/BaseUpdateView";
import {MissingAttachmentView} from "./timeline/MissingAttachmentView.js";
@ -25,6 +24,17 @@ import {RedactedView} from "./timeline/RedactedView.js";
import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
import {BaseObservableList as ObservableList} from "../../../../../observable/list/BaseObservableList";
export interface TileView extends IView {
readonly value: SimpleTile;
onClick(event: UIEvent);
}
export type TileViewConstructor = new (
tile: SimpleTile,
viewClassForTile: ViewClassForEntryFn,
renderFlags?: { reply?: boolean, interactive?: boolean }
) => TileView;
export type ViewClassForEntryFn = (tile: SimpleTile) => TileViewConstructor;
//import {TimelineViewModel} from "../../../../../domain/session/room/timeline/TimelineViewModel.js";
export interface TimelineViewModel extends IObservableValue {
showJumpDown: boolean;
@ -55,13 +65,17 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
private tilesView?: TilesListView;
private resizeObserver?: ResizeObserver;
constructor(vm: TimelineViewModel, private readonly viewClassForTile: ViewClassForEntryFn) {
super(vm);
}
render(t: Builder<TimelineViewModel>, vm: TimelineViewModel) {
// assume this view will be mounted in the parent DOM straight away
requestAnimationFrame(() => {
// do initial scroll positioning
this.restoreScrollPosition();
});
this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition());
this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition(), this.viewClassForTile);
const root = t.div({className: "Timeline"}, [
t.div({
className: "Timeline_scroller bottom-aligned-scroll",
@ -174,16 +188,13 @@ class TilesListView extends ListView<SimpleTile, TileView> {
private onChanged: () => void;
constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void) {
const options = {
constructor(tiles: ObservableList<SimpleTile>, onChanged: () => void, private readonly viewClassForTile: ViewClassForEntryFn) {
super({
list: tiles,
onItemClick: (tileView, evt) => tileView.onClick(evt),
};
super(options, entry => {
const View = viewClassForEntry(entry);
if (View) {
return new View(entry);
}
}, tile => {
const TileView = viewClassForTile(tile);
return new TileView(tile, viewClassForTile);
});
this.onChanged = onChanged;
}
@ -195,7 +206,7 @@ class TilesListView extends ListView<SimpleTile, TileView> {
onUpdate(index: number, value: SimpleTile, param: any) {
if (param === "shape") {
const ExpectedClass = viewClassForEntry(value);
const ExpectedClass = this.viewClassForTile(value);
const child = this.getChildInstanceByIndex(index);
if (!ExpectedClass || !(child instanceof ExpectedClass)) {
// shape was updated, so we need to recreate the tile view,

View file

@ -24,14 +24,10 @@ import {AnnouncementView} from "./timeline/AnnouncementView.js";
import {RedactedView} from "./timeline/RedactedView.js";
import {SimpleTile} from "../../../../../domain/session/room/timeline/tiles/SimpleTile.js";
import {GapView} from "./timeline/GapView.js";
import type {TileViewConstructor, ViewClassForEntryFn} from "./TimelineView";
export type TileView = GapView | AnnouncementView | TextMessageView |
ImageView | VideoView | FileView | LocationView | MissingAttachmentView | RedactedView;
// TODO: this is what works for a ctor but doesn't actually check we constrain the returned ctors to the types above
type TileViewConstructor = (this: TileView, SimpleTile) => void;
export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | undefined {
switch (entry.shape) {
export function viewClassForTile(vm: SimpleTile): TileViewConstructor {
switch (vm.shape) {
case "gap":
return GapView;
case "announcement":
@ -51,5 +47,7 @@ export function viewClassForEntry(entry: SimpleTile): TileViewConstructor | unde
return MissingAttachmentView;
case "redacted":
return RedactedView;
default:
throw new Error(`Tiles of shape "${vm.shape}" are not supported, check the tileClassForEntry function in the view model`);
}
}

View file

@ -17,6 +17,11 @@ limitations under the License.
import {TemplateView} from "../../../general/TemplateView";
export class AnnouncementView extends TemplateView {
// ignore other arguments
constructor(vm) {
super(vm);
}
render(t) {
return t.li({className: "AnnouncementView"}, t.div(vm => vm.announcement));
}

View file

@ -24,10 +24,11 @@ import {Menu} from "../../../general/Menu.js";
import {ReactionsView} from "./ReactionsView.js";
export class BaseMessageView extends TemplateView {
constructor(value, renderFlags, tagName = "li") {
constructor(value, viewClassForTile, renderFlags, tagName = "li") {
super(value);
this._menuPopup = null;
this._tagName = tagName;
this._viewClassForTile = viewClassForTile;
// TODO An enum could be nice to make code easier to read at call sites.
this._renderFlags = renderFlags;
}

View file

@ -18,6 +18,11 @@ import {TemplateView} from "../../../general/TemplateView";
import {spinner} from "../../../common.js";
export class GapView extends TemplateView {
// ignore other argument
constructor(vm) {
super(vm);
}
render(t) {
const className = {
GapView: true,

View file

@ -16,15 +16,18 @@ limitations under the License.
import {renderStaticAvatar} from "../../../avatar";
import {TemplateView} from "../../../general/TemplateView";
import {viewClassForEntry} from "../common";
export class ReplyPreviewView extends TemplateView {
constructor(vm, viewClassForTile) {
super(vm);
this._viewClassForTile = viewClassForTile;
}
render(t, vm) {
const viewClass = viewClassForEntry(vm);
if (!viewClass) {
const TileView = this._viewClassForTile(vm);
if (!TileView) {
throw new Error(`Shape ${vm.shape} is unrecognized.`)
}
const view = new viewClass(vm, { reply: true, interactive: false });
const view = new TileView(vm, this._viewClassForTile, { reply: true, interactive: false });
return t.div(
{ className: "ReplyPreviewView" },
t.blockquote([

View file

@ -35,7 +35,7 @@ export class TextMessageView extends BaseMessageView {
return new ReplyPreviewError();
}
else if (replyTile) {
return new ReplyPreviewView(replyTile);
return new ReplyPreviewView(replyTile, this._viewClassForTile);
}
else {
return null;

View file

@ -1,9 +1,15 @@
const cssvariables = require("postcss-css-variables");
const flexbugsFixes = require("postcss-flexbugs-fixes");
const compileVariables = require("./scripts/postcss/css-compile-variables");
const urlVariables = require("./scripts/postcss/css-url-to-variables");
const urlProcessor = require("./scripts/postcss/css-url-processor");
const fs = require("fs");
const path = require("path");
const manifest = require("./package.json");
const version = manifest.version;
const compiledVariables = new Map();
const derive = require("./scripts/postcss/color").derive;
const replacer = require("./scripts/postcss/svg-colorizer").buildColorizedSVG;
const commonOptions = {
logLevel: "warn",
@ -32,11 +38,14 @@ const commonOptions = {
css: {
postcss: {
plugins: [
cssvariables({
preserve: (declaration) => {
return declaration.value.indexOf("var(--ios-") == 0;
}
}),
compileVariables({derive, compiledVariables}),
urlVariables({compileVariables}),
urlProcessor({replacer}),
// cssvariables({
// preserve: (declaration) => {
// return declaration.value.indexOf("var(--ios-") == 0;
// }
// }),
// the grid option creates some source fragment that causes the vite warning reporter to crash because
// it wants to log a warning on a line that does not exist in the source fragment.
// autoprefixer({overrideBrowserslist: ["IE 11"], grid: "no-autoplace"}),
@ -46,4 +55,4 @@ const commonOptions = {
}
};
module.exports = commonOptions;
module.exports = { commonOptions, compiledVariables };

View file

@ -1,8 +1,9 @@
const injectWebManifest = require("./scripts/build-plugins/manifest");
const {injectServiceWorker, createPlaceholderValues} = require("./scripts/build-plugins/service-worker");
const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes");
const {defineConfig} = require('vite');
const mergeOptions = require('merge-options').bind({concatArrays: true});
const commonOptions = require("./vite.common-config.js");
const {commonOptions, compiledVariables} = require("./vite.common-config.js");
export default defineConfig(({mode}) => {
const definePlaceholders = createPlaceholderValues(mode);
@ -15,6 +16,13 @@ export default defineConfig(({mode}) => {
sourcemap: true,
},
plugins: [
themeBuilder({
themeConfig: {
themes: {"element": "./src/platform/web/ui/css/themes/element"},
default: "element",
},
compiledVariables
}),
// important this comes before service worker
// otherwise the manifest and the icons it refers to won't be cached
injectWebManifest("assets/manifest.json"),

View file

@ -1,11 +1,13 @@
const path = require("path");
const mergeOptions = require('merge-options');
const commonOptions = require("./vite.common-config.js");
const themeBuilder = require("./scripts/build-plugins/rollup-plugin-build-themes");
const {commonOptions, compiledVariables} = require("./vite.common-config.js");
const pathsToExport = [
"main.js",
"index.css",
"download-sandbox.html"
"download-sandbox.html",
"theme-element-light.css",
"theme-element-dark.css",
];
export default mergeOptions(commonOptions, {
@ -27,4 +29,13 @@ export default mergeOptions(commonOptions, {
}
}
},
plugins: [
themeBuilder({
themeConfig: {
themes: { element: "./src/platform/web/ui/css/themes/element" },
default: "element",
},
compiledVariables,
}),
],
});

View file

@ -1,6 +1,6 @@
const path = require("path");
const mergeOptions = require('merge-options');
const commonOptions = require("./vite.common-config.js");
const {commonOptions} = require("./vite.common-config.js");
const manifest = require("./package.json");
const externalDependencies = Object.keys(manifest.dependencies)