132 changed files with 2628 additions and 954 deletions
@ -0,0 +1,3 @@
|
||||
module.exports = { |
||||
extends: ["./node_modules/matrix-react-sdk/.eslintrc.js"], |
||||
} |
@ -0,0 +1,25 @@
|
||||
Theming Riot |
||||
============ |
||||
|
||||
Themes are a very basic way of providing simple alternative look & feels to the |
||||
riot-web app via CSS & custom imagery. |
||||
|
||||
They are *NOT* co be confused with 'skins', which describe apps which sit on top |
||||
of matrix-react-sdk - e.g. in theory Riot itself is a react-sdk skin. |
||||
As of Jan 2017, skins are not fully supported; riot is the only available skin. |
||||
|
||||
To define a theme for Riot: |
||||
|
||||
1. Pick a name, e.g. `teal`. at time of writing we have `light` and `dark`. |
||||
2. Fork `src/skins/vector/css/themes/dark.scss` to be teal.scss |
||||
3. Fork `src/skins/vector/css/themes/_base.scss` to be _teal.scss |
||||
4. Override variables in _teal.scss as desired. You may wish to delete ones |
||||
which don't differ from _base.scss, to make it clear which are being |
||||
overridden. If every single colour is being changed (as per _dark.scss) |
||||
then you might as well keep them all. |
||||
5. Add the theme to the list of entrypoints in webpack.config.js |
||||
6. Add the theme to the list of themes in matrix-react-sdk's UserSettings.js |
||||
7. Sit back and admire your handywork. |
||||
|
||||
In future, the assets for a theme will probably be gathered together into a |
||||
single directory tree. |
@ -0,0 +1,67 @@
|
||||
/* |
||||
Copyright 2017 Karl Glatz <karl@glatz.biz> |
||||
Copyright 2017 OpenMarket 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. |
||||
*/ |
||||
|
||||
const path = require('path'); |
||||
const electron = require('electron'); |
||||
|
||||
const app = electron.app; |
||||
const Tray = electron.Tray; |
||||
const MenuItem = electron.MenuItem; |
||||
|
||||
let trayIcon = null; |
||||
|
||||
exports.hasTray = function hasTray() { |
||||
return (trayIcon !== null); |
||||
} |
||||
|
||||
exports.create = function (win, config) { |
||||
// no trays on darwin
|
||||
if (process.platform === 'darwin' || trayIcon) { |
||||
return; |
||||
} |
||||
|
||||
const toggleWin = function () { |
||||
if (win.isVisible() && !win.isMinimized()) { |
||||
win.hide(); |
||||
} else { |
||||
if (win.isMinimized()) win.restore(); |
||||
if (!win.isVisible()) win.show(); |
||||
win.focus(); |
||||
} |
||||
}; |
||||
|
||||
const contextMenu = electron.Menu.buildFromTemplate([ |
||||
{ |
||||
label: 'Show/Hide ' + config.brand, |
||||
click: toggleWin |
||||
}, |
||||
{ |
||||
type: 'separator' |
||||
}, |
||||
{ |
||||
label: 'Quit', |
||||
click: function () { |
||||
app.quit(); |
||||
} |
||||
} |
||||
]); |
||||
|
||||
trayIcon = new Tray(config.icon_path); |
||||
trayIcon.setToolTip(config.brand); |
||||
trayIcon.setContextMenu(contextMenu); |
||||
trayIcon.on('click', toggleWin); |
||||
}; |
@ -0,0 +1,13 @@
|
||||
module.exports = { |
||||
plugins: [ |
||||
require("postcss-import")(), |
||||
require("autoprefixer")(), |
||||
require("postcss-simple-vars")(), |
||||
require("postcss-extend")(), |
||||
require("postcss-nested")(), |
||||
require("postcss-mixins")(), |
||||
require("postcss-strip-inline-comments")(), |
||||
], |
||||
"parser": "postcss-scss", |
||||
"local-plugins": true, |
||||
}; |
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// copies the resources into the webapp directory.
|
||||
//
|
||||
|
||||
// cpx includes globbed parts of the filename in the destination, but excludes
|
||||
// common parents. Hence, "res/{a,b}/**": the output will be "dest/a/..." and
|
||||
// "dest/b/...".
|
||||
const COPY_LIST = [ |
||||
["res/{media,vector-icons}/**", "webapp"], |
||||
["src/skins/vector/{fonts,img}/**", "webapp"], |
||||
["node_modules/emojione/assets/svg/*", "webapp/emojione/svg/"], |
||||
["./config.json", "webapp", {directwatch: 1}], |
||||
]; |
||||
|
||||
const parseArgs = require('minimist'); |
||||
const Cpx = require('cpx'); |
||||
const chokidar = require('chokidar'); |
||||
|
||||
const argv = parseArgs( |
||||
process.argv.slice(2), {} |
||||
); |
||||
|
||||
var watch = argv.w; |
||||
var verbose = argv.v; |
||||
|
||||
function errCheck(err) { |
||||
if (err) { |
||||
console.error(err.message); |
||||
process.exit(1); |
||||
} |
||||
} |
||||
|
||||
function next(i, err) { |
||||
errCheck(err); |
||||
|
||||
if (i >= COPY_LIST.length) { |
||||
return; |
||||
} |
||||
|
||||
const ent = COPY_LIST[i]; |
||||
const source = ent[0]; |
||||
const dest = ent[1]; |
||||
const opts = ent[2] || {}; |
||||
|
||||
const cpx = new Cpx.Cpx(source, dest); |
||||
|
||||
if (verbose) { |
||||
cpx.on("copy", (event) => { |
||||
console.log(`Copied: ${event.srcPath} --> ${event.dstPath}`); |
||||
}); |
||||
cpx.on("remove", (event) => { |
||||
console.log(`Removed: ${event.path}`); |
||||
}); |
||||
} |
||||
|
||||
const cb = (err) => {next(i+1, err)}; |
||||
|
||||
if (watch) { |
||||
if (opts.directwatch) { |
||||
// cpx -w creates a watcher for the parent of any files specified,
|
||||
// which in the case of config.json is '.', which inevitably takes
|
||||
// ages to crawl. So we create our own watcher on the files
|
||||
// instead.
|
||||
const copy = () => {cpx.copy(errCheck)}; |
||||
chokidar.watch(source) |
||||
.on('add', copy) |
||||
.on('change', copy) |
||||
.on('ready', cb) |
||||
.on('error', errCheck); |
||||
} else { |
||||
cpx.on('watch-ready', cb); |
||||
cpx.on("watch-error", cb); |
||||
cpx.watch(); |
||||
} |
||||
} else { |
||||
cpx.copy(cb); |
||||
} |
||||
} |
||||
|
||||
next(0); |
@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python |
||||
# |
||||
# download and unpack a riot-web tarball. |
||||
# |
||||
# Allows `bundles` to be extracted to a common directory, and a link to |
||||
# config.json to be added. |
||||
|
||||
from __future__ import print_function |
||||
|
||||
import argparse |
||||
import os |
||||
import os.path |
||||
import subprocess |
||||
import sys |
||||
import tarfile |
||||
|
||||
try: |
||||
# python3 |
||||
from urllib.request import urlretrieve |
||||
except ImportError: |
||||
# python2 |
||||
from urllib import urlretrieve |
||||
|
||||
class DeployException(Exception): |
||||
pass |
||||
|
||||
def create_relative_symlink(linkname, target): |
||||
relpath = os.path.relpath(target, os.path.dirname(linkname)) |
||||
print ("Symlink %s -> %s" % (linkname, relpath)) |
||||
os.symlink(relpath, linkname) |
||||
|
||||
|
||||
def move_bundles(source, dest): |
||||
"""Move the contents of the 'bundles' directory to a common dir |
||||
|
||||
We check that we will not be overwriting anything before we proceed. |
||||
|
||||
Args: |
||||
source (str): path to 'bundles' within the extracted tarball |
||||
dest (str): target common directory |
||||
""" |
||||
|
||||
if not os.path.isdir(dest): |
||||
os.mkdir(dest) |
||||
|
||||
# build a map from source to destination, checking for non-existence as we go. |
||||
renames = {} |
||||
for f in os.listdir(source): |
||||
dst = os.path.join(dest, f) |
||||
if os.path.exists(dst): |
||||
raise DeployException( |
||||
"Not deploying. The bundle includes '%s' which we have previously deployed." |
||||
% f |
||||
) |
||||
renames[os.path.join(source, f)] = dst |
||||
|
||||
for (src, dst) in renames.iteritems(): |
||||
print ("Move %s -> %s" % (src, dst)) |
||||
os.rename(src, dst) |
||||
|
||||
class Deployer: |
||||
def __init__(self): |
||||
self.packages_path = "." |
||||
self.bundles_path = None |
||||
self.should_clean = False |
||||
self.config_location = None |
||||
self.verify_signature = True |
||||
|
||||
def deploy(self, tarball, extract_path): |
||||
"""Download a tarball if necessary, and unpack it |
||||
|
||||
Returns: |
||||
(str) the path to the unpacked deployment |
||||
""" |
||||
print("Deploying %s to %s" % (tarball, extract_path)) |
||||
|
||||
name_str = os.path.basename(tarball).replace(".tar.gz", "") |
||||
extracted_dir = os.path.join(extract_path, name_str) |
||||
if os.path.exists(extracted_dir): |
||||
raise DeployException('Cannot unpack %s: %s already exists' % ( |
||||
tarball, extracted_dir)) |
||||
|
||||
downloaded = False |
||||
if tarball.startswith("http://") or tarball.startswith("https://"): |
||||
tarball = self.download_and_verify(tarball) |
||||
print("Downloaded file: %s" % tarball) |
||||
downloaded = True |
||||
|
||||
try: |
||||
with tarfile.open(tarball) as tar: |
||||
tar.extractall(extract_path) |
||||
finally: |
||||
if self.should_clean and downloaded: |
||||
os.remove(tarball) |
||||
|
||||
print ("Extracted into: %s" % extracted_dir) |
||||
|
||||
if self.config_location: |
||||
create_relative_symlink( |
||||
target=self.config_location, |
||||
linkname=os.path.join(extracted_dir, 'config.json') |
||||
) |
||||
|
||||
if self.bundles_path: |
||||
extracted_bundles = os.path.join(extracted_dir, 'bundles') |
||||
move_bundles(source=extracted_bundles, dest=self.bundles_path) |
||||
|
||||
# replace the (hopefully now empty) extracted_bundles dir with a |
||||
# symlink to the common dir. |
||||
os.rmdir(extracted_bundles) |
||||
create_relative_symlink( |
||||
target=self.bundles_path, |
||||
linkname=extracted_bundles, |
||||
) |
||||
return extracted_dir |
||||
|
||||
def download_and_verify(self, url): |
||||
tarball = self.download_file(url) |
||||
|
||||
if self.verify_signature: |
||||
sigfile = self.download_file(url + ".asc") |
||||
subprocess.check_call(["gpg", "--verify", sigfile, tarball]) |
||||
|
||||
return tarball |
||||
|
||||
def download_file(self, url): |
||||
if not os.path.isdir(self.packages_path): |
||||
os.mkdir(self.packages_path) |
||||
local_filename = os.path.join(self.packages_path, |
||||
url.split('/')[-1]) |
||||
sys.stdout.write("Downloading %s -> %s..." % (url, local_filename)) |
||||
sys.stdout.flush() |
||||
urlretrieve(url, local_filename) |
||||
print ("Done") |
||||
return local_filename |
||||
|
||||
if __name__ == "__main__": |
||||
parser = argparse.ArgumentParser("Deploy a Riot build on a web server.") |
||||
parser.add_argument( |
||||
"-p", "--packages-dir", default="./packages", help=( |
||||
"The directory to download the tarball into. (Default: '%(default)s')" |
||||
) |
||||
) |
||||
parser.add_argument( |
||||
"-e", "--extract-path", default="./deploys", help=( |
||||
"The location to extract .tar.gz files to. (Default: '%(default)s')" |
||||
) |
||||
) |
||||
parser.add_argument( |
||||
"-b", "--bundles-dir", nargs='?', default="./bundles", help=( |
||||
"A directory to move the contents of the 'bundles' directory to. A \ |
||||
symlink to the bundles directory will also be written inside the \ |
||||
extracted tarball. Example: './bundles'. \ |
||||
(Default: '%(default)s')" |
||||
) |
||||
) |
||||
parser.add_argument( |
||||
"-c", "--clean", action="store_true", default=False, help=( |
||||
"Remove .tar.gz files after they have been downloaded and extracted. \ |
||||
(Default: %(default)s)" |
||||
) |
||||
) |
||||
parser.add_argument( |
||||
"--config", nargs='?', default='./config.json', help=( |
||||
"Write a symlink at config.json in the extracted tarball to this \ |
||||
location. (Default: '%(default)s')" |
||||
) |
||||
) |
||||
parser.add_argument( |
||||
"tarball", help=( |
||||
"filename of tarball, or URL to download." |
||||
), |
||||
) |
||||
|
||||
args = parser.parse_args() |
||||
|
||||
deployer = Deployer() |
||||
deployer.packages_path = args.packages_dir |
||||
deployer.bundles_path = args.bundles_dir |
||||
deployer.should_clean = args.clean |
||||
deployer.config_location = args.config |
||||
|
||||
deployer.deploy(args.tarball, args.extract_path) |