Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
pierreavn committed Nov 28, 2022
0 parents commit 5f8e156
Show file tree
Hide file tree
Showing 2,981 changed files with 35,470 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
archive/
dist/
.DS_Store
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Gemstone Redmine Theme
🎨 Customizable, free and open-source Redmine theme.
UI/UX by [Tabler UI Kit](https://tabler.io/)

## Configuration
- `targetVersion`: Version for which config has been built
- `debug` (bool): Is debug mode?
- `color` (string): Primary color, see https://preview.tabler.io/docs/colors.html
- `logo` (string): URL of the logo to display
- `menuIcons`: Tabler Icons for the different menu items, see https://tabler-icons.io/

## Setup Readmine with Docker
docker network create redmine-network
docker run --name redmine-db --network redmine-network -e POSTGRES_USER=redmine -e POSTGRES_PASSWORD=xxx -p 5432:5432 -d postgres
docker run -d --name redmine --network redmine-network -p 3000:3000 -v ~/workspace/gemstone/dist:/usr/src/redmine/public/themes/tabler -e REDMINE_DB_POSTGRES=redmine-db -e REDMINE_DB_USERNAME=redmine -e REDMINE_DB_PASSWORD=xxx redmine

## Sources
WebContainers: https://github.com/sveltejs/learn.svelte.dev
Start carver with VSCode Live Server:
- https://github.com/ritwickdey/vscode-live-server/issues/657#issuecomment-959848707
- https://stackoverflow.com/a/72999996/11787313
11 changes: 11 additions & 0 deletions carver/assets/css/carver.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#alert-error {
display: none;
}

a {
cursor: pointer;
}

.input-group-text {
background-color: #fff !important;
}
14 changes: 14 additions & 0 deletions carver/assets/css/tabler.min.css

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions carver/assets/js/carver-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const inputLogo = document.getElementById('input-logo');

/**
* Generate configuration
* @returns
*/
const generateConfig = (targetVersion) => {
return {
targetVersion,
debug: false,
color: document.querySelector('input[name="color"]:checked').value,
logo: inputLogo.value,
menuIcons: {
"projects": "briefcase",
"overview": "dashboard",
"activity": "activity",
"issues": "note",
"time-entries": "clock",
"agile": "layout-kanban",
"gantt": "timeline",
"calendar": "calendar",
"news": "speakerphone",
"documents": "archive",
"wiki": "book-2",
"files": "files",
"settings": "tool"
}
}
}

const previewLogo = () => {
window.open(inputLogo.value, '_blank').focus();
}
118 changes: 118 additions & 0 deletions carver/assets/js/carver-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
var webContainer = null;
var config = null;

const btnSubmit = document.getElementById('btn-submit');

// Parse target version
const urlParams = new URLSearchParams(window.location.search);
var targetVersion = urlParams.get('version');

/**
* Get list of available versions
* @param {*} afterVersion Version after to fetch
*/
const listVersions = async (pages = 5, afterVersion = null) => {
const response = await fetch(`https://${GEMSTONE_CDN}/tags.html${afterVersion ? `?after=${afterVersion}` : ''}`);
const data = await response.text();

const regexp = new RegExp(`href="\\/${GEMSTONE_REPO_OWNER}\\/${GEMSTONE_REPO_NAME}\\/releases\\/tag\\/(\\d+\\.\\d+\\.\\d+)"`, "g");
const versions = [...data.matchAll(regexp)].map(match => match[1]);

let previousVersions = [];
if (versions.length >= 10 && pages > 1) {
previousVersions = await listVersions(pages - 1, versions[versions.length - 1]);
}

return [...versions, ...previousVersions];
}

/**
* Submit form and generate config
*/
const submit = () => {
try {
btnSubmit.classList.add("btn-loading");
btnSubmit.disabled = true;

config = generateConfig(targetVersion);
console.log('Configuration:', config);

if (webContainer) {
buildAndDownload();
}
} catch (err) {
onError(err);
}
}

/**
* Build and Download theme
*/
const buildAndDownload = async () => {
try {
await buildTheme(webContainer, config);
console.log(`Downloaded theme version ${targetVersion}!`);

btnSubmit.classList.remove("btn-loading");
btnSubmit.disabled = false;
} catch (err) {
onError(err);
}
}

const onError = (err) => {
document.getElementById('alert-error').style.display = "block";
btnSubmit.classList.remove("btn-loading");
btnSubmit.disabled = true;
if (err) throw err;
}

/**
* Main script
*/
listVersions().then(versions => {
try {
if (versions.length === 0) {
throw new Error(`No versions found for repo '${GEMSTONE_REPO_OWNER}/${GEMSTONE_REPO_NAME}'!`)
}
console.log('Found versions:', versions);
const latestVersion = versions[0];

// Set default target version
if (!targetVersion || !versions.includes(targetVersion)) {
targetVersion = versions[0];
}

// Append versions to select
const versionSelect = document.getElementById('select-version');
versionSelect.innerHTML = '';
versions.forEach((version, i) => {
const label = i === 0 ? `${version} (latest)` : version;
versionSelect.options[versionSelect.options.length] = new Option(label, version);
});
versionSelect.value = targetVersion;

// Trigger re-initialization on version change
versionSelect.addEventListener('change', () => {
const version = versionSelect.value;
if (version === latestVersion) {
window.location.search = "";
} else {
window.location.search = `?version=${versionSelect.value}`;
}
});

// Invoke container
invokeContainer(targetVersion).then(wc => {
webContainer = wc;
if (config) {
buildAndDownload();
}
}).catch(err => onError(err));

versionSelect.disabled = false;
btnSubmit.disabled = false;
} catch (err) {
onError(err);
}
}).catch(err => onError(err));
3 changes: 3 additions & 0 deletions carver/assets/js/carver-variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const GEMSTONE_CDN = 'gemstone-theme.b-cdn.net';
const GEMSTONE_REPO_OWNER = 'pierreavn';
const GEMSTONE_REPO_NAME = 'gemstone';
132 changes: 132 additions & 0 deletions carver/assets/js/carver-webcontainers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
var WebContainer;

/**
* Invoke and boot a new WebContainer to build theme
* @param {*} version Theme version (tag) to use (if not provided, latest is used)
*/
const invokeContainer = async (version = null) => {
console.log(`-- Invoking WebContainer for version ${version ?? 'master'}:`);
console.log('Downloading repository archive...');
const repositoryRef = version ? `tags/${version}.zip` : `heads/master.zip`;
const content = await Promise.all([
fetch('assets/js/unzip.cjs')
.then((r) => r.text()),
fetch(`https://${GEMSTONE_CDN}/archive/refs/${repositoryRef}`)
.then((r) => r.arrayBuffer()),
]);

console.log('Booting WebContainer...');
const webContainer = await WebContainer.boot();

console.log('Loading files tree...');
const tree = {
'unzip.cjs': {
file: { contents: content[0] },
},
'archive.zip': {
file: { contents: new Uint8Array(content[1]) },
},
};
await webContainer.loadFiles(tree);

// Unzip Archive
// Warning: In this modified unzip.cjs version,
// all files should be in a single parent directory inside archive
console.log('Unzipping repository archive...');
await run(webContainer, "node", ["unzip.cjs"]);

// Install NPM Dependencies
console.log('Installing dependencies...');
await run(webContainer, "npm", ["install"]);

console.log(`WebContainer invoked for ${version ?? 'latest version'}!`);
return webContainer;
}

/**
* Run command inside given webContainer
* @param {*} webContainer
* @param {*} cmd
* @param {*} args
*/
const run = async (webContainer, cmd, args = []) => {
const result = await webContainer.run(
{
command: cmd,
args
},
{
output: (data) => console.log(data),
stderr: (data) => console.error(data),
});

if (await result.onExit !== 0) {
throw new Error('Failed to run command');
}
}

/**
* Build theme with given config into webContainer
* @param {*} webContainer
* @param {*} config
*/
const buildTheme = async (webContainer, config) => {
// Write config file
console.log('Writing config file...');
const tree = {
'src': {
directory: {
'config.json': {
file: { contents: JSON.stringify(config, null, 2) }
}
}
}
};
await webContainer.loadFiles(tree);

// Build theme
console.log('Building theme...');
await run(webContainer, "npm", ["run", "build"]);

// Fetch Archive (Uint8Array)
console.log('Fetching archive...');
const themeArchive = await webContainer.fs.readFile('dist/gemstone.zip');

// Download Archive
console.log('Downloading archive...');
downloadBlob(themeArchive, `gemstone_${config.targetVersion}.zip`, "application/zip");
}

/**
* Download file from blob data
* @param {*} data
* @param {*} fileName
* @param {*} mimeType
*/
const downloadBlob = (data, fileName, mimeType) => {
var blob, url;
blob = new Blob([data], {
type: mimeType
});
url = window.URL.createObjectURL(blob);
downloadURL(url, fileName);
setTimeout(function() {
return window.URL.revokeObjectURL(url);
}, 1000);
}

/**
* Download file from URL
* @param {*} data
* @param {*} fileName
*/
const downloadURL = (data, fileName) => {
var a;
a = document.createElement('a');
a.href = data;
a.download = fileName;
document.body.appendChild(a);
a.style = 'display: none';
a.click();
a.remove();
}
15 changes: 15 additions & 0 deletions carver/assets/js/tabler.min.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions carver/assets/js/unzip.cjs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions carver/assets/js/webcontainers.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5f8e156

Please sign in to comment.