Feat: Vite + React (#991)

* remove test_web

* integrate reactjs-vite

* update lint-staged

* simple cors proxy server which handles cookies and redirections

* overrider global fetch with proxy call

* simple examples for set cookies and fetch request

* app bar

* add redux

* loadPlugins.
error: cant load urlencode module

* add ts-node for transforming

* plugin search & select

* plugin slice

* accordion components

* path item & popular novel

* optional for filters

* search novel

* parse novel

* parseChapter

* update cookies

* default filters

* fix: import path

* bundler module resolution

* new json_plugins

* fix: generate multisrc (missing filters)

* fix: host scripts

* fix: plugin code path

* remove typechecking action
This commit is contained in:
nyagami
2024-06-16 18:13:41 +07:00
committed by GitHub
parent a4cb781f32
commit d36e8ec85f
333 changed files with 5857 additions and 28579 deletions
-21
View File
@@ -1,21 +0,0 @@
name: TypeCheck
on:
pull_request:
branches:
- master
jobs:
typechecking:
if: github.repository == 'LNReader/lnreader-plugins'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18"
- name: Install dependencies
run: npm install
- name: Typescript checking
run: tsc
+3 -1
View File
@@ -4,9 +4,11 @@ node_modules
config.json config.json
# generated multisrc .ts files # generated multisrc .ts files
plugins/*/*\[*\]*.ts src/plugins/*/*\[*\]*.ts
# ignore dist stuffs # ignore dist stuffs
.js .js
.dist .dist
.DS_Store .DS_Store
dist
total.svg
-1
View File
@@ -1,3 +1,2 @@
node_modules node_modules
.js .js
test_web
+1 -1
View File
@@ -1,4 +1,4 @@
module.exports = { export default {
tabWidth: 2, tabWidth: 2,
bracketSpacing: true, bracketSpacing: true,
useTabs: false, useTabs: false,
+2 -1
View File
@@ -20,8 +20,9 @@ git reset
rm -r -fo .js rm -r -fo .js
npm run clearMultisrc npm run clearMultisrc
npm run generate npm run generate
npx tsc -m node16
npm run json npm run json
git add -f icons .dist .js/plugins total.svg git add -f public/icons .dist .js/src/plugins total.svg
git commit -m "Host plugins" git commit -m "Host plugins"
git push -f origin $dist git push -f origin $dist
+2 -1
View File
@@ -20,8 +20,9 @@ git reset
rm -rf .js rm -rf .js
npm run clearMultisrc npm run clearMultisrc
npm run generate npm run generate
npx tsc -m node16
npm run json npm run json
git add -f icons .dist .js/plugins total.svg git add -f public/icons .dist .js/src/plugins total.svg
git commit -m "Host plugins" git commit -m "Host plugins"
git push -f origin $dist git push -f origin $dist
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ico.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Plugin testing</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
-175
View File
@@ -1,175 +0,0 @@
require('module-alias/register');
import path from 'path';
import dayjs from 'dayjs';
import express from 'express';
import bodyParser from 'body-parser';
import * as pluginApi from './test_web/api/plugins';
import localizedFormat from 'dayjs/plugin/localizedFormat';
const app = express();
const port = Number(process.argv[2]);
if (!port) {
process.exit(0);
}
const host = 'localhost';
const dirname = path.join(__dirname, '..');
export const getHeaders = () => {
return pluginApi.fetchHeaders;
};
app.use((req, res, next) => {
const xHeaders = req.headers['x-custom-headers'];
if (xHeaders && typeof xHeaders === 'string') {
for (const [headerName, headerValue] of Object.entries(
pluginApi.fetchHeaders,
)) {
if (typeof headerName === 'string' && typeof headerValue === 'string')
delete pluginApi.fetchHeaders[headerName];
}
const headers = JSON.parse(xHeaders);
for (const [headerName, headerValue] of Object.entries(headers)) {
if (typeof headerName === 'string' && typeof headerValue === 'string')
pluginApi.fetchHeaders[headerName] = headerValue;
}
}
if (pluginApi.fetchHeaders.userAgent)
console.log('Custom headers: ', pluginApi.fetchHeaders);
next();
});
app.use(bodyParser.json());
app.use('/static', express.static(path.join(dirname, 'test_web', 'static')));
app.use('/icons', express.static(path.join(dirname, 'icons')));
app.get('/all_plugins', (req, res) => {
const allPlugins = pluginApi.all_plugins();
res.json(allPlugins);
});
app.post('/filters', async (req, res) => {
const filters = await pluginApi.getFilter(req.body['pluginRequirePath']);
res.json(filters || []);
});
app.post('/popularNovels/', async (req, res) => {
const requirePath = req.body['pluginRequirePath'];
const showLatestNovels = req.body['showLatestNovels'] || false;
const defaultFilters = await pluginApi.getFilter(requirePath);
const filters = showLatestNovels
? defaultFilters
: req.body['filters'] || defaultFilters;
const page = parseInt(req.body['page']);
try {
const novels = await pluginApi.popularNovels(requirePath, page || 1, {
showLatestNovels,
filters,
});
res.json(novels);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/searchNovels/', async (req, res) => {
const page = parseInt(req.body['page']) || 1;
try {
const novels = await pluginApi.searchNovels(
req.body['pluginRequirePath'],
page,
req.body['searchTerm'],
);
res.json(novels);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/parseNovel/', async (req, res) => {
try {
const sourceNovel = await pluginApi.parseNovel(
req.body['pluginRequirePath'],
req.body['novelPath'],
);
res.json(sourceNovel);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/hasParsePage', async (req, res) => {
try {
const parsePage = await pluginApi.hasParsePage(
req.body['pluginRequirePath'],
);
res.json({ hasParsePage: parsePage !== undefined });
} catch (err: unknown) {
res.json({ hasParsePage: false });
}
});
app.post('/parsePage/', async (req, res) => {
try {
const sourcePage = await pluginApi.parsePage(
req.body['pluginRequirePath'],
req.body['novelPath'],
req.body['page'],
);
res.json(sourcePage);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/parseChapter/', async (req, res) => {
try {
const chapterText = await pluginApi.parseChapter(
req.body['pluginRequirePath'],
req.body['chapterPath'],
);
res.send(chapterText);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/fetchImage/', async (req, res) => {
try {
const base64 = await pluginApi.fetchImage(
req.body['pluginRequirePath'],
req.body['url'],
);
res.send(base64);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.post('/resolveUrl/', async (req, res) => {
try {
const fullURL = await pluginApi.resolveUrl(
req.body['pluginRequirePath'],
req.body['path'],
req.body['isNovel'],
);
res.send(fullURL);
} catch (err: unknown) {
res.json({ error: String(err) });
}
});
app.get('/', (req, res) => {
res.sendFile(path.join(dirname, 'test_web', 'index.html'));
});
app.listen(port, host, () => {
console.log(`Server listening at http://localhost:${port}`);
});
//Dayjs localization
const language = Intl?.DateTimeFormat()?.resolvedOptions()?.locale || 'en';
require('dayjs/locale/ar');
require('dayjs/locale/de');
require('dayjs/locale/es');
require('dayjs/locale/it');
require('dayjs/locale/pt');
require('dayjs/locale/ru');
require('dayjs/locale/tr');
require('dayjs/locale/uk');
require('dayjs/locale/zh');
dayjs.locale(language);
dayjs.extend(localizedFormat);
-19
View File
@@ -1,19 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@libs/*": [
"./libs/*",
],
"@icons/*": [
"./icons/*",
],
"@plugins/*": [
"./plugins/*",
],
"@scripts/*": [
"./scripts/*",
]
},
},
}
+4024 -5031
View File
File diff suppressed because it is too large Load Diff
+30 -28
View File
@@ -1,15 +1,17 @@
{ {
"name": "lnreader-plugins", "name": "lnreader-plugins",
"version": "2.2.0", "version": "3.0.0",
"description": "Plugins repo for LNReader", "description": "Plugins repo for LNReader",
"main": "index.js", "main": "index.js",
"type": "module",
"scripts": { "scripts": {
"start": "ts-node ./tswatch.ts", "start": "concurrently \"npm run dev\" \"npm run proxy\"",
"dev": "vite",
"proxy": "node proxy_server.js",
"test": "exit 0", "test": "exit 0",
"json": "tsc && node ./.js/scripts/json_plugins.js", "json": "node ./scripts/json_plugins.js",
"generate": "ts-node ./scripts/multisrc/generate.ts", "generate": "node ./scripts/multisrc/generate.js",
"clearMultisrc": "find . -type f -wholename \"*plugins*\\[*\\]*.[t,j]s\" -delete", "clearMultisrc": "find . -type f -wholename \"*plugins*\\[*\\]*.[t,j]s\" -delete",
"less": "npx less ./test_web/static/css/index.less ./test_web/static/css/index.css",
"host-linux": "chmod +x ./host.sh && ./host.sh", "host-linux": "chmod +x ./host.sh && ./host.sh",
"host-windows": "powershell ./host.ps1", "host-windows": "powershell ./host.ps1",
"prettier": "prettier --write \"./**/*.{js,ts}\"", "prettier": "prettier --write \"./**/*.{js,ts}\"",
@@ -19,42 +21,42 @@
"author": "LNReader", "author": "LNReader",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"body-parser": "^1.20.2", "@emotion/react": "^11.11.4",
"browserify": "^17.0.0", "@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.13",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@reduxjs/toolkit": "^2.2.5",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cli-color": "^2.0.3",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"express": "^4.18.2",
"htmlparser2": "^9.1.0", "htmlparser2": "^9.1.0",
"image-size": "^1.1.1",
"less": "^4.2.0",
"module-alias": "^2.2.3",
"protobufjs": "^7.2.6", "protobufjs": "^7.2.6",
"qs": "^6.11.2", "qs": "^6.11.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^9.1.2",
"react-virtuoso": "^4.7.11",
"react-window": "^1.8.10",
"sanitize-html": "^2.12.1", "sanitize-html": "^2.12.1",
"terser": "^5.31.1",
"ts-node": "^10.9.2",
"tsc-alias": "^1.8.8",
"typescript": "^5.3.3",
"urlencode": "^2.0.0" "urlencode": "^2.0.0"
}, },
"_moduleAliases": {
"@libs": "./.js/libs",
"@icons": "./.js/icons",
"@plugins": "./.js/plugins",
"@scripts": "./.js/scripts",
"@typings": "./.js/types"
},
"devDependencies": { "devDependencies": {
"@types/express": "^4.17.21", "@types/qs": "^6.9.15",
"@types/jquery": "^3.5.29", "@types/react": "^18.2.66",
"@types/node": "^20.11.19", "@types/react-dom": "^18.2.22",
"@types/react-window": "^1.8.8",
"@vitejs/plugin-react-swc": "^3.5.0",
"concurrently": "^8.2.2",
"http-proxy": "^1.18.1",
"husky": "^9.0.11", "husky": "^9.0.11",
"image-size": "^1.1.1",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"prettier": "3.2.5", "prettier": "3.2.5",
"tsc-watch": "^6.0.4" "terser": "^5.31.1",
"ts-node": "^10.9.2",
"vite": "^5.2.0"
}, },
"lint-staged": { "lint-staged": {
"**/*.{js,ts}": "prettier --write" "**/*.{js,ts,jsx,tsx}": "prettier --write"
} }
} }
+134
View File
@@ -0,0 +1,134 @@
import http from 'http';
import httpProxy from 'http-proxy';
import url from 'url';
import fs from 'fs';
const CLIENT_HOST = 'http://localhost:3000';
const proxy = httpProxy.createProxyServer({});
const disAllowedHeaders = [
'sec-ch-ua',
'sec-ch-ua-mobile',
'sec-ch-ua-platform',
'sec-fetch-site',
'origin',
'sec-fetch-site',
'sec-fetch-dest',
'pragma',
];
const proxyRequest = (req, res) => {
const _url = new URL(req.url);
proxy.web(req, res, {
target: _url.origin,
selfHandleResponse: true,
followRedirects: true,
});
};
proxy.on('proxyRes', function (proxyRes, req, res) {
var requestState = req.corsAnywhereRequestState;
var statusCode = proxyRes.statusCode;
if (
statusCode === 301 ||
statusCode === 302 ||
statusCode === 303 ||
statusCode === 307 ||
statusCode === 308
) {
req.method = 'GET';
req.headers['content-length'] = '0';
delete req.headers['content-type'];
requestState.location = parsedLocation;
// Remove all listeners (=reset events to initial state)
req.removeAllListeners();
// Remove the error listener so that the ECONNRESET "error" that
// may occur after aborting a request does not propagate to res.
// https://github.com/nodejitsu/node-http-proxy/blob/v1.11.1/lib/http-proxy/passes/web-incoming.js#L134
proxyReq.removeAllListeners('error');
proxyReq.once('error', function catchAndIgnoreError() {});
proxyReq.abort();
// Initiate a new proxy request.
proxyRequest(req, res);
return false;
}
res.setHeader('Access-Control-Allow-Origin', CLIENT_HOST);
res.setHeader('Access-Control-Allow-Credentials', true);
for (const _header in proxyRes.headers) {
res.setHeader(_header, proxyRes.headers[_header]);
}
delete proxyRes.headers['set-cookie'];
delete proxyRes.headers['set-cookie2'];
proxyRes.on('data', function (chunk) {
res.write(chunk);
});
proxyRes.on('end', function () {
res.end();
});
});
var temp_cookies = null;
const cookiesHandler = (req, res) => {
let cookies = '';
req.on('readable', () => {
cookies += req.read();
});
req.on('end', () => {
temp_cookies = cookies;
res.setHeader('Access-Control-Allow-Origin', CLIENT_HOST);
res.setHeader('Access-Control-Allow-Credentials', true);
res.end();
});
};
const pluginPathsHandler = (req, res) => {
const pluginsDir = 'src/plugins';
const pluginPaths = [];
const langNames = fs.readdirSync(pluginsDir);
langNames.forEach(langName => {
const langDir = pluginsDir + '/' + langName;
const pluginNames = fs.readdirSync(langDir);
pluginNames.forEach(pluginName => {
if (!pluginName.startsWith('.') && !pluginName.includes('broken')) {
pluginPaths.push(langName + '/' + pluginName);
}
});
});
res.setHeader('Access-Control-Allow-Origin', CLIENT_HOST);
res.setHeader('Access-Control-Allow-Credentials', true);
res.end(JSON.stringify(pluginPaths));
};
http
.createServer(function (req, res) {
const path = req.url.replace(/^\//, '');
if (path === 'cookies') {
cookiesHandler(req, res);
} else if (path === 'pluginPaths') {
pluginPathsHandler(req, res);
} else {
const _url = new URL(path);
for (const _header of disAllowedHeaders) {
delete req.headers[_header];
}
for (const _header in req.headers) {
if (req.headers[_header]?.includes('localhost')) {
delete req.headers[_header];
}
}
req.headers['sec-fetch-mode'] = 'cors';
if (temp_cookies) {
req.headers['cookie'] = temp_cookies;
}
req.headers.host = _url.host;
req.url = _url.toString();
res.statusCode = 200;
proxyRequest(req, res);
}
})
.listen(3001);

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1005 B

After

Width:  |  Height:  |  Size: 1005 B

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 501 B

Before

Width:  |  Height:  |  Size: 874 B

After

Width:  |  Height:  |  Size: 874 B

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 650 B

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 609 B

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 750 B

After

Width:  |  Height:  |  Size: 750 B

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 1019 B

After

Width:  |  Height:  |  Size: 1019 B

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 737 B

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 289 B

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 977 B

After

Width:  |  Height:  |  Size: 977 B

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More