Merge pull request #2507 from element-hq/feature/bma/generateWorldScreenshots
Generate world screenshots and publish them as GitHub pages.
This commit is contained in:
commit
148e7c2241
7 changed files with 1173 additions and 1803 deletions
35
.github/workflows/generate_github_pages.yml
vendored
Normal file
35
.github/workflows/generate_github_pages.yml
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
name: Generate GitHub Pages
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# At 00:00 on every Tuesday UTC
|
||||||
|
- cron: '0 0 * * 2'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-github-pages:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Skip in forks
|
||||||
|
if: github.repository == 'element-hq/element-x-android'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use JDK 17
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin' # See 'Supported distributions' for available options
|
||||||
|
java-version: '17'
|
||||||
|
- name: Configure gradle
|
||||||
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
with:
|
||||||
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
- name: Set up Python 3.9
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
- name: Run World screenshots generation script
|
||||||
|
run: |
|
||||||
|
./tools/test/generateWorldScreenshots.py
|
||||||
|
- name: Deploy GitHub Pages
|
||||||
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./screenshots
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -50,10 +50,19 @@ img {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.missing {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
th {
|
th {
|
||||||
background: #0DBD8B;
|
background: #0DBD8B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
br {
|
||||||
|
display: block;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
#form_container {
|
#form_container {
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,19 @@
|
||||||
*/
|
*/
|
||||||
import { screenshots } from './data.js';
|
import { screenshots } from './data.js';
|
||||||
|
|
||||||
const header = screenshots[0];
|
const dataLanguages = screenshots[0];
|
||||||
|
const dataPaths = screenshots[1];
|
||||||
|
|
||||||
|
// Read default visible languages from the fragment
|
||||||
|
const fragment = new URLSearchParams(window.location.hash.substring(1));
|
||||||
|
// Get the wanted languages from the fragment, or default to "de" and "fr", and ensure "en" is always there
|
||||||
|
const wantedLanguages = (fragment.get('languages') ? fragment.get('languages').split(',') : ['de', 'fr']) + ["en"];
|
||||||
|
|
||||||
|
// Map dataLanguages to visibleLanguages, set to 1 if the language is in wantedLanguages, 0 otherwise
|
||||||
|
let visibleLanguages = dataLanguages.map((language) => wantedLanguages.includes(language) ? 1 : 0);
|
||||||
|
|
||||||
let visibleLanguages = header.map((x) => 1);
|
|
||||||
let imageWidth = 300;
|
let imageWidth = 300;
|
||||||
|
let showAllScreenshots = false;
|
||||||
|
|
||||||
function addForm() {
|
function addForm() {
|
||||||
// Insert the form into the div with id form_container
|
// Insert the form into the div with id form_container
|
||||||
|
|
@ -26,14 +35,14 @@ function addForm() {
|
||||||
const languageLabel = document.createElement('label');
|
const languageLabel = document.createElement('label');
|
||||||
languageLabel.textContent = 'Languages:';
|
languageLabel.textContent = 'Languages:';
|
||||||
form.appendChild(languageLabel);
|
form.appendChild(languageLabel);
|
||||||
// Add a check box per entry in the header
|
// Add a check box per entry in the dataLanguages
|
||||||
for (let i = 0; i < header.length; i++){
|
for (let i = 0; i < dataLanguages.length; i++){
|
||||||
const label = document.createElement('label');
|
const label = document.createElement('label');
|
||||||
const text = document.createTextNode(header[i]);
|
const text = document.createTextNode(dataLanguages[i]);
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'checkbox';
|
input.type = 'checkbox';
|
||||||
input.disabled = i == 0;
|
input.disabled = i == 0;
|
||||||
input.name = header[i];
|
input.name = dataLanguages[i];
|
||||||
input.checked = visibleLanguages[i] == 1;
|
input.checked = visibleLanguages[i] == 1;
|
||||||
input.onchange = (e) => {
|
input.onchange = (e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
|
|
@ -47,6 +56,8 @@ function addForm() {
|
||||||
label.appendChild(text);
|
label.appendChild(text);
|
||||||
form.appendChild(label);
|
form.appendChild(label);
|
||||||
}
|
}
|
||||||
|
// Add a break line
|
||||||
|
form.appendChild(document.createElement('br'));
|
||||||
// Add a label with the text "Width"
|
// Add a label with the text "Width"
|
||||||
const label = document.createElement('label');
|
const label = document.createElement('label');
|
||||||
label.textContent = 'Screenshots width:';
|
label.textContent = 'Screenshots width:';
|
||||||
|
|
@ -64,6 +75,20 @@ function addForm() {
|
||||||
addTable();
|
addTable();
|
||||||
};
|
};
|
||||||
form.appendChild(widthInput);
|
form.appendChild(widthInput);
|
||||||
|
// Add a label with the text "Show all screenshots"
|
||||||
|
const label2 = document.createElement('label');
|
||||||
|
label2.textContent = 'Show all screenshots:';
|
||||||
|
label2.title = 'Show all screenshots, including those with no translated versions.';
|
||||||
|
const input2 = document.createElement('input');
|
||||||
|
input2.type = 'checkbox';
|
||||||
|
input2.name = "test";
|
||||||
|
input2.checked = showAllScreenshots;
|
||||||
|
input2.onchange = (e) => {
|
||||||
|
showAllScreenshots = e.target.checked;
|
||||||
|
addTable();
|
||||||
|
};
|
||||||
|
label2.appendChild(input2);
|
||||||
|
form.appendChild(label2);
|
||||||
document.getElementById('form_container').appendChild(form);
|
document.getElementById('form_container').appendChild(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,55 +111,76 @@ function addTable() {
|
||||||
// First item of screenshots contains the languages
|
// First item of screenshots contains the languages
|
||||||
// Build the languages row
|
// Build the languages row
|
||||||
const languagesHeaderRow = document.createElement('tr');
|
const languagesHeaderRow = document.createElement('tr');
|
||||||
for (let i = 0; i < header.length; i++) {
|
for (let languageIndex = 0; languageIndex < dataLanguages.length; languageIndex++) {
|
||||||
// Do not add the language if it is hidden
|
// Do not add the language if it is hidden
|
||||||
if (visibleLanguages[i] == 0) {
|
if (visibleLanguages[languageIndex] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const th = document.createElement('th');
|
const th = document.createElement('th');
|
||||||
th.textContent = header[i];
|
th.textContent = dataLanguages[languageIndex];
|
||||||
languagesHeaderRow.appendChild(th);
|
languagesHeaderRow.appendChild(th);
|
||||||
}
|
}
|
||||||
const numVisibleLanguages = languagesHeaderRow.childElementCount
|
const numVisibleLanguages = languagesHeaderRow.childElementCount
|
||||||
|
// Second item contains the paths
|
||||||
// Next items are the data
|
// Next items are the data
|
||||||
var currentHeaderValue = "";
|
var currentHeaderValue = "";
|
||||||
for (let i = 1; i < screenshots.length; i++) {
|
for (let screenshotIndex = 2; screenshotIndex < screenshots.length; screenshotIndex++) {
|
||||||
// Add a header for row, if different from previous
|
let englishFile = screenshots[screenshotIndex][0];
|
||||||
let name = getNiceName(screenshots[i][0]);
|
|
||||||
if(name != currentHeaderValue) {
|
|
||||||
currentHeaderValue = name;
|
|
||||||
const trHead = document.createElement('tr');
|
|
||||||
const tdHead = document.createElement('td');
|
|
||||||
tdHead.colSpan = numVisibleLanguages;
|
|
||||||
tdHead.className = "view-header";
|
|
||||||
tdHead.textContent = name;
|
|
||||||
trHead.appendChild(tdHead);
|
|
||||||
tbody.appendChild(trHead);
|
|
||||||
tbody.appendChild(languagesHeaderRow.cloneNode(true));
|
|
||||||
}
|
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
for (let j = 0; j < screenshots[i].length; j++) {
|
let hasTranslatedFiles = false;
|
||||||
if (visibleLanguages[j] == 0) {
|
for (let languageIndex = 0; languageIndex < dataLanguages.length; languageIndex++) {
|
||||||
|
if (visibleLanguages[languageIndex] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const td = document.createElement('td');
|
const td = document.createElement('td');
|
||||||
let imageFile = screenshots[i][j];
|
if (languageIndex == 0) {
|
||||||
if (imageFile === '') {
|
const fullFile = `${dataPaths[0]}/${englishFile}.png`;
|
||||||
const text = document.createElement('p');
|
|
||||||
text.textContent = 'No image';
|
|
||||||
td.appendChild(text);
|
|
||||||
} else {
|
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.className = "screenshot";
|
img.className = "screenshot";
|
||||||
img.src = `../${imageFile}`;
|
img.src = `../${fullFile}`;
|
||||||
img.title = imageFile;
|
img.title = fullFile;
|
||||||
img.alt = "Missing image";
|
img.alt = "Missing image";
|
||||||
img.width = imageWidth;
|
img.width = imageWidth;
|
||||||
td.appendChild(img);
|
td.appendChild(img);
|
||||||
|
} else {
|
||||||
|
let hasFile = screenshots[screenshotIndex][languageIndex];
|
||||||
|
if (hasFile === 0) {
|
||||||
|
const text = document.createElement('p');
|
||||||
|
text.className = "missing";
|
||||||
|
text.textContent = 'No image';
|
||||||
|
td.appendChild(text);
|
||||||
|
} else {
|
||||||
|
hasTranslatedFiles = true;
|
||||||
|
// Foreign file is the same as the english file, replacing the language
|
||||||
|
const foreignFile = englishFile.replace("en]", `${dataLanguages[languageIndex]}]`).replace("_S_", "_T_")
|
||||||
|
const fullForeignFile = `${dataPaths[languageIndex]}/${foreignFile}.png`;
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.className = "screenshot";
|
||||||
|
img.src = `../${fullForeignFile}`;
|
||||||
|
img.title = fullForeignFile;
|
||||||
|
img.alt = "Missing image";
|
||||||
|
img.width = imageWidth;
|
||||||
|
td.appendChild(img);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
}
|
}
|
||||||
tbody.appendChild(tr);
|
if (showAllScreenshots || hasTranslatedFiles) {
|
||||||
|
// Add a header for row, if different from previous
|
||||||
|
let name = getNiceName(englishFile);
|
||||||
|
if (name != currentHeaderValue) {
|
||||||
|
currentHeaderValue = name;
|
||||||
|
const trHead = document.createElement('tr');
|
||||||
|
const tdHead = document.createElement('td');
|
||||||
|
tdHead.colSpan = numVisibleLanguages;
|
||||||
|
tdHead.className = "view-header";
|
||||||
|
tdHead.textContent = name;
|
||||||
|
trHead.appendChild(tdHead);
|
||||||
|
tbody.appendChild(trHead);
|
||||||
|
tbody.appendChild(languagesHeaderRow.cloneNode(true));
|
||||||
|
}
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
table.appendChild(thead);
|
table.appendChild(thead);
|
||||||
table.appendChild(tbody);
|
table.appendChild(tbody);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Element X Android Gallery</title>
|
<title>Element X Android Gallery</title>
|
||||||
<link rel="icon" href="../.idea/icon.png">
|
<link rel="icon" href="https://raw.githubusercontent.com/element-hq/element-x-android/develop/.idea/icon.png">
|
||||||
<link rel="stylesheet" href="html/screenshots.css">
|
<link rel="stylesheet" href="html/screenshots.css">
|
||||||
</head>
|
</head>
|
||||||
<!-- load script which is in ./html/script.js -->
|
<!-- load script which is in ./html/script.js -->
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,52 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2024 New Vector Ltd
|
||||||
|
#
|
||||||
|
# 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 os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from util import compare
|
from util import compare
|
||||||
|
|
||||||
|
|
||||||
# Read all arguments and return a list of them, this are the languages list.
|
# Read all arguments and return a list of them, this are the languages list.
|
||||||
def readArguments():
|
def readArguments():
|
||||||
# Return sys.argv without the first argument
|
# Return sys.argv without the first argument
|
||||||
return sys.argv[1:]
|
return sys.argv[1:]
|
||||||
|
|
||||||
|
|
||||||
def generateAllScreenshots(languages):
|
def generateAllScreenshots(languages):
|
||||||
# If languages is empty, generate all screenshots
|
# If languages is empty, generate all screenshots
|
||||||
if len(languages) == 0:
|
if len(languages) == 0:
|
||||||
print("Generating all screenshots...")
|
print("Generating all screenshots...")
|
||||||
os.system("./gradlew recordPaparazziDebug -PallLanguages")
|
os.system("./gradlew recordPaparazziDebug -PallLanguages")
|
||||||
else:
|
else:
|
||||||
tFile = "tests/uitests/src/test/kotlin/ui/T.kt"
|
tFile = "tests/uitests/src/test/kotlin/ui/T.kt"
|
||||||
print("Generating screenshots for languages: %s" % languages)
|
print("Generating screenshots for languages: %s" % languages)
|
||||||
# Patch file T.kt, replace `@TestParameter(value = ["de"]) localeStr: String,` with `@TestParameter(value = ["de", "fr"]) localeStr: String,`
|
# Record the languages one by one, else it's getting too slow
|
||||||
with open(tFile, "r") as file:
|
for lang in languages:
|
||||||
data = file.read()
|
print("Generating screenshots for language: %s" % lang)
|
||||||
languagesList = ", ".join([f"\"{lang}\"" for lang in languages])
|
# Patch file T.kt, replace `@TestParameter(value = ["de"]) localeStr: String,` with `@TestParameter(value = [<the languages>]) localeStr: String,`
|
||||||
data = data.replace("@TestParameter(value = [\"de\"]) localeStr: String,", "@TestParameter(value = [%s]) localeStr: String," % languagesList)
|
with open(tFile, "r") as file:
|
||||||
with open(tFile, "w") as file:
|
data = file.read()
|
||||||
file.write(data)
|
data = data.replace("@TestParameter(value = [\"de\"]) localeStr: String,", "@TestParameter(value = [\"%s\"]) localeStr: String," % lang)
|
||||||
os.system("./gradlew recordPaparazziDebug -PallLanguagesNoEnglish")
|
with open(tFile, "w") as file:
|
||||||
# Git reset the change on file T.kt
|
file.write(data)
|
||||||
os.system("git checkout HEAD -- %s" % tFile)
|
os.system("./gradlew recordPaparazziDebug -PallLanguagesNoEnglish")
|
||||||
|
# Git reset the change on file T.kt
|
||||||
|
os.system("git checkout HEAD -- %s" % tFile)
|
||||||
|
|
||||||
|
|
||||||
def detectLanguages():
|
def detectLanguages():
|
||||||
|
|
@ -82,38 +99,44 @@ def detectRecordedLanguages():
|
||||||
# List all the subfolders of the screenshots folder which contains 2 letters, sorted alphabetically
|
# List all the subfolders of the screenshots folder which contains 2 letters, sorted alphabetically
|
||||||
return sorted([f for f in os.listdir("screenshots") if len(f) == 2])
|
return sorted([f for f in os.listdir("screenshots") if len(f) == 2])
|
||||||
|
|
||||||
|
|
||||||
def generateJavascriptFile():
|
def generateJavascriptFile():
|
||||||
__doc__ = "Generate a javascript file to load the screenshots"
|
__doc__ = "Generate a javascript file to load the screenshots"
|
||||||
print("Generating javascript file...")
|
print("Generating javascript file...")
|
||||||
languages = detectRecordedLanguages()
|
languages = detectRecordedLanguages()
|
||||||
# First item is the list of languages, adding "en" at the beginning
|
# First item is the list of languages, adding "en" at the beginning
|
||||||
data = [["en"] + languages]
|
data = [["en"] + languages]
|
||||||
# If any translated screenshot exists, keep the file
|
# Second item is the path of the containing file
|
||||||
|
data.append(["./tests/uitests/src/test/snapshots/images"] + ["./screenshots/" + l for l in languages])
|
||||||
files = sorted(
|
files = sorted(
|
||||||
os.listdir("tests/uitests/src/test/snapshots/images/"),
|
os.listdir("tests/uitests/src/test/snapshots/images/"),
|
||||||
key=lambda file: file[file.find("_", 6):],
|
key=lambda file: file[file.find("_", 6):],
|
||||||
)
|
)
|
||||||
for file in files:
|
for file in files:
|
||||||
fullFile = "./tests/uitests/src/test/snapshots/images/" + file
|
# Continue if file contains "-Night", keep only light screenshots (maybe the night screenshots could be on the second column?)
|
||||||
dataForFile = [fullFile]
|
if "-Night" in file:
|
||||||
hasAnyTranslatedFile = False
|
continue
|
||||||
|
dataForFile = [file[:-4]]
|
||||||
for l in languages:
|
for l in languages:
|
||||||
translatedFile = "./screenshots/" + l + "/" + file[:3] + "T" + file[4:-7] + l + file[-5:]
|
simpleFile = file[:3] + "T" + file[4:-7] + l + file[-5:-4]
|
||||||
|
translatedFile = "./screenshots/" + l + "/" + simpleFile + ".png"
|
||||||
if os.path.exists(translatedFile):
|
if os.path.exists(translatedFile):
|
||||||
hasAnyTranslatedFile = True
|
dataForFile.append(1)
|
||||||
dataForFile.append(translatedFile)
|
|
||||||
else:
|
else:
|
||||||
dataForFile.append("")
|
dataForFile.append(0)
|
||||||
if hasAnyTranslatedFile:
|
data.append(dataForFile)
|
||||||
data.append(dataForFile)
|
|
||||||
|
|
||||||
with open("screenshots/html/data.js", "w") as f:
|
with open("screenshots/html/data.js", "w") as f:
|
||||||
f.write("// Generated file, do not edit\n")
|
f.write("// Generated file, do not edit\n")
|
||||||
f.write("export const screenshots = [\n")
|
f.write("export const screenshots = [\n")
|
||||||
for line in data:
|
for line in data:
|
||||||
f.write("[\n")
|
f.write("[")
|
||||||
for item in line:
|
for item in line:
|
||||||
f.write("\"" + item + "\",\n")
|
# If item is a string, add quotes
|
||||||
|
if isinstance(item, str):
|
||||||
|
f.write("\"" + item + "\",")
|
||||||
|
else:
|
||||||
|
f.write(str(item) + ",")
|
||||||
f.write("],\n")
|
f.write("],\n")
|
||||||
f.write("];\n")
|
f.write("];\n")
|
||||||
|
|
||||||
|
|
|
||||||
40
tools/test/generateWorldScreenshots.py
Executable file
40
tools/test/generateWorldScreenshots.py
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2024 New Vector Ltd
|
||||||
|
#
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
def detectAllExistingTranslations():
|
||||||
|
# Read all the folder in "libraries/ui-strings/src/main/res"
|
||||||
|
folders = os.listdir("libraries/ui-strings/src/main/res")
|
||||||
|
# Remove the "values" folder
|
||||||
|
folders.remove("values")
|
||||||
|
# Map to keep only the language code
|
||||||
|
folders = list(map(lambda folder: folder[7:], folders))
|
||||||
|
# Map to keep only the string before the "-"
|
||||||
|
folders = list(map(lambda folder: folder.split("-")[0], folders))
|
||||||
|
# Remove duplicates
|
||||||
|
folders = list(set(folders))
|
||||||
|
return folders
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
languages = detectAllExistingTranslations()
|
||||||
|
print ("Will record the screenshots for those languages: %s" % languages)
|
||||||
|
# Run the python script "generateAllScreenshots.py" with the detected languages
|
||||||
|
os.system("./tools/test/generateAllScreenshots.py %s" % " ".join(languages))
|
||||||
|
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue