mirror of
https://github.com/spacedeck/spacedeck-open.git
synced 2026-01-29 22:45:25 +01:00
Compare commits
46 Commits
remove-cdn
...
pr169
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84081876e4 | ||
|
|
6bd7130597 | ||
|
|
5c753cb82a | ||
|
|
ea40f1cc7a | ||
|
|
26329b000b | ||
|
|
20bbb5aa08 | ||
|
|
176b383cbd | ||
|
|
56988fc1c4 | ||
|
|
f23b44e6c2 | ||
|
|
927b0ddac1 | ||
|
|
1cd69e6538 | ||
|
|
ec797709fd | ||
|
|
8d73918d0d | ||
|
|
7e5b988606 | ||
|
|
35c747eabb | ||
|
|
fab692787c | ||
|
|
c1a3700bae | ||
|
|
c64c41f624 | ||
|
|
3fd00bf755 | ||
|
|
e135976299 | ||
|
|
170ed5471d | ||
|
|
1b05aaeaf8 | ||
|
|
a7704875c5 | ||
|
|
60667187f3 | ||
|
|
058c414ae3 | ||
|
|
ba72cf7dc8 | ||
|
|
3d391c571c | ||
|
|
96e9b82fbb | ||
|
|
5a9a79addb | ||
|
|
fbf18839f9 | ||
|
|
65476a0d09 | ||
|
|
18d09b49be | ||
|
|
b2cf8cf336 | ||
|
|
e04eedb2c4 | ||
|
|
72221fcf0b | ||
|
|
531ad2f833 | ||
|
|
ba10889ef8 | ||
|
|
16e926b76a | ||
|
|
0e97945b95 | ||
|
|
581ee8eb04 | ||
|
|
c1e29fb7e2 | ||
|
|
447076845c | ||
|
|
a051fc4e6f | ||
|
|
5a7839ceaa | ||
|
|
26f4da68ea | ||
|
|
c9b6e7c2c8 |
20
.github/ISSUE_TEMPLATE.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
## Expected Behavior
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
|
||||
## Possible Solution
|
||||
|
||||
|
||||
## Steps to Reproduce the Problem
|
||||
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Specifications
|
||||
|
||||
- OS Platform and Distribution (e.g., Linux Ubuntu 16.04):
|
||||
- Node version:
|
||||
- Database engine (e.g., SQLite):
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ public/stylesheets/*.css
|
||||
database.sqlite
|
||||
*.swp
|
||||
*~
|
||||
|
||||
storage/
|
||||
.DS_Store
|
||||
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
||||
nodejs 10.23.1
|
||||
14
.vscode/launch.json
vendored
Normal file
14
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Nodemon",
|
||||
"runtimeExecutable": "${workspaceFolder}/node_modules/nodemon/bin/nodemon.js",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"program": "${workspaceFolder}/spacedeck.js",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
14
Dockerfile
14
Dockerfile
@@ -21,6 +21,20 @@ RUN cd audiowaveform/build/ && cmake -D ENABLE_TESTS=0 -D BUILD_STATIC=1 ..
|
||||
RUN cd audiowaveform/build/ && make
|
||||
RUN cd audiowaveform/build/ && make install
|
||||
|
||||
# install chromium
|
||||
RUN apk add --no-cache \
|
||||
chromium \
|
||||
nss \
|
||||
freetype \
|
||||
freetype-dev \
|
||||
harfbuzz \
|
||||
ca-certificates \
|
||||
ttf-freefont
|
||||
|
||||
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||
|
||||
# install other requirements
|
||||
|
||||
RUN apk add graphicsmagick ffmpeg ffmpeg-dev ghostscript
|
||||
|
||||
10
Gulpfile.js
10
Gulpfile.js
@@ -1,13 +1,15 @@
|
||||
const gulp = require('gulp')
|
||||
const sass = require('gulp-sass')
|
||||
const concat = require('gulp-concat')
|
||||
const gulp = require('gulp');
|
||||
const sass = require('gulp-sass');
|
||||
const concat = require('gulp-concat');
|
||||
const cleanCSS = require('gulp-clean-css');
|
||||
|
||||
gulp.task('styles', function(done) {
|
||||
gulp.src('styles/**/*.scss')
|
||||
.pipe(sass({
|
||||
errLogToConsole: true
|
||||
}))
|
||||
.pipe(cleanCSS())
|
||||
.pipe(gulp.dest('./public/stylesheets/'))
|
||||
.pipe(concat('style.css'))
|
||||
done()
|
||||
})
|
||||
});
|
||||
66
README.md
66
README.md
@@ -45,6 +45,52 @@ To install all node dependencies, run (do this once):
|
||||
|
||||
See [config/default.json](config/default.json). Set `storage_local_path` for a local sqlite database or `storage_region`, `storage_bucket`, `storage_cdn` and `storage_endpoint` for AWS S3. `mail_provider` may be one of `console` or `smtp`. Also, omit a trailing `/` for the `endpoint`.
|
||||
|
||||
|
||||
## Disable DB logs
|
||||
|
||||
```json
|
||||
...
|
||||
"db_logs_disabled": true
|
||||
...
|
||||
```
|
||||
|
||||
## Configure color swatches
|
||||
|
||||
Add a custom array of swatches to your config/default.json.
|
||||
|
||||
**You should include the swatch transparent (rgba(0,0,0,0)) so users can remove the color applied.**
|
||||
|
||||
## Configure default colors
|
||||
You can define text, stroke and fill color in your config/default.json.
|
||||
|
||||
**You also should include the default colors in your custom swatches palette.**
|
||||
|
||||
```json
|
||||
...
|
||||
"spacedeck": {
|
||||
"default_text_color": "#E11F26",
|
||||
"default_stroke_color": "#9E0F13",
|
||||
"default_fill_color": "#64BCCA",
|
||||
"swatches": [
|
||||
{"id":8, "hex":"#000000"},
|
||||
{"id":30, "hex":"rgba(0,0,0,0)"},
|
||||
{"id":31, "hex": "#E11F26"},
|
||||
{"id":32, "hex": "#9E0F13"},
|
||||
{"id":33, "hex": "#64BCCA"},
|
||||
{"id":34, "hex": "#40808A"},
|
||||
{"id":35, "hex": "#036492"},
|
||||
{"id":36, "hex": "#005179"},
|
||||
{"id":37, "hex": "#84427E"},
|
||||
{"id":38, "hex": "#6C3468"},
|
||||
{"id":39, "hex": "#F79B84"},
|
||||
{"id":40, "hex": "#B57362"},
|
||||
{"id":41, "hex": "#E7D45A"},
|
||||
{"id":42, "hex": "#ACA044"}
|
||||
]
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
# Run (web server)
|
||||
|
||||
node spacedeck.js
|
||||
@@ -64,6 +110,26 @@ For advanced media conversion:
|
||||
By default, media files are uploaded to the ```storage``` folder.
|
||||
The database is stored in ```database.sqlite``` by default.
|
||||
|
||||
# Other databases (Not officially supported)
|
||||
|
||||
## Postgres
|
||||
|
||||
Add the [pg](https://www.npmjs.com/package/pg) module and change the config/default.json to
|
||||
|
||||
```
|
||||
"storage_dialect": "postgres",
|
||||
```
|
||||
|
||||
Adapt the other values as needed
|
||||
|
||||
```
|
||||
"storage_host": "localhost",
|
||||
"storage_database": "spacedeck",
|
||||
"storage_username": "username",
|
||||
"storage_password": "password",
|
||||
```
|
||||
|
||||
|
||||
# Run with Docker
|
||||
|
||||
- configure `config/default.json`
|
||||
|
||||
@@ -7,6 +7,13 @@
|
||||
"endpoint": "http://localhost:9666",
|
||||
"invite_code": "top-sekrit",
|
||||
|
||||
"storage_dialect": "sqlite",
|
||||
|
||||
"storage_host": "localhost",
|
||||
"storage_database": "spacedeck",
|
||||
"storage_username": "username",
|
||||
"storage_password": "password",
|
||||
|
||||
"storage_local_path": "./storage",
|
||||
"storage_local_db": "./database.sqlite",
|
||||
"storage_region": "eu-central-1",
|
||||
@@ -18,7 +25,7 @@
|
||||
"redis_mock": true,
|
||||
"redis_host": "localhost",
|
||||
|
||||
"phantom_api_secret": "very_secret_phantom_password",
|
||||
"export_api_secret": "very_secret_export_password",
|
||||
|
||||
"mail_provider": "smtp",
|
||||
"mail_smtp_host": "your.smtp.host",
|
||||
@@ -26,5 +33,6 @@
|
||||
"mail_smtp_secure": true,
|
||||
"mail_smtp_require_tls": true,
|
||||
"mail_smtp_user": "your.smtp.user",
|
||||
"mail_smtp_pass": "your.secret.smtp.password"
|
||||
"mail_smtp_pass": "your.secret.smtp.password",
|
||||
"spacedeck": {}
|
||||
}
|
||||
|
||||
53
docs/nginx_setup.md
Normal file
53
docs/nginx_setup.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Nginx as reverse proxy
|
||||
|
||||
Below theres an example of how the site configuration for nginx as a reverse proxy can look like:
|
||||
|
||||
```
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
servername spacedeck.domain.de
|
||||
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
ssl on;
|
||||
|
||||
server_name spacedeck.domain.de;
|
||||
|
||||
ssl_certificate /etc/ssl/spacedeck.domain.de.cer;
|
||||
ssl_certificate_key /etc/ssl/spacedeck.domain.de.key;
|
||||
|
||||
include ssl_params;
|
||||
|
||||
charset utf-8;
|
||||
client_max_body_size 50m;
|
||||
|
||||
add_header Content-Security-Policy "default-src https: wss:; script-src https: 'unsafe-inline' 'unsafe-eval'; style-src https: 'unsafe-inline'";
|
||||
add_header X-XSS-Protection "1; mode=block;";
|
||||
add_header Referrer-Policy "no-referrer";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:9666;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
location /socket {
|
||||
proxy_pass http://127.0.0.1:9666;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -50,6 +50,8 @@ const convertableAudioTypes = [
|
||||
"audio/x-hx-aac-adts",
|
||||
"audio/aac"];
|
||||
|
||||
// ffmpeg progress
|
||||
var duration = 0, time = 0, progress = 0;
|
||||
|
||||
function getDuration(localFilePath, callback){
|
||||
exec.execFile("ffprobe", ["-show_format", "-of", "json", localFilePath], function(error, stdout, stderr) {
|
||||
@@ -58,6 +60,40 @@ function getDuration(localFilePath, callback){
|
||||
});
|
||||
}
|
||||
|
||||
function getConversionProgress(content){
|
||||
// get duration of source
|
||||
var matches = (content) ? content.match(/Duration: (.*?), start:/) : [];
|
||||
if( matches && matches.length>0 ){
|
||||
var rawDuration = matches[1];
|
||||
// convert rawDuration from 00:00:00.00 to seconds.
|
||||
var ar = rawDuration.split(":").reverse();
|
||||
duration = parseFloat(ar[0]);
|
||||
if (ar[1]) duration += parseInt(ar[1]) * 60;
|
||||
if (ar[2]) duration += parseInt(ar[2]) * 60 * 60;
|
||||
}
|
||||
// get the time
|
||||
matches = content.match(/time=(.*?) bitrate/g);
|
||||
if( matches && matches.length>0 ){
|
||||
var rawTime = matches.pop();
|
||||
// needed if there is more than one match
|
||||
if (Array.isArray(rawTime)){
|
||||
rawTime = rawTime.pop().replace('time=','').replace(' bitrate','');
|
||||
} else {
|
||||
rawTime = rawTime.replace('time=','').replace(' bitrate','');
|
||||
}
|
||||
|
||||
// convert rawTime from 00:00:00.00 to seconds.
|
||||
ar = rawTime.split(":").reverse();
|
||||
time = parseFloat(ar[0]);
|
||||
if (ar[1]) time += parseInt(ar[1]) * 60;
|
||||
if (ar[2]) time += parseInt(ar[2]) * 60 * 60;
|
||||
|
||||
//calculate the progress
|
||||
progress = Math.round((time/duration) * 100);
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
function createWaveform(fileName, localFilePath, callback){
|
||||
var filePathImage = localFilePath + "-" + (new Date().getTime()) + ".png";
|
||||
|
||||
@@ -135,7 +171,7 @@ function convertVideo(fileName, filePath, codec, callback, progressCallback) {
|
||||
ff.stderr.on('data', function (data) {
|
||||
console.log('[ffmpeg-video] stderr: ' + data);
|
||||
if (progressCallback) {
|
||||
progressCallback(data);
|
||||
progressCallback(getConversionProgress(""+data)+"%");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
57
helpers/exporter.js
Normal file
57
helpers/exporter.js
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../models/db');
|
||||
const config = require('config');
|
||||
const puppeteer = require('puppeteer');
|
||||
const os = require('os');
|
||||
|
||||
module.exports = {
|
||||
// type = "pdf" or "png"
|
||||
takeScreenshot: function(space,type,on_success,on_error) {
|
||||
var spaceId = space._id;
|
||||
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
|
||||
|
||||
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
|
||||
|
||||
var timeout = 5000;
|
||||
if (type=="pdf") timeout = 30000;
|
||||
|
||||
space_url += "?api_token="+config.get("export_api_secret");
|
||||
|
||||
console.log("[space-screenshot] url: "+space_url);
|
||||
console.log("[space-screenshot] export_path: "+export_path);
|
||||
|
||||
(async () => {
|
||||
let browser;
|
||||
let page;
|
||||
try {
|
||||
browser = await puppeteer.launch(
|
||||
{
|
||||
headless: true,
|
||||
args: ['--disable-dev-shm-usage', '--no-sandbox']
|
||||
}
|
||||
);
|
||||
page = await browser.newPage();
|
||||
|
||||
page.setDefaultTimeout(timeout);
|
||||
await page.setJavaScriptEnabled(false);
|
||||
await page.goto(space_url, {waitUntil: 'networkidle2'});
|
||||
await page.emulateMediaType('screen');
|
||||
|
||||
if (type=="pdf") {
|
||||
await page.pdf({path: export_path, printBackground: true, width: space.width+'px', height: space.height+'px' });
|
||||
}else{
|
||||
await page.screenshot({path: export_path, printBackground: true});
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
on_success(export_path);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error("[space-screenshot] puppeteer abnormal exit for url "+space_url);
|
||||
on_error();
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../models/db');
|
||||
const config = require('config');
|
||||
const phantom = require('node-phantom-simple');
|
||||
const os = require('os');
|
||||
|
||||
module.exports = {
|
||||
// type = "pdf" or "png"
|
||||
takeScreenshot: function(space,type,on_success,on_error) {
|
||||
var spaceId = space._id;
|
||||
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
|
||||
|
||||
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
|
||||
|
||||
var timeout = 5000;
|
||||
if (type=="pdf") timeout = 30000;
|
||||
|
||||
space_url += "?api_token="+config.get("phantom_api_secret");
|
||||
|
||||
console.log("[space-screenshot] url: "+space_url);
|
||||
console.log("[space-screenshot] export_path: "+export_path);
|
||||
|
||||
var on_success_called = false;
|
||||
|
||||
var on_exit = function(exit_code) {
|
||||
if (exit_code>0) {
|
||||
console.error("phantom abnormal exit for url "+space_url);
|
||||
if (!on_success_called && on_error) {
|
||||
on_error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
return browser.createPage(function (err, page) {
|
||||
console.log("page created, opening ",space_url);
|
||||
|
||||
if (type=="pdf") {
|
||||
var psz = {
|
||||
width: space.width+"px",
|
||||
height: space.height+"px"
|
||||
};
|
||||
page.set('paperSize', psz);
|
||||
}
|
||||
|
||||
page.set('settings.resourceTimeout',timeout);
|
||||
page.set('settings.javascriptEnabled',false);
|
||||
|
||||
return page.open(space_url, function (err,status) {
|
||||
page.render(export_path, function() {
|
||||
on_success_called = true;
|
||||
if (on_success) {
|
||||
on_success(export_path);
|
||||
}
|
||||
page.close();
|
||||
browser.exit();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}, {
|
||||
onExit: on_exit
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -140,10 +140,9 @@ function render_space_as_html(space, artifacts) {
|
||||
var style="html, body, #space { overflow: visible !important; }\n";
|
||||
style+=".wrapper { border: none !important; }\n";
|
||||
|
||||
h='<html>\n<head>\n<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,700,600,800,300|Montserrat:400,700|EB+Garamond|Vollkorn|Fire+Sans|Lato|Roboto|Source+Code+Pro|Ubuntu|Raleway|Playfair+Display|Crimson+Text" rel="stylesheet" type="text/css">\n<link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/>\n<link rel="stylesheet" href="/stylesheets/style.css"><style>'+style+'</style>\n</head>\n<body id="main">\n'+h+"\n</html>\n";
|
||||
h='<html>\n<head>\n<link rel="stylesheet" href="/stylesheets/style.css"><style>'+style+'</style>\n</head>\n<body id="main">\n'+h+"\n</html>\n";
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
exports.render_space_as_html = render_space_as_html;
|
||||
|
||||
|
||||
@@ -320,5 +320,6 @@
|
||||
"follow_present": "Folgen",
|
||||
"mute_present": "Entfolgen",
|
||||
"follow_present_help": "Wenn jemand den Space präsentiert, folgen die anderen Mitglieder automatisch der Präsentation. Mit diesem Knopf lässt sich das an- oder ausschalten.",
|
||||
"media": "Media"
|
||||
"media": "Media",
|
||||
"tool_edit_text": "Text bearbeiten"
|
||||
}
|
||||
@@ -322,6 +322,7 @@
|
||||
"follow_present": "Follow",
|
||||
"mute_present": "Unfollow",
|
||||
"follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.",
|
||||
"export": "export",
|
||||
"media": "Media"
|
||||
"export": "Export",
|
||||
"media": "Media",
|
||||
"tool_edit_text": "Edit Text"
|
||||
}
|
||||
@@ -173,7 +173,7 @@
|
||||
"tool_bullets": "Bullets",
|
||||
"tool_numbers": "Nombres",
|
||||
"color_fill": "Fill",
|
||||
"tool_font": "Font",
|
||||
"tool_font": "Poliça",
|
||||
"color_stroke": "Traçat",
|
||||
"color_text": "Tèxte",
|
||||
"tool_type": "Tipe",
|
||||
@@ -253,7 +253,7 @@
|
||||
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements qu’an creats.",
|
||||
"access_current_members": "Membres actuals",
|
||||
"access_new_members": "Convidar de novèls membres",
|
||||
"access_no_members": "Los membres d’aqueste Espacii apreissaràn aquí.",
|
||||
"access_no_members": "Los membres d’aqueste Espaci apareisseràn aquí.",
|
||||
"comments": "comentaris",
|
||||
"landing_customers": "La fisança de milièr de personas.",
|
||||
"landing_features_title": "Un jòc d'enfants d’utilizar.",
|
||||
@@ -309,7 +309,7 @@
|
||||
"list": "lista",
|
||||
"link": "Ligam",
|
||||
"download_space": "Telecargar espaci",
|
||||
"download_space_as_pdf": "Telecargar espaci PDF",
|
||||
"download_space_as_pdf": "Telecargar espaci coma PDF",
|
||||
"type": "Tipe",
|
||||
"download": "Telecargar",
|
||||
"Previous Zone": "Zòna precedenta",
|
||||
@@ -321,7 +321,7 @@
|
||||
"unlock": "Desverrolhar",
|
||||
"follow_present": "Seguir",
|
||||
"mute_present": "Quitar de seguir",
|
||||
"follow_present_help": "follow_present_help",
|
||||
"follow_present_help": "Se qualqu’un mai presenta aqueste espaci, los demai membres seguiràn automaticament la presentacion. Basculatz l’abonament a la presentacion amb aqueste boton.",
|
||||
"export": "exportar",
|
||||
"media": "Media"
|
||||
"media": "Mèdia"
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ module.exports = (req, res, next) => {
|
||||
// space is private
|
||||
|
||||
// special permission for screenshot/pdf export from backend
|
||||
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
|
||||
if (req.query['api_token'] && req.query['api_token'] == config.get('export_api_secret')) {
|
||||
finalizeReq(space, "viewer");
|
||||
return;
|
||||
}
|
||||
|
||||
26
models/db.js
26
models/db.js
@@ -6,24 +6,28 @@ function sequel_log(a,b,c) {
|
||||
}
|
||||
|
||||
const Sequelize = require('sequelize');
|
||||
const sequelize = new Sequelize('database', 'username', 'password', {
|
||||
host: 'localhost',
|
||||
dialect: 'sqlite',
|
||||
|
||||
const sequelize = new Sequelize(
|
||||
config.get('storage_database'),
|
||||
config.get('storage_username'),
|
||||
config.get('storage_password'),
|
||||
{
|
||||
host: config.get('storage_host'),
|
||||
dialect: config.get('storage_dialect'),
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
},
|
||||
|
||||
// SQLite only
|
||||
storage: config.get('storage_local_db'),
|
||||
logging: sequel_log,
|
||||
|
||||
logging: config.has('db_logs_disabled') ? false : sequel_log,
|
||||
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
|
||||
operatorsAliases: false
|
||||
});
|
||||
operatorsAliases: false,
|
||||
// SQLite only
|
||||
storage: config.get('storage_local_db')
|
||||
}
|
||||
);
|
||||
// https://github.com/sequelize/sequelize/issues/8019#issuecomment-384316346
|
||||
Sequelize.postgres.DECIMAL.parse = function (value) { return parseFloat(value); };
|
||||
|
||||
var User;
|
||||
var Session;
|
||||
|
||||
7466
package-lock.json
generated
Normal file
7466
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -3,7 +3,9 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node spacedeck.js"
|
||||
"start": "node spacedeck.js",
|
||||
"dev": "nodemon spacedeck.js",
|
||||
"styles": "gulp styles"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
@@ -22,19 +24,14 @@
|
||||
"file-type": "^7.6.0",
|
||||
"glob": "7.1.1",
|
||||
"gm": "^1.23.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"helmet": "^3.5.0",
|
||||
"i18n-2": "0.6.3",
|
||||
"log-timestamp": "latest",
|
||||
"mock-aws-s3": "^2.6.0",
|
||||
"moment": "^2.19.3",
|
||||
"morgan": "^1.9.1",
|
||||
"node-phantom-simple": "2.2.4",
|
||||
"node-server-screenshot": "^0.2.1",
|
||||
"nodemailer": "^4.6.7",
|
||||
"phantomjs-prebuilt": "^2.1.16",
|
||||
"puppeteer": "8.0.0",
|
||||
"read-chunk": "^2.1.0",
|
||||
"request": "^2.88.0",
|
||||
"sanitize-html": "^1.11.1",
|
||||
@@ -49,6 +46,13 @@
|
||||
"validator": "7.0.0",
|
||||
"ws": "3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-sass": "^4.0.2",
|
||||
"nodemon": "^2.0.6"
|
||||
},
|
||||
"main": "app.js",
|
||||
"description": "",
|
||||
"directories": {},
|
||||
|
||||
@@ -52,9 +52,18 @@ function setup_directives() {
|
||||
}
|
||||
|
||||
var play_func = function() {
|
||||
video.play();
|
||||
var playPromise = video.play();
|
||||
if (playPromise !== undefined) {
|
||||
playPromise.then(_ => {
|
||||
// Automatic playback started!
|
||||
player_state = "playing";
|
||||
update_view();
|
||||
})
|
||||
.catch(error => {
|
||||
// Auto-play was prevented
|
||||
// Show paused UI.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var pause_func = function() {
|
||||
|
||||
@@ -68,9 +68,9 @@ var SpacedeckSections = {
|
||||
line_height: 1.5,
|
||||
letter_spacing: 0,
|
||||
|
||||
stroke_color: "#000000",
|
||||
fill_color: "#00000000",
|
||||
text_color: "#000000",
|
||||
stroke_color: ENV.options.default_stroke_color ? ENV.options.default_stroke_color : "#000000",
|
||||
fill_color: ENV.options.default_fill_color ? ENV.options.default_fill_color : "#000000",
|
||||
text_color: ENV.options.default_text_color ? ENV.options.default_text_color : "#000000",
|
||||
background_color: "#ffffff",
|
||||
|
||||
padding: 0,
|
||||
@@ -109,7 +109,7 @@ var SpacedeckSections = {
|
||||
color_picker_hue: 127,
|
||||
color_picker_opacity: 255,
|
||||
|
||||
swatches: [
|
||||
swatches: ENV.options.swatches ? ENV.options.swatches : [
|
||||
{id:1, hex:"#ff00ff"},
|
||||
{id:2, hex:"#ffff00"},
|
||||
{id:3, hex:"#00ffff"},
|
||||
@@ -133,18 +133,7 @@ var SpacedeckSections = {
|
||||
{id:26, hex:"#d55c4b"},
|
||||
{id:27, hex:"#6f4021"},
|
||||
{id:29, hex:"#95a5a6"},
|
||||
{id:30, hex:"rgba(0,0,0,0)"},
|
||||
],
|
||||
|
||||
swatches_text: [
|
||||
{id:1, hex:"#9b59b6"},
|
||||
{id:2, hex:"#3498db"},
|
||||
{id:3, hex:"#2ecc71"},
|
||||
{id:4, hex:"#f1c40f"},
|
||||
{id:5, hex:"#e67e22"},
|
||||
{id:6, hex:"#d55c4b"},
|
||||
{id:8, hex:"#ffffff"},
|
||||
{id:10, hex:"#252525"},
|
||||
{id:30, hex:"rgba(0,0,0,0)"}
|
||||
],
|
||||
|
||||
fonts: [
|
||||
@@ -182,7 +171,8 @@ var SpacedeckSections = {
|
||||
toolbar_props_in: false,
|
||||
toolbar_artifacts_x: "-1000px",
|
||||
toolbar_artifacts_y: "-1000px",
|
||||
toolbar_artifacts_in: true
|
||||
toolbar_artifacts_in: true,
|
||||
toolbar_lock_in: false
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -848,7 +838,7 @@ var SpacedeckSections = {
|
||||
if (!a) return false;
|
||||
if (!this.active_space) return false;
|
||||
|
||||
if (this.active_space_role=="viewer" || (a.locked && this.active_space_role!="admin")) {
|
||||
if (this.active_space_role=="viewer" || (a.locked && this.active_space_role=="viewer")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1018,8 +1008,7 @@ var SpacedeckSections = {
|
||||
};
|
||||
},
|
||||
|
||||
update_selection_metrics: function(arts) {
|
||||
|
||||
update_selection_metrics: function(arts, temporary) {
|
||||
if (this.active_tool == "scribble") {
|
||||
this.selection_metrics.count = 1;
|
||||
return;
|
||||
@@ -1052,8 +1041,6 @@ var SpacedeckSections = {
|
||||
// FIXME make sure that menus fit in window
|
||||
this.toolbar_props_x = pp.x+"px";
|
||||
this.toolbar_props_y = pp.y+"px";
|
||||
|
||||
//this.hide_toolbar_artifacts();
|
||||
}
|
||||
|
||||
this.selection_metrics.x1 = sr.x1;
|
||||
@@ -1068,6 +1055,7 @@ var SpacedeckSections = {
|
||||
|
||||
if (!arts) arts = this.selected_artifacts();
|
||||
|
||||
if (!temporary) {
|
||||
this.first_selected_artifact = arts[0];
|
||||
this.selection_metrics.count=arts.length;
|
||||
this.selection_metrics.scribble_selection = false;
|
||||
@@ -1087,6 +1075,7 @@ var SpacedeckSections = {
|
||||
this.selection_metrics.has_link=true;
|
||||
this.insert_link_url = arts[0].meta.link_uri;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
begin_transaction: function() {
|
||||
@@ -1435,13 +1424,13 @@ var SpacedeckSections = {
|
||||
this.color_picker_rgb = rgb_to_hex(rgba.r,rgba.g,rgba.b);
|
||||
},
|
||||
|
||||
update_selected_artifacts: function(change_func, override_locked) {
|
||||
update_selected_artifacts: function(change_func, override_locked, temporary) {
|
||||
var artifacts = this.selected_artifacts(!override_locked);
|
||||
|
||||
if (!artifacts.length) return;
|
||||
|
||||
this.update_artifacts(artifacts, change_func);
|
||||
this.update_selection_metrics();
|
||||
this.update_selection_metrics(null, temporary||false);
|
||||
},
|
||||
|
||||
nudge_selected_artifacts: function(dx, dy, event) {
|
||||
@@ -1922,10 +1911,7 @@ var SpacedeckSections = {
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
delayed_edit_artifact: function(evt) {
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
|
||||
delayed_edit_artifact: function() {
|
||||
var a = this.selected_artifacts()[0];
|
||||
|
||||
var el = $("#ios-focuser-"+a._id);
|
||||
@@ -2086,8 +2072,6 @@ var SpacedeckSections = {
|
||||
if (a.description!=dom.innerHTML) {
|
||||
a.description = dom.innerHTML;
|
||||
|
||||
console.log("new DOM:",dom.innerHTML);
|
||||
|
||||
this.update_board_artifact_viewmodel(a);
|
||||
this.queue_artifact_for_save(a);
|
||||
|
||||
@@ -2526,11 +2510,18 @@ var SpacedeckSections = {
|
||||
},
|
||||
|
||||
show_toolbar_props: function() {
|
||||
if (this.selection_metrics.count==0) return;
|
||||
if (this.selection_metrics.count==0) {
|
||||
this.toolbar_lock_in = false;
|
||||
return;
|
||||
}
|
||||
arts = this.selected_artifacts();
|
||||
// check if selected artifacts are all from the same user
|
||||
let same_user = true;
|
||||
for (var i=0;i<arts.length; i++) {
|
||||
if (arts[i].mime=="x-spacedeck/zone") return;
|
||||
if (arts[i].user_id!==this.user._id) same_user = false;
|
||||
}
|
||||
this.toolbar_lock_in = same_user;
|
||||
this.toolbar_props_in = true;
|
||||
},
|
||||
|
||||
|
||||
@@ -285,6 +285,7 @@ function setup_whiteboard_directives() {
|
||||
$scope.start_adding_placeholder(evt);
|
||||
return;
|
||||
} else if ($scope.active_tool=="pan") {
|
||||
this.deselect();
|
||||
this.start_pan(evt);
|
||||
return;
|
||||
}
|
||||
@@ -723,6 +724,9 @@ function setup_whiteboard_directives() {
|
||||
|
||||
//save_artifact(ars[i], null, $scope.display_saving_error);
|
||||
}
|
||||
|
||||
// update vector handles
|
||||
$scope.update_selection_metrics();
|
||||
}
|
||||
|
||||
if (this.mouse_state == "text_editor") {
|
||||
@@ -941,18 +945,14 @@ function setup_whiteboard_directives() {
|
||||
|
||||
// special case for arrow's 3rd point
|
||||
if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
|
||||
/*control_points[2].dx += dx/2;
|
||||
control_points[2].dy += dy/2; */
|
||||
|
||||
control_points[2].dx = (control_points[0].dx+control_points[1].dx)/2;
|
||||
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
|
||||
}
|
||||
|
||||
return _this.normalize_control_points(control_points, old_a);
|
||||
});
|
||||
}, false, true); // override_locked: false, temporary: true
|
||||
|
||||
} else if (this.mouse_state == "scribble") {
|
||||
|
||||
$scope.update_selected_artifacts(function(a) {
|
||||
var old_a = a;
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ function vec2_angle(v) {
|
||||
function render_vector_drawing(a, padding) {
|
||||
var shape = a.shape || "";
|
||||
var path = [];
|
||||
if(typeof a.control_points == 'string'){
|
||||
a.control_points = JSON.parse(a.control_points);
|
||||
}
|
||||
var p = a.control_points[0];
|
||||
|
||||
if (!p) return "";
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@ const db = require('../../models/db');
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
var space_render = require('../../helpers/space-render');
|
||||
var phantom = require('../../helpers/phantom');
|
||||
var exporter = require('../../helpers/exporter');
|
||||
|
||||
var async = require('async');
|
||||
var moment = require('moment');
|
||||
@@ -51,7 +51,7 @@ router.get('/png', function(req, res, next) {
|
||||
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
|
||||
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
|
||||
|
||||
phantom.takeScreenshot(req.space, "png", function(local_path) {
|
||||
exporter.takeScreenshot(req.space, "png", function(local_path) {
|
||||
var localResizedFilePath = local_path + ".thumb.jpg";
|
||||
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
|
||||
|
||||
@@ -109,7 +109,7 @@ function make_export_filename(space, extension) {
|
||||
router.get('/pdf', function(req, res, next) {
|
||||
var s3_filename = make_export_filename(req.space, "pdf");
|
||||
|
||||
phantom.takeScreenshot(req.space, "pdf", function(local_path) {
|
||||
exporter.takeScreenshot(req.space, "pdf", function(local_path) {
|
||||
uploader.uploadFile(s3_filename, "application/pdf", local_path, function(err, url) {
|
||||
res.status(201).json({
|
||||
url: url
|
||||
|
||||
@@ -9,7 +9,7 @@ var redis = require('../../helpers/redis');
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
var space_render = require('../../helpers/space-render');
|
||||
var phantom = require('../../helpers/phantom');
|
||||
var exporter = require('../../helpers/exporter');
|
||||
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
|
||||
@@ -10,7 +10,7 @@ var redis = require('../../helpers/redis');
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
var space_render = require('../../helpers/space-render');
|
||||
var phantom = require('../../helpers/phantom');
|
||||
var exporter = require('../../helpers/exporter');
|
||||
var payloadConverter = require('../../helpers/artifact_converter');
|
||||
|
||||
var slug = require('slug');
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
const puppeteer = require('puppeteer');
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/db');
|
||||
|
||||
var fs = require('fs');
|
||||
var phantom = require('node-phantom-simple');
|
||||
var md5 = require('md5');
|
||||
|
||||
var express = require('express');
|
||||
@@ -19,40 +19,39 @@ function website_to_png(url,on_success,on_error) {
|
||||
console.log("[webgrabber] url: "+url);
|
||||
console.log("[webgrabber] export_path: "+export_path);
|
||||
|
||||
var on_success_called = false;
|
||||
|
||||
var on_exit = function(exit_code) {
|
||||
if (exit_code>0) {
|
||||
console.log("[phantom-webgrabber] abnormal exit for url "+url);
|
||||
if (!on_success_called && on_error) {
|
||||
on_error();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fs.stat(export_path, function(err, stat) {
|
||||
if (!err) {
|
||||
// file exists
|
||||
console.log("[webgrabber] serving cached snapshot of url: "+url);
|
||||
on_success(export_path);
|
||||
} else {
|
||||
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
|
||||
return browser.createPage(function (err, page) {
|
||||
page.set('settings.resourceTimeout',timeout);
|
||||
page.set('settings.javascriptEnabled',false);
|
||||
(async () => {
|
||||
let browser;
|
||||
let page;
|
||||
try {
|
||||
browser = await puppeteer.launch(
|
||||
{
|
||||
headless: true,
|
||||
args: ['--disable-dev-shm-usage', '--no-sandbox']
|
||||
}
|
||||
);
|
||||
page = await browser.newPage();
|
||||
|
||||
return page.open(url, function(err, status) {
|
||||
console.log("[webgrabber] status: "+status);
|
||||
page.render(export_path, function() {
|
||||
on_success_called = true;
|
||||
page.setDefaultTimeout(timeout);
|
||||
await page.setJavaScriptEnabled(false);
|
||||
await page.goto(url, {waitUntil: 'networkidle2'});
|
||||
await page.emulateMedia('screen');
|
||||
await page.screenshot({path: export_path, printBackground: true});
|
||||
|
||||
await browser.close();
|
||||
on_success(export_path);
|
||||
browser.exit();
|
||||
});
|
||||
});
|
||||
});
|
||||
}, {
|
||||
onExit: on_exit
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.log("[webgrabber] puppeteer abnormal exit for url "+url);
|
||||
on_error();
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
&.hide-text .text { opacity: 0 }
|
||||
&.hide-text .text {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&.locked.selected {
|
||||
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.5) !important;
|
||||
@@ -26,7 +28,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.artifact-text.text-blank [contentEditable=true]:not(.text-editing) p:first-child::after {
|
||||
&.artifact-text.text-blank
|
||||
[contentEditable="true"]:not(.text-editing)
|
||||
p:first-child::after {
|
||||
content: "Double click to edit";
|
||||
opacity: 0.25;
|
||||
}
|
||||
@@ -79,7 +83,6 @@
|
||||
// padding-right: 20px; margin-right: -20px;
|
||||
|
||||
//> * {pointer-events: auto; }
|
||||
|
||||
}
|
||||
|
||||
&:not(.artifact-text) {
|
||||
@@ -142,7 +145,9 @@
|
||||
li {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
&:last-child {margin-bottom: 0em !important; }
|
||||
&:last-child {
|
||||
margin-bottom: 0em !important;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@@ -179,10 +184,14 @@
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em; // compatibility to old system
|
||||
&:last-child {margin-bottom: 0em !important; }
|
||||
&:last-child {
|
||||
margin-bottom: 0em !important;
|
||||
}
|
||||
}
|
||||
|
||||
a {pointer-events: none; }
|
||||
a {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ol {
|
||||
padding: 0;
|
||||
@@ -247,7 +256,7 @@
|
||||
}
|
||||
|
||||
.oembed,
|
||||
div[class*='oembed-'] {
|
||||
div[class*="oembed-"] {
|
||||
height: 100%;
|
||||
.play {
|
||||
position: absolute;
|
||||
@@ -290,7 +299,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.title {font-size: 20px; }
|
||||
.title {
|
||||
font-size: 20px;
|
||||
}
|
||||
.image {
|
||||
height: auto;
|
||||
top: 0;
|
||||
@@ -323,6 +334,13 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-size: cover;
|
||||
background-color: black;
|
||||
|
||||
&.playing {
|
||||
video {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
@@ -340,21 +358,22 @@
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.tl-controls {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
text-align: center;
|
||||
z-index: 2;
|
||||
|
||||
.btn {
|
||||
margin-top: 20px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FIXME: fix later
|
||||
@@ -419,7 +438,6 @@
|
||||
|
||||
.btn {
|
||||
margin-top: 20px;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,19 +464,41 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.bold { font-weight: bold;}
|
||||
&.italic { font-style: italic;}
|
||||
&.underline { text-decoration: underline;}
|
||||
&.strike { text-decoration: line-through;}
|
||||
&.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
&.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
&.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&.strike {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
&.align-top .text-cell {vertical-align: top;}
|
||||
&.align-middle .text-cell {vertical-align: middle;}
|
||||
&.align-bottom .text-cell {vertical-align: bottom;}
|
||||
&.align-top .text-cell {
|
||||
vertical-align: top;
|
||||
}
|
||||
&.align-middle .text-cell {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&.align-bottom .text-cell {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
&.align-left { text-align: left; }
|
||||
&.align-center { text-align: center; }
|
||||
&.align-right { text-align: right; }
|
||||
&.align-justify { text-align: justify; }
|
||||
&.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
&.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
&.align-right {
|
||||
text-align: right;
|
||||
}
|
||||
&.align-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
audio {
|
||||
width: 100%;
|
||||
@@ -475,8 +515,12 @@
|
||||
&.artifact-zone {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10px;
|
||||
&:after {display: none; }
|
||||
.shape {display: none; }
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
.shape {
|
||||
display: none;
|
||||
}
|
||||
.zone {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -490,21 +534,20 @@ body.present-mode {
|
||||
}
|
||||
.artifact {
|
||||
cursor: default !important;
|
||||
.text a { pointer-events: auto !important; }
|
||||
.text a {
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:not(.present-mode) {
|
||||
|
||||
#space {
|
||||
|
||||
.Medium {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.artifact {
|
||||
|
||||
&.selected.text-editing,
|
||||
&.text-editing {
|
||||
cursor: text;
|
||||
@@ -532,19 +575,22 @@ body:not(.present-mode) {
|
||||
//background-color: rgba(40,140,215,0.35);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mouse-scribble, .tool-scribble, .tool-line, .tool-arrow {
|
||||
.mouse-scribble,
|
||||
.tool-scribble,
|
||||
.tool-line,
|
||||
.tool-arrow {
|
||||
cursor: crosshair !important;
|
||||
|
||||
.artifact {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
.artifact:after, .artifact:before {
|
||||
.artifact:after,
|
||||
.artifact:before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -558,21 +604,27 @@ body:not(.present-mode) {
|
||||
}
|
||||
|
||||
.artifact.state-idle {
|
||||
.progress, .progress-text {
|
||||
.progress,
|
||||
.progress-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.artifact.state-processing, .artifact.state-uploading {
|
||||
.artifact.state-processing,
|
||||
.artifact.state-uploading {
|
||||
.progress {
|
||||
height: 100%;
|
||||
padding: 10px;
|
||||
|
||||
background-color: $blue;
|
||||
padding: 4px;
|
||||
opacity: 0.9;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.progress-container {
|
||||
background: $white;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-left: 10px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
.progress-text {
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
@@ -585,33 +637,38 @@ body:not(.present-mode) {
|
||||
color: #888;
|
||||
font-size: 10px;
|
||||
}
|
||||
video, audio, .tl-controls, .timeline {
|
||||
video,
|
||||
audio,
|
||||
.tl-controls,
|
||||
.timeline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.state-processing .spinner {
|
||||
opacity: 1;
|
||||
background-image: url('/images/hourglass.gif');
|
||||
background-image: url("/images/hourglass.gif");
|
||||
}
|
||||
|
||||
.state-uploading .spinner {
|
||||
opacity: 0.8;
|
||||
background-image: url('/images/hourglass.gif');
|
||||
background-image: url("/images/hourglass.gif");
|
||||
}
|
||||
|
||||
.state-idle .spinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
.artifact.image {
|
||||
background-color: transparent;
|
||||
&.state-loading { background-color: rgba(40,140,215,0.05);}
|
||||
&.state-processing { background-color: rgba(107,195,111,0.05);}
|
||||
&.state-loading {
|
||||
background-color: rgba(40, 140, 215, 0.05);
|
||||
}
|
||||
&.state-processing {
|
||||
background-color: rgba(107, 195, 111, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
@import "unicode";
|
||||
|
||||
@-webkit-keyframes appear {
|
||||
0% { opacity: 0;}
|
||||
30% { opacity: 0;}
|
||||
100% { opacity: 1; }
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.avatar {
|
||||
background-color: $blue;
|
||||
@@ -26,7 +31,9 @@
|
||||
#user-root {
|
||||
margin-left: 5px !important;
|
||||
padding: 0px !important;
|
||||
.btn {margin-right: 10px; }
|
||||
.btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar.folder-sidebar {
|
||||
@@ -70,7 +77,9 @@
|
||||
li {
|
||||
display: inline-block;
|
||||
line-height: 44px;
|
||||
&.active span {color: $light; }
|
||||
&.active span {
|
||||
color: $light;
|
||||
}
|
||||
span {
|
||||
color: $medium;
|
||||
display: inline-block;
|
||||
@@ -82,8 +91,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#folder {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
@@ -150,7 +157,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
> .icon {
|
||||
margin-left: -15px
|
||||
margin-left: -15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,15 +219,22 @@
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
|
||||
#folder-grid .item {
|
||||
float: left;
|
||||
width: 100%;
|
||||
|
||||
@media screen and (min-width: 640px) {width: 50%; }
|
||||
@media screen and (min-width: 800px) {width: 33.333333%; }
|
||||
@media screen and (min-width: 1000px) {width: 25%; }
|
||||
@media screen and (min-width: 1200px) {width: 20%; }
|
||||
@media screen and (min-width: 640px) {
|
||||
width: 50%;
|
||||
}
|
||||
@media screen and (min-width: 800px) {
|
||||
width: 33.333333%;
|
||||
}
|
||||
@media screen and (min-width: 1000px) {
|
||||
width: 25%;
|
||||
}
|
||||
@media screen and (min-width: 1200px) {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.compact-hidden {
|
||||
@@ -265,7 +279,9 @@
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.folder-drop {display:none; }
|
||||
.folder-drop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.appear > a,
|
||||
&.creating > a {
|
||||
@@ -349,7 +365,13 @@
|
||||
background-color: $darker;
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.1)));
|
||||
-webkit-mask-image: -webkit-gradient(
|
||||
linear,
|
||||
left top,
|
||||
left bottom,
|
||||
from(rgba(0, 0, 0, 0)),
|
||||
to(rgba(0, 0, 0, 0.1))
|
||||
);
|
||||
background-color: black;
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -394,7 +416,9 @@
|
||||
background-color: white;
|
||||
border-radius: $radius * 2;
|
||||
|
||||
&:active { opacity: 0.95 !important; }
|
||||
&:active {
|
||||
opacity: 0.95 !important;
|
||||
}
|
||||
|
||||
box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid black;
|
||||
@@ -420,6 +444,7 @@
|
||||
overflow: hidden;
|
||||
|
||||
background-color: transparent;
|
||||
background-size: cover;
|
||||
border-top-left-radius: $radius * 2;
|
||||
border-top-right-radius: $radius * 2;
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
<div class="footer">
|
||||
<p>
|
||||
<div class="col-xs-6">
|
||||
© 2020 <a href="https://mntre.com">MNT Research GmbH</a>, Fehlerstr. 8, 12161 Berlin, Germany<br>
|
||||
© 2011–2020 Spacedeck GmbH (in liquidation)<br>
|
||||
Source Code: <a href="https://github.com/mntmn/spacedeck-open">https://github.com/mntmn/spacedeck-open</a>
|
||||
Spacedeck is Free and Open Source Software. The developers are not affiliated with this website.<br>
|
||||
© 2011–2020 <a href="https://github.com/mntmn/spacedeck-open">Spacedeck Open Developers</a><br>
|
||||
<br>
|
||||
Font: <a href="https://rsms.me/inter/">Inter by rsms</a>
|
||||
</div>
|
||||
|
||||
@@ -85,13 +85,13 @@
|
||||
v-bind:class="{text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))}"
|
||||
id="artifact-{{a._id}}">
|
||||
|
||||
|
||||
<div v-if="a.view && a.view.major_type" style="height:100%; width:100%" v-bind:title="(a.editor_name || (a.user && a.user.nickname) || '')">
|
||||
<span v-if="a.locked && is_selected(a)" class="link-wrapper">
|
||||
<span class="btn btn-sm btn-icon btn-round btn-primary">
|
||||
<span class="icon icon-lock-closed"></span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<!-- text -->
|
||||
<div v-if="a.view.major_type == 'text'" class="text" v-bind:style="a.view.inner_style">
|
||||
<div class="text-table">
|
||||
@@ -151,30 +151,18 @@
|
||||
</div>
|
||||
|
||||
<!-- video -->
|
||||
<div v-if="a.view.major_type == 'video'" v-videoplayer="a" class="video" v-bind:style="a.view.inner_style">
|
||||
<video preload="metadata" v-bind:poster="a.view.thumbnail_uri">
|
||||
<div v-if="a.view.major_type == 'video'" class="video">
|
||||
<video preload="metadata" controls="auto" v-bind:poster="a.view.thumbnail_uri">
|
||||
<source v-for="rep in a.view.payload_alternatives" v-bind:src="rep.payload_uri" v-bind:type="rep.mime" />
|
||||
<source v-if="a.view.payload_uri && a.view.mime" v-bind:src="a.view.payload_uri" v-bind:type="a.view.mime" />
|
||||
</video>
|
||||
|
||||
<div class="tl-controls">
|
||||
<div class="btn btn-md btn-toggle btn-round" v-bind:class="{alt:a.player_view.state=='playing'}">
|
||||
<span class="btn-option play">
|
||||
<span class="icon icon-controls-play"></span>
|
||||
</span>
|
||||
|
||||
<span class="btn-option pause">
|
||||
<span class="icon icon-controls-pause"></span>
|
||||
</span>
|
||||
<div class="progress" v-bind:style="{width: a.description}">
|
||||
<div class="progress-container">
|
||||
<h3>
|
||||
{{a.description}}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<span class="btn btn-md btn-round btn-icon stop" v-show="a.player_view.state=='playing' || a.player_view.state=='paused'">
|
||||
<span class="icon icon-controls-stop"></span>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<div class="spinner"></div>
|
||||
<div class="progress" v-bind:style="{width: a.view.progress+'%'}">{{a.description}}</div>
|
||||
</div>
|
||||
|
||||
<!-- audio -->
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="dialog-section">
|
||||
<label class="btn btn-xxl btn-transparent btn-icon">
|
||||
<span class="icon icon-picture-upload"></span>
|
||||
<input type="file" accept="*/*" multiple v-on:change="handle_image_file_upload($event)" id="image_file_upload">
|
||||
<input type="file" multiple v-on:change="handle_image_file_upload($event)" id="image_file_upload">
|
||||
</label>
|
||||
<p>Click to Upload<br/> or drag file(s) anywhere.</p>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
<button class="btn btn-divider"></button>
|
||||
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="lock_selected_artifacts()" v-if="active_space_role=='admin'" title="<%=__("lock")%>">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="lock_selected_artifacts()" v-if="active_space_role=='admin' || toolbar_lock_in" title="<%=__("lock")%>">
|
||||
<span class="icon icon-lock-closed"></span>
|
||||
<span class="icon-label"><%=__("lock")%></span>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="unlock_selected_artifacts()" v-if="active_space_role=='admin'" title="<%=__("unlock")%>">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="unlock_selected_artifacts()" v-if="active_space_role=='admin' || toolbar_lock_in" title="<%=__("unlock")%>">
|
||||
<span class="icon icon-lock-open"></span>
|
||||
<span class="icon-label"><%=__("unlock")%></span>
|
||||
</button>
|
||||
|
||||
@@ -43,7 +43,8 @@
|
||||
|
||||
<div class="dropdown bottom light center">
|
||||
<div class="btn-collapse in">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="handle_insert_image_url()" v-on:touchstart="handle_insert_image_url()" title="<%=__("media")%>">
|
||||
<input type="file" multiple v-on:change="handle_image_file_upload($event)" id="image_file_upload" class="btn btn-transparent btn-icon-labeled" style="position: absolute; z-index: 1; opacity: 0;">
|
||||
<button class="btn btn-transparent btn-icon-labeled" title="<%=__("media")%>">
|
||||
<span class="icon icon-upload"></span>
|
||||
<span class="icon-label" ><%=__("media")%></span>
|
||||
</button>
|
||||
@@ -59,19 +60,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown top left light">
|
||||
<div class="btn-collapse">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='image'}" v-on:click="open_dialog('image')" title="<%=__("image")%>">
|
||||
<span class="icon icon-picture"></span>
|
||||
<span class="icon-label"><%=__("image")%></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="dialog">
|
||||
<%- include("./image.html") %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown top left light" v-bind:class="{open:opened_dialog=='zones'}">
|
||||
<div class="btn-collapse in">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='zones'}" v-on:click="open_dialog('zones')" title="<%=__("tool_zones")%>">
|
||||
|
||||
@@ -97,6 +97,17 @@
|
||||
|
||||
<button class="btn btn-divider"></button>
|
||||
|
||||
<div class="dropdown bottom light center">
|
||||
<div class="btn-collapse" v-bind:class="{in:selection_metrics.contains_text}">
|
||||
<button
|
||||
class="btn btn-icon-labeled btn-transparent"
|
||||
v-on:click="delayed_edit_artifact()">
|
||||
<span class="icon icon-pencil"></span>
|
||||
<span class="icon-label"><%=__("tool_edit_text")%></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='object-options'}">
|
||||
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('object-options')" v-bind:class="{open : opened_dialog=='object-options'}">
|
||||
<span class="icon icon-cogwheel"></span>
|
||||
|
||||
@@ -7,21 +7,18 @@
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
|
||||
<link href="/images/favicon.png" rel="icon" type="image/x-icon" />
|
||||
<link href='https://fonts.googleapis.com/css?family=Inter' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||
|
||||
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
|
||||
|
||||
<script>
|
||||
//window.browser_lang = '< %= locale %>';
|
||||
var ENV = {
|
||||
name: 'development',
|
||||
webHost: location.host,
|
||||
webEndpoint: location.origin,
|
||||
apiEndpoint: location.origin,
|
||||
websocketsEndpoint: location.origin.replace("https:","wss:").replace("http:","ws:")
|
||||
websocketsEndpoint: location.origin.replace("https:","wss:").replace("http:","ws:"),
|
||||
options: <%- config.spacedeck ? JSON.stringify(config.spacedeck) : "{}" %>
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user