Merge pull request #2507 from element-hq/feature/bma/generateWorldScreenshots
Generate world screenshots and publish them as GitHub pages.
This commit is contained in:
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;
|
||||
}
|
||||
|
||||
.missing {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #0DBD8B;
|
||||
}
|
||||
|
||||
br {
|
||||
display: block;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
#form_container {
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,19 @@
|
||||
*/
|
||||
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 showAllScreenshots = false;
|
||||
|
||||
function addForm() {
|
||||
// Insert the form into the div with id form_container
|
||||
@@ -26,14 +35,14 @@ function addForm() {
|
||||
const languageLabel = document.createElement('label');
|
||||
languageLabel.textContent = 'Languages:';
|
||||
form.appendChild(languageLabel);
|
||||
// Add a check box per entry in the header
|
||||
for (let i = 0; i < header.length; i++){
|
||||
// Add a check box per entry in the dataLanguages
|
||||
for (let i = 0; i < dataLanguages.length; i++){
|
||||
const label = document.createElement('label');
|
||||
const text = document.createTextNode(header[i]);
|
||||
const text = document.createTextNode(dataLanguages[i]);
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.disabled = i == 0;
|
||||
input.name = header[i];
|
||||
input.name = dataLanguages[i];
|
||||
input.checked = visibleLanguages[i] == 1;
|
||||
input.onchange = (e) => {
|
||||
if (e.target.checked) {
|
||||
@@ -47,6 +56,8 @@ function addForm() {
|
||||
label.appendChild(text);
|
||||
form.appendChild(label);
|
||||
}
|
||||
// Add a break line
|
||||
form.appendChild(document.createElement('br'));
|
||||
// Add a label with the text "Width"
|
||||
const label = document.createElement('label');
|
||||
label.textContent = 'Screenshots width:';
|
||||
@@ -64,6 +75,20 @@ function addForm() {
|
||||
addTable();
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -86,55 +111,76 @@ function addTable() {
|
||||
// First item of screenshots contains the languages
|
||||
// Build the languages row
|
||||
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
|
||||
if (visibleLanguages[i] == 0) {
|
||||
if (visibleLanguages[languageIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
const th = document.createElement('th');
|
||||
th.textContent = header[i];
|
||||
th.textContent = dataLanguages[languageIndex];
|
||||
languagesHeaderRow.appendChild(th);
|
||||
}
|
||||
const numVisibleLanguages = languagesHeaderRow.childElementCount
|
||||
// Second item contains the paths
|
||||
// Next items are the data
|
||||
var currentHeaderValue = "";
|
||||
for (let i = 1; i < screenshots.length; i++) {
|
||||
// Add a header for row, if different from previous
|
||||
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));
|
||||
}
|
||||
for (let screenshotIndex = 2; screenshotIndex < screenshots.length; screenshotIndex++) {
|
||||
let englishFile = screenshots[screenshotIndex][0];
|
||||
const tr = document.createElement('tr');
|
||||
for (let j = 0; j < screenshots[i].length; j++) {
|
||||
if (visibleLanguages[j] == 0) {
|
||||
let hasTranslatedFiles = false;
|
||||
for (let languageIndex = 0; languageIndex < dataLanguages.length; languageIndex++) {
|
||||
if (visibleLanguages[languageIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
const td = document.createElement('td');
|
||||
let imageFile = screenshots[i][j];
|
||||
if (imageFile === '') {
|
||||
const text = document.createElement('p');
|
||||
text.textContent = 'No image';
|
||||
td.appendChild(text);
|
||||
} else {
|
||||
if (languageIndex == 0) {
|
||||
const fullFile = `${dataPaths[0]}/${englishFile}.png`;
|
||||
const img = document.createElement('img');
|
||||
img.className = "screenshot";
|
||||
img.src = `../${imageFile}`;
|
||||
img.title = imageFile;
|
||||
img.src = `../${fullFile}`;
|
||||
img.title = fullFile;
|
||||
img.alt = "Missing image";
|
||||
img.width = imageWidth;
|
||||
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);
|
||||
}
|
||||
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(tbody);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
</head>
|
||||
<!-- load script which is in ./html/script.js -->
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
#!/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 re
|
||||
import sys
|
||||
from util import compare
|
||||
|
||||
|
||||
# Read all arguments and return a list of them, this are the languages list.
|
||||
def readArguments():
|
||||
# Return sys.argv without the first argument
|
||||
return sys.argv[1:]
|
||||
|
||||
|
||||
def generateAllScreenshots(languages):
|
||||
# If languages is empty, generate all screenshots
|
||||
if len(languages) == 0:
|
||||
print("Generating all screenshots...")
|
||||
os.system("./gradlew recordPaparazziDebug -PallLanguages")
|
||||
print("Generating all screenshots...")
|
||||
os.system("./gradlew recordPaparazziDebug -PallLanguages")
|
||||
else:
|
||||
tFile = "tests/uitests/src/test/kotlin/ui/T.kt"
|
||||
print("Generating screenshots for languages: %s" % languages)
|
||||
# Patch file T.kt, replace `@TestParameter(value = ["de"]) localeStr: String,` with `@TestParameter(value = ["de", "fr"]) localeStr: String,`
|
||||
with open(tFile, "r") as file:
|
||||
data = file.read()
|
||||
languagesList = ", ".join([f"\"{lang}\"" for lang in languages])
|
||||
data = data.replace("@TestParameter(value = [\"de\"]) localeStr: String,", "@TestParameter(value = [%s]) localeStr: String," % languagesList)
|
||||
with open(tFile, "w") as file:
|
||||
file.write(data)
|
||||
os.system("./gradlew recordPaparazziDebug -PallLanguagesNoEnglish")
|
||||
# Git reset the change on file T.kt
|
||||
os.system("git checkout HEAD -- %s" % tFile)
|
||||
|
||||
|
||||
tFile = "tests/uitests/src/test/kotlin/ui/T.kt"
|
||||
print("Generating screenshots for languages: %s" % languages)
|
||||
# Record the languages one by one, else it's getting too slow
|
||||
for lang in languages:
|
||||
print("Generating screenshots for language: %s" % lang)
|
||||
# Patch file T.kt, replace `@TestParameter(value = ["de"]) localeStr: String,` with `@TestParameter(value = [<the languages>]) localeStr: String,`
|
||||
with open(tFile, "r") as file:
|
||||
data = file.read()
|
||||
data = data.replace("@TestParameter(value = [\"de\"]) localeStr: String,", "@TestParameter(value = [\"%s\"]) localeStr: String," % lang)
|
||||
with open(tFile, "w") as file:
|
||||
file.write(data)
|
||||
os.system("./gradlew recordPaparazziDebug -PallLanguagesNoEnglish")
|
||||
# Git reset the change on file T.kt
|
||||
os.system("git checkout HEAD -- %s" % tFile)
|
||||
|
||||
|
||||
def detectLanguages():
|
||||
@@ -82,38 +99,44 @@ def detectRecordedLanguages():
|
||||
# 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])
|
||||
|
||||
|
||||
def generateJavascriptFile():
|
||||
__doc__ = "Generate a javascript file to load the screenshots"
|
||||
print("Generating javascript file...")
|
||||
languages = detectRecordedLanguages()
|
||||
# First item is the list of languages, adding "en" at the beginning
|
||||
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(
|
||||
os.listdir("tests/uitests/src/test/snapshots/images/"),
|
||||
key=lambda file: file[file.find("_", 6):],
|
||||
)
|
||||
for file in files:
|
||||
fullFile = "./tests/uitests/src/test/snapshots/images/" + file
|
||||
dataForFile = [fullFile]
|
||||
hasAnyTranslatedFile = False
|
||||
# Continue if file contains "-Night", keep only light screenshots (maybe the night screenshots could be on the second column?)
|
||||
if "-Night" in file:
|
||||
continue
|
||||
dataForFile = [file[:-4]]
|
||||
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):
|
||||
hasAnyTranslatedFile = True
|
||||
dataForFile.append(translatedFile)
|
||||
dataForFile.append(1)
|
||||
else:
|
||||
dataForFile.append("")
|
||||
if hasAnyTranslatedFile:
|
||||
data.append(dataForFile)
|
||||
dataForFile.append(0)
|
||||
data.append(dataForFile)
|
||||
|
||||
with open("screenshots/html/data.js", "w") as f:
|
||||
f.write("// Generated file, do not edit\n")
|
||||
f.write("export const screenshots = [\n")
|
||||
for line in data:
|
||||
f.write("[\n")
|
||||
f.write("[")
|
||||
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")
|
||||
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user