46 Commits

Author SHA1 Message Date
dm
84081876e4 update puppeteer to version 8.0.0
space thumbnails use background-size: cover
2021-03-23 23:57:16 +01:00
bckmnn
6bd7130597 fixes pdf export of images 2021-02-20 17:04:58 +01:00
bckmnn
5c753cb82a removed phantom js 2021-02-20 16:43:42 +01:00
bckmnn
ea40f1cc7a added line drawings to pdf 2021-02-14 17:25:41 +01:00
bckmnn
26329b000b replaced phantomjs with puppeteer
started to replace phantomjs with puppeteer
2021-02-14 17:18:50 +01:00
banglashi
20bbb5aa08 Merge pull request #167 from arillo/fix/upload-conversion-progress
fix double %
2021-02-14 12:54:48 +01:00
dm
176b383cbd fix double % 2021-02-14 12:54:14 +01:00
dm
56988fc1c4 set video to left: 0
(so the possible alignment settings doesn't apply)
2021-02-06 01:25:07 +01:00
banglashi
f23b44e6c2 Merge pull request #144 from Mejans/patch-1
Update + correction
2021-02-06 01:14:43 +01:00
banglashi
927b0ddac1 Merge pull request #162 from arillo/feature/convert-percentage
add video conversion progress
2021-02-06 01:13:22 +01:00
dm
1cd69e6538 add gulp clean css to minify css 2021-02-06 01:11:54 +01:00
dm
ec797709fd style convertion progress 2021-02-06 01:04:41 +01:00
dm
8d73918d0d Merge branch 'mnt' of github.com:spacedeck/spacedeck-open into feature/convert-percentage 2021-02-06 00:27:30 +01:00
banglashi
7e5b988606 Merge pull request #159 from arillo/feature/nodemon
use nodemon for development
2021-02-06 00:23:52 +01:00
dm
35c747eabb add storage & .DS_Store to .gitignore 2021-02-06 00:23:01 +01:00
dm
fab692787c add .tool-versions file 2021-02-06 00:22:44 +01:00
dm
c1a3700bae add vscode launch configuration 2021-02-06 00:22:36 +01:00
banglashi
c64c41f624 Merge pull request #161 from arillo/config/disable-db-logs
disable db logs via config/default.json
2021-02-06 00:17:59 +01:00
banglashi
3fd00bf755 Merge branch 'mnt' into config/disable-db-logs 2021-02-06 00:17:46 +01:00
banglashi
e135976299 Merge pull request #158 from arillo/feature/default-color-via-config
allow for custom color swatches via config/default.json
2021-02-06 00:14:22 +01:00
banglashi
170ed5471d Merge pull request #157 from arillo/feature/postgres
Feature/postgres
2021-02-06 00:13:21 +01:00
dm
1b05aaeaf8 add video conversion progress 2021-01-26 01:38:46 +01:00
dm
a7704875c5 check existance of config value 2021-01-21 23:43:47 +01:00
dm
60667187f3 disable db logs via config/default.json 2021-01-21 23:26:08 +01:00
dm
058c414ae3 fix 2021-01-21 09:09:17 +01:00
dm
ba72cf7dc8 use configurable default colors on reset 2021-01-21 08:54:29 +01:00
dm
3d391c571c ammend docs 2021-01-20 09:14:49 +01:00
dm
96e9b82fbb add nodemon 2021-01-18 21:06:54 +01:00
dm
5a9a79addb allow for custom color swatches
reset colors to black on deselect
fix fill_color default black
2021-01-18 20:53:54 +01:00
mntmn
fbf18839f9 Fix Space browsing/editing on touch devices such as iPad (#152)
* touch (tablet): fix deselect when tapping space; add edit text butto
* touch: fix media upload on iDevice
* touch: fix vector transforming (points of arrows, scribbles)

Co-authored-by: Lukas F. Hartmann <lukas@mntre.com>
2021-01-11 13:04:07 +01:00
dm
65476a0d09 remove pg dependency, add docs 2021-01-07 15:41:37 +01:00
dm
18d09b49be unify config, add postgres decimal fix 2021-01-07 15:26:13 +01:00
dm
b2cf8cf336 change to unindented config definition 2021-01-07 14:54:24 +01:00
dm
e04eedb2c4 add postgres support 2021-01-07 14:37:54 +01:00
Lukas F. Hartmann
72221fcf0b fix copyright in footer 2020-12-18 18:17:15 +01:00
Mejans
531ad2f833 Update oc.js 2020-12-02 19:38:16 +01:00
Mejans
ba10889ef8 Update + correction 2020-12-02 19:35:59 +01:00
Lukas F. Hartmann
16e926b76a nuke some leftover traces of googlefonts 2020-11-24 20:15:13 +01:00
mntmn
0e97945b95 Merge branch 'mnt' of github.com:spacedeck/spacedeck-open into mnt 2020-11-23 12:25:56 +01:00
mntmn
581ee8eb04 Merge branch 'arillo-feature/video-native-timeline' into mnt 2020-11-23 12:24:30 +01:00
mntmn
c1e29fb7e2 Merge branch 'feature/video-native-timeline' of https://github.com/arillo/spacedeck-open into arillo-feature/video-native-timeline 2020-11-23 12:23:10 +01:00
Julian David
447076845c Add ISSUE_TEMPLATE.md (#139)
Co-authored-by: julian.mora <julian.mora@bizagi.com>
2020-11-23 12:12:54 +01:00
Christian
a051fc4e6f Create nginx_setup.md (#138) 2020-11-23 12:12:35 +01:00
banglashi
5a7839ceaa allow to lock own artefacts as an editor (#132)
* allow to lock own artefacts as an editor
2020-11-23 12:10:35 +01:00
DM
26f4da68ea show native video controls when playing 2020-11-23 11:56:02 +01:00
mntmn
c9b6e7c2c8 Merge pull request #137 from spacedeck/remove-cdns
Replace google fonts include with locally hosted Inter font
2020-11-21 20:42:28 +01:00
38 changed files with 8157 additions and 16518 deletions

20
.github/ISSUE_TEMPLATE.md vendored Normal file
View 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
View File

@@ -5,4 +5,5 @@ public/stylesheets/*.css
database.sqlite database.sqlite
*.swp *.swp
*~ *~
storage/
.DS_Store

1
.tool-versions Normal file
View File

@@ -0,0 +1 @@
nodejs 10.23.1

14
.vscode/launch.json vendored Normal file
View 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}"
}
]
}

View File

@@ -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
RUN cd audiowaveform/build/ && make install 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 # install other requirements
RUN apk add graphicsmagick ffmpeg ffmpeg-dev ghostscript RUN apk add graphicsmagick ffmpeg ffmpeg-dev ghostscript

View File

@@ -1,13 +1,15 @@
const gulp = require('gulp') const gulp = require('gulp');
const sass = require('gulp-sass') const sass = require('gulp-sass');
const concat = require('gulp-concat') const concat = require('gulp-concat');
const cleanCSS = require('gulp-clean-css');
gulp.task('styles', function(done) { gulp.task('styles', function(done) {
gulp.src('styles/**/*.scss') gulp.src('styles/**/*.scss')
.pipe(sass({ .pipe(sass({
errLogToConsole: true errLogToConsole: true
})) }))
.pipe(cleanCSS())
.pipe(gulp.dest('./public/stylesheets/')) .pipe(gulp.dest('./public/stylesheets/'))
.pipe(concat('style.css')) .pipe(concat('style.css'))
done() done()
}) });

View File

@@ -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`. 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) # Run (web server)
node spacedeck.js node spacedeck.js
@@ -64,6 +110,26 @@ For advanced media conversion:
By default, media files are uploaded to the ```storage``` folder. By default, media files are uploaded to the ```storage``` folder.
The database is stored in ```database.sqlite``` by default. 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 # Run with Docker
- configure `config/default.json` - configure `config/default.json`

View File

@@ -7,6 +7,13 @@
"endpoint": "http://localhost:9666", "endpoint": "http://localhost:9666",
"invite_code": "top-sekrit", "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_path": "./storage",
"storage_local_db": "./database.sqlite", "storage_local_db": "./database.sqlite",
"storage_region": "eu-central-1", "storage_region": "eu-central-1",
@@ -18,7 +25,7 @@
"redis_mock": true, "redis_mock": true,
"redis_host": "localhost", "redis_host": "localhost",
"phantom_api_secret": "very_secret_phantom_password", "export_api_secret": "very_secret_export_password",
"mail_provider": "smtp", "mail_provider": "smtp",
"mail_smtp_host": "your.smtp.host", "mail_smtp_host": "your.smtp.host",
@@ -26,5 +33,6 @@
"mail_smtp_secure": true, "mail_smtp_secure": true,
"mail_smtp_require_tls": true, "mail_smtp_require_tls": true,
"mail_smtp_user": "your.smtp.user", "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
View 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;
}
}
```

View File

@@ -50,6 +50,8 @@ const convertableAudioTypes = [
"audio/x-hx-aac-adts", "audio/x-hx-aac-adts",
"audio/aac"]; "audio/aac"];
// ffmpeg progress
var duration = 0, time = 0, progress = 0;
function getDuration(localFilePath, callback){ function getDuration(localFilePath, callback){
exec.execFile("ffprobe", ["-show_format", "-of", "json", localFilePath], function(error, stdout, stderr) { 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){ function createWaveform(fileName, localFilePath, callback){
var filePathImage = localFilePath + "-" + (new Date().getTime()) + ".png"; var filePathImage = localFilePath + "-" + (new Date().getTime()) + ".png";
@@ -135,7 +171,7 @@ function convertVideo(fileName, filePath, codec, callback, progressCallback) {
ff.stderr.on('data', function (data) { ff.stderr.on('data', function (data) {
console.log('[ffmpeg-video] stderr: ' + data); console.log('[ffmpeg-video] stderr: ' + data);
if (progressCallback) { if (progressCallback) {
progressCallback(data); progressCallback(getConversionProgress(""+data)+"%");
} }
}); });

57
helpers/exporter.js Normal file
View 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();
}
})();
}
};

View File

@@ -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
});
}
};

View File

@@ -103,7 +103,7 @@ function render_space_as_html(space, artifacts) {
walk(dom("#space")[0],0); walk(dom("#space")[0],0);
//console.log("compiled template: \n"+compiled_js); //console.log("compiled template: \n"+compiled_js);
} }
// -------- // --------
var mouse_state = "idle"; var mouse_state = "idle";
var active_tool = "pointer"; var active_tool = "pointer";
@@ -136,14 +136,13 @@ function render_space_as_html(space, artifacts) {
} catch (e) { } catch (e) {
console.error("error rendering space "+space._id+" as html: "+e); console.error("error rendering space "+space._id+" as html: "+e);
} }
var style="html, body, #space { overflow: visible !important; }\n"; var style="html, body, #space { overflow: visible !important; }\n";
style+=".wrapper { border: none !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; return h;
} }
exports.render_space_as_html = render_space_as_html; exports.render_space_as_html = render_space_as_html;

View File

@@ -320,5 +320,6 @@
"follow_present": "Folgen", "follow_present": "Folgen",
"mute_present": "Entfolgen", "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.", "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"
}

View File

@@ -322,6 +322,7 @@
"follow_present": "Follow", "follow_present": "Follow",
"mute_present": "Unfollow", "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.", "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", "export": "Export",
"media": "Media" "media": "Media",
} "tool_edit_text": "Edit Text"
}

View File

@@ -173,7 +173,7 @@
"tool_bullets": "Bullets", "tool_bullets": "Bullets",
"tool_numbers": "Nombres", "tool_numbers": "Nombres",
"color_fill": "Fill", "color_fill": "Fill",
"tool_font": "Font", "tool_font": "Poliça",
"color_stroke": "Traçat", "color_stroke": "Traçat",
"color_text": "Tèxte", "color_text": "Tèxte",
"tool_type": "Tipe", "tool_type": "Tipe",
@@ -253,7 +253,7 @@
"access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements quan creats.", "access_anonymous_edit_blocking": "Los convidats pòdon pas modificar los elements quan creats.",
"access_current_members": "Membres actuals", "access_current_members": "Membres actuals",
"access_new_members": "Convidar de novèls membres", "access_new_members": "Convidar de novèls membres",
"access_no_members": "Los membres daqueste Espacii apreissaràn aquí.", "access_no_members": "Los membres daqueste Espaci apareisseràn aquí.",
"comments": "comentaris", "comments": "comentaris",
"landing_customers": "La fisança de milièr de personas.", "landing_customers": "La fisança de milièr de personas.",
"landing_features_title": "Un jòc d'enfants dutilizar.", "landing_features_title": "Un jòc d'enfants dutilizar.",
@@ -309,7 +309,7 @@
"list": "lista", "list": "lista",
"link": "Ligam", "link": "Ligam",
"download_space": "Telecargar espaci", "download_space": "Telecargar espaci",
"download_space_as_pdf": "Telecargar espaci PDF", "download_space_as_pdf": "Telecargar espaci coma PDF",
"type": "Tipe", "type": "Tipe",
"download": "Telecargar", "download": "Telecargar",
"Previous Zone": "Zòna precedenta", "Previous Zone": "Zòna precedenta",
@@ -321,7 +321,7 @@
"unlock": "Desverrolhar", "unlock": "Desverrolhar",
"follow_present": "Seguir", "follow_present": "Seguir",
"mute_present": "Quitar de seguir", "mute_present": "Quitar de seguir",
"follow_present_help": "follow_present_help", "follow_present_help": "Se qualquun mai presenta aqueste espaci, los demai membres seguiràn automaticament la presentacion. Basculatz labonament a la presentacion amb aqueste boton.",
"export": "exportar", "export": "exportar",
"media": "Media" "media": "Mèdia"
} }

View File

@@ -86,7 +86,7 @@ module.exports = (req, res, next) => {
// space is private // space is private
// special permission for screenshot/pdf export from backend // 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"); finalizeReq(space, "viewer");
return; return;
} }

View File

@@ -6,24 +6,28 @@ function sequel_log(a,b,c) {
} }
const Sequelize = require('sequelize'); const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', { const sequelize = new Sequelize(
host: 'localhost', config.get('storage_database'),
dialect: 'sqlite', config.get('storage_username'),
config.get('storage_password'),
pool: { {
max: 5, host: config.get('storage_host'),
min: 0, dialect: config.get('storage_dialect'),
acquire: 30000, pool: {
idle: 10000 max: 5,
}, min: 0,
acquire: 30000,
// SQLite only idle: 10000
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
// 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 User;
var Session; var Session;

7466
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,9 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node spacedeck.js" "start": "node spacedeck.js",
"dev": "nodemon spacedeck.js",
"styles": "gulp styles"
}, },
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=10.0.0"
@@ -22,19 +24,14 @@
"file-type": "^7.6.0", "file-type": "^7.6.0",
"glob": "7.1.1", "glob": "7.1.1",
"gm": "^1.23.1", "gm": "^1.23.1",
"gulp": "^4.0.2",
"gulp-concat": "^2.6.1",
"gulp-sass": "^4.0.2",
"helmet": "^3.5.0", "helmet": "^3.5.0",
"i18n-2": "0.6.3", "i18n-2": "0.6.3",
"log-timestamp": "latest", "log-timestamp": "latest",
"mock-aws-s3": "^2.6.0", "mock-aws-s3": "^2.6.0",
"moment": "^2.19.3", "moment": "^2.19.3",
"morgan": "^1.9.1", "morgan": "^1.9.1",
"node-phantom-simple": "2.2.4",
"node-server-screenshot": "^0.2.1",
"nodemailer": "^4.6.7", "nodemailer": "^4.6.7",
"phantomjs-prebuilt": "^2.1.16", "puppeteer": "8.0.0",
"read-chunk": "^2.1.0", "read-chunk": "^2.1.0",
"request": "^2.88.0", "request": "^2.88.0",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
@@ -49,6 +46,13 @@
"validator": "7.0.0", "validator": "7.0.0",
"ws": "3.3.1" "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", "main": "app.js",
"description": "", "description": "",
"directories": {}, "directories": {},

View File

@@ -52,9 +52,18 @@ function setup_directives() {
} }
var play_func = function() { var play_func = function() {
video.play(); var playPromise = video.play();
player_state = "playing"; if (playPromise !== undefined) {
update_view(); playPromise.then(_ => {
// Automatic playback started!
player_state = "playing";
update_view();
})
.catch(error => {
// Auto-play was prevented
// Show paused UI.
});
}
} }
var pause_func = function() { var pause_func = function() {

View File

@@ -68,9 +68,9 @@ var SpacedeckSections = {
line_height: 1.5, line_height: 1.5,
letter_spacing: 0, letter_spacing: 0,
stroke_color: "#000000", stroke_color: ENV.options.default_stroke_color ? ENV.options.default_stroke_color : "#000000",
fill_color: "#00000000", fill_color: ENV.options.default_fill_color ? ENV.options.default_fill_color : "#000000",
text_color: "#000000", text_color: ENV.options.default_text_color ? ENV.options.default_text_color : "#000000",
background_color: "#ffffff", background_color: "#ffffff",
padding: 0, padding: 0,
@@ -109,7 +109,7 @@ var SpacedeckSections = {
color_picker_hue: 127, color_picker_hue: 127,
color_picker_opacity: 255, color_picker_opacity: 255,
swatches: [ swatches: ENV.options.swatches ? ENV.options.swatches : [
{id:1, hex:"#ff00ff"}, {id:1, hex:"#ff00ff"},
{id:2, hex:"#ffff00"}, {id:2, hex:"#ffff00"},
{id:3, hex:"#00ffff"}, {id:3, hex:"#00ffff"},
@@ -133,18 +133,7 @@ var SpacedeckSections = {
{id:26, hex:"#d55c4b"}, {id:26, hex:"#d55c4b"},
{id:27, hex:"#6f4021"}, {id:27, hex:"#6f4021"},
{id:29, hex:"#95a5a6"}, {id:29, hex:"#95a5a6"},
{id:30, hex:"rgba(0,0,0,0)"}, {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"},
], ],
fonts: [ fonts: [
@@ -182,7 +171,8 @@ var SpacedeckSections = {
toolbar_props_in: false, toolbar_props_in: false,
toolbar_artifacts_x: "-1000px", toolbar_artifacts_x: "-1000px",
toolbar_artifacts_y: "-1000px", toolbar_artifacts_y: "-1000px",
toolbar_artifacts_in: true toolbar_artifacts_in: true,
toolbar_lock_in: false
}, },
methods: { methods: {
@@ -848,7 +838,7 @@ var SpacedeckSections = {
if (!a) return false; if (!a) return false;
if (!this.active_space) 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; return false;
} }
@@ -1018,8 +1008,7 @@ var SpacedeckSections = {
}; };
}, },
update_selection_metrics: function(arts) { update_selection_metrics: function(arts, temporary) {
if (this.active_tool == "scribble") { if (this.active_tool == "scribble") {
this.selection_metrics.count = 1; this.selection_metrics.count = 1;
return; return;
@@ -1052,8 +1041,6 @@ var SpacedeckSections = {
// FIXME make sure that menus fit in window // FIXME make sure that menus fit in window
this.toolbar_props_x = pp.x+"px"; this.toolbar_props_x = pp.x+"px";
this.toolbar_props_y = pp.y+"px"; this.toolbar_props_y = pp.y+"px";
//this.hide_toolbar_artifacts();
} }
this.selection_metrics.x1 = sr.x1; this.selection_metrics.x1 = sr.x1;
@@ -1068,24 +1055,26 @@ var SpacedeckSections = {
if (!arts) arts = this.selected_artifacts(); if (!arts) arts = this.selected_artifacts();
this.first_selected_artifact = arts[0]; if (!temporary) {
this.selection_metrics.count=arts.length; this.first_selected_artifact = arts[0];
this.selection_metrics.scribble_selection = false; this.selection_metrics.count=arts.length;
if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") { this.selection_metrics.scribble_selection = false;
if (arts[0].shape == "scribble") { if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") {
this.selection_metrics.scribble_selection = true; if (arts[0].shape == "scribble") {
this.selection_metrics.scribble_selection = true;
}
this.selection_metrics.vector_points = arts[0].control_points;
this.selection_metrics.vector_selection = true;
} else {
this.selection_metrics.vector_points = [{},{}];
this.selection_metrics.vector_selection = false;
}
this.selection_metrics.has_link=false;
this.insert_link_url="";
if (arts.length == 1 && arts[0].meta && arts[0].meta.link_uri && arts[0].meta.link_uri.length>0) {
this.selection_metrics.has_link=true;
this.insert_link_url = arts[0].meta.link_uri;
} }
this.selection_metrics.vector_points = arts[0].control_points;
this.selection_metrics.vector_selection = true;
} else {
this.selection_metrics.vector_points = [{},{}];
this.selection_metrics.vector_selection = false;
}
this.selection_metrics.has_link=false;
this.insert_link_url="";
if (arts.length == 1 && arts[0].meta && arts[0].meta.link_uri && arts[0].meta.link_uri.length>0) {
this.selection_metrics.has_link=true;
this.insert_link_url = arts[0].meta.link_uri;
} }
}, },
@@ -1435,13 +1424,13 @@ var SpacedeckSections = {
this.color_picker_rgb = rgb_to_hex(rgba.r,rgba.g,rgba.b); 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); var artifacts = this.selected_artifacts(!override_locked);
if (!artifacts.length) return; if (!artifacts.length) return;
this.update_artifacts(artifacts, change_func); this.update_artifacts(artifacts, change_func);
this.update_selection_metrics(); this.update_selection_metrics(null, temporary||false);
}, },
nudge_selected_artifacts: function(dx, dy, event) { nudge_selected_artifacts: function(dx, dy, event) {
@@ -1922,10 +1911,7 @@ var SpacedeckSections = {
}.bind(this)); }.bind(this));
}, },
delayed_edit_artifact: function(evt) { delayed_edit_artifact: function() {
evt.stopPropagation();
evt.preventDefault();
var a = this.selected_artifacts()[0]; var a = this.selected_artifacts()[0];
var el = $("#ios-focuser-"+a._id); var el = $("#ios-focuser-"+a._id);
@@ -2086,8 +2072,6 @@ var SpacedeckSections = {
if (a.description!=dom.innerHTML) { if (a.description!=dom.innerHTML) {
a.description = dom.innerHTML; a.description = dom.innerHTML;
console.log("new DOM:",dom.innerHTML);
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
this.queue_artifact_for_save(a); this.queue_artifact_for_save(a);
@@ -2526,11 +2510,18 @@ var SpacedeckSections = {
}, },
show_toolbar_props: function() { 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(); 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++) { for (var i=0;i<arts.length; i++) {
if (arts[i].mime=="x-spacedeck/zone") return; 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; this.toolbar_props_in = true;
}, },

View File

@@ -6,7 +6,7 @@
function setup_whiteboard_directives() { function setup_whiteboard_directives() {
var mode_touch = false; var mode_touch = false;
if ('ontouchstart' in window) { if ('ontouchstart' in window) {
mode_touch = true; mode_touch = true;
var edown = "touchstart"; var edown = "touchstart";
@@ -38,7 +38,7 @@ function setup_whiteboard_directives() {
$(el).bind("mousedown", this.handle_mouse_down_space.bind(this)); $(el).bind("mousedown", this.handle_mouse_down_space.bind(this));
$(el).bind("mousemove", this.handle_mouse_move.bind(this)); $(el).bind("mousemove", this.handle_mouse_move.bind(this));
$(el).bind("mouseup", this.handle_mouse_up_space.bind(this)); $(el).bind("mouseup", this.handle_mouse_up_space.bind(this));
$(el).bind("wheel", this.handle_wheel_space.bind(this)); $(el).bind("wheel", this.handle_wheel_space.bind(this));
$(document.body).bind("mouseleave", this.handle_mouse_leave.bind(this)); $(document.body).bind("mouseleave", this.handle_mouse_leave.bind(this));
@@ -99,7 +99,7 @@ function setup_whiteboard_directives() {
this.handle_mouse_down_space(evt); this.handle_mouse_down_space(evt);
return; return;
} }
if ($scope.active_tool == "note") { if ($scope.active_tool == "note") {
this.handle_mouse_down_space(evt, true); this.handle_mouse_down_space(evt, true);
return; return;
@@ -154,7 +154,7 @@ function setup_whiteboard_directives() {
var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-","")); var a = $scope.find_artifact_by_id(evt.currentTarget.id.replace("artifact-",""));
if (!a) return; if (!a) return;
if (a.payload_uri) { if (a.payload_uri) {
$scope.download_selected_artifacts(); $scope.download_selected_artifacts();
} }
@@ -200,10 +200,10 @@ function setup_whiteboard_directives() {
var $scope = this.vm.$root; var $scope = this.vm.$root;
if (!evt.ctrlKey && !evt.shiftKey) return; if (!evt.ctrlKey && !evt.shiftKey) return;
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
var amount = 1; var amount = 1;
var dy = evt.originalEvent.deltaY; var dy = evt.originalEvent.deltaY;
if (dy>0) { if (dy>0) {
@@ -222,7 +222,7 @@ function setup_whiteboard_directives() {
if (!force && evt.which != 2) { if (!force && evt.which != 2) {
if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return; if (evt.target != evt.currentTarget && !_.include(["wrapper"],evt.target.className)) return;
} }
var $scope = this.vm.$root; var $scope = this.vm.$root;
$scope.opened_dialog="none"; $scope.opened_dialog="none";
@@ -234,7 +234,7 @@ function setup_whiteboard_directives() {
if ((mode_touch && $scope.active_tool=="pointer") || evt.which == 2 || evt.buttons == 4) { if ((mode_touch && $scope.active_tool=="pointer") || evt.which == 2 || evt.buttons == 4) {
$scope.active_tool = "pan"; $scope.active_tool = "pan";
} }
if ($scope.active_tool=="note") { if ($scope.active_tool=="note") {
this.deselect(); this.deselect();
this.mouse_state = "transform"; this.mouse_state = "transform";
@@ -285,6 +285,7 @@ function setup_whiteboard_directives() {
$scope.start_adding_placeholder(evt); $scope.start_adding_placeholder(evt);
return; return;
} else if ($scope.active_tool=="pan") { } else if ($scope.active_tool=="pan") {
this.deselect();
this.start_pan(evt); this.start_pan(evt);
return; return;
} }
@@ -305,7 +306,7 @@ function setup_whiteboard_directives() {
this.old_panx = el.scrollLeft; this.old_panx = el.scrollLeft;
this.old_pany = el.scrollTop; this.old_pany = el.scrollTop;
} }
var cursor = this.cursor_point_to_space(evt); var cursor = this.cursor_point_to_space(evt);
$scope.mouse_ox = cursor.x; $scope.mouse_ox = cursor.x;
$scope.mouse_oy = cursor.y; $scope.mouse_oy = cursor.y;
@@ -314,7 +315,7 @@ function setup_whiteboard_directives() {
deselect: function() { deselect: function() {
var $scope = this.vm.$root; var $scope = this.vm.$root;
$scope.deselect(); $scope.deselect();
}, },
@@ -342,7 +343,7 @@ function setup_whiteboard_directives() {
rects_intersecting: function(r1,r2) { rects_intersecting: function(r1,r2) {
if (!r1 || !r2) return false; if (!r1 || !r2) return false;
if ( (r1.x+r1.w < r2.x) if ( (r1.x+r1.w < r2.x)
|| (r1.x > r2.x+r2.w) || (r1.x > r2.x+r2.w)
|| (r1.y+r1.h < r2.y) || (r1.y+r1.h < r2.y)
@@ -352,7 +353,7 @@ function setup_whiteboard_directives() {
artifacts_in_rect: function(rect) { artifacts_in_rect: function(rect) {
if (!rect) return []; if (!rect) return [];
var $scope = this.vm.$root; var $scope = this.vm.$root;
return _.filter($scope.active_space_artifacts, function(a) { return _.filter($scope.active_space_artifacts, function(a) {
@@ -688,7 +689,7 @@ function setup_whiteboard_directives() {
var $scope = this.vm.$root; var $scope = this.vm.$root;
evt.preventDefault(); evt.preventDefault();
if (this.mouse_state == "lasso") { if (this.mouse_state == "lasso") {
var lasso_rect = this.abs_rect(this.lasso); var lasso_rect = this.abs_rect(this.lasso);
@@ -696,7 +697,7 @@ function setup_whiteboard_directives() {
var arts = this.artifacts_in_rect(lasso_rect); var arts = this.artifacts_in_rect(lasso_rect);
this.multi_select(arts); this.multi_select(arts);
} else { } else {
if (this._no_artifact_toolbar_this_round) { if (this._no_artifact_toolbar_this_round) {
this._no_artifact_toolbar_this_round = false; this._no_artifact_toolbar_this_round = false;
} else { } else {
@@ -708,7 +709,7 @@ function setup_whiteboard_directives() {
} }
else if (_.include(["transform","move","vector_transform","scribble"],this.mouse_state)) { else if (_.include(["transform","move","vector_transform","scribble"],this.mouse_state)) {
var ars = $scope.selected_artifacts(); var ars = $scope.selected_artifacts();
for (var i=0; i<ars.length; i++) { for (var i=0; i<ars.length; i++) {
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) { if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
@@ -723,6 +724,9 @@ function setup_whiteboard_directives() {
//save_artifact(ars[i], null, $scope.display_saving_error); //save_artifact(ars[i], null, $scope.display_saving_error);
} }
// update vector handles
$scope.update_selection_metrics();
} }
if (this.mouse_state == "text_editor") { if (this.mouse_state == "text_editor") {
@@ -794,7 +798,7 @@ function setup_whiteboard_directives() {
$scope.websocket_send(cursor_msg); $scope.websocket_send(cursor_msg);
} }
// side effects ftw! // side effects ftw!
$scope.snap_ruler_x = -1000; $scope.snap_ruler_x = -1000;
$scope.snap_ruler_y = -1000; $scope.snap_ruler_y = -1000;
@@ -818,7 +822,7 @@ function setup_whiteboard_directives() {
if (this.mouse_state == "move") { if (this.mouse_state == "move") {
$scope.hide_toolbar_props(); $scope.hide_toolbar_props();
var snap_dx = 0; var snap_dx = 0;
var snap_dy = 0; var snap_dy = 0;
@@ -878,7 +882,7 @@ function setup_whiteboard_directives() {
} }
$scope.hide_toolbar_props(); $scope.hide_toolbar_props();
var ew = (edges.x2-edges.x1); var ew = (edges.x2-edges.x1);
var eh = (edges.y2-edges.y1); var eh = (edges.y2-edges.y1);
@@ -924,7 +928,7 @@ function setup_whiteboard_directives() {
} else if (this.mouse_state == "vector_transform") { } else if (this.mouse_state == "vector_transform") {
$scope.hide_toolbar_props(); $scope.hide_toolbar_props();
var _this = this; var _this = this;
$scope.update_selected_artifacts(function(a) { $scope.update_selected_artifacts(function(a) {
var old_a = $scope.find_artifact_before_transaction(a); var old_a = $scope.find_artifact_before_transaction(a);
@@ -941,18 +945,14 @@ function setup_whiteboard_directives() {
// special case for arrow's 3rd point // special case for arrow's 3rd point
if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) { 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].dx = (control_points[0].dx+control_points[1].dx)/2;
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2; control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
} }
return _this.normalize_control_points(control_points, old_a); return _this.normalize_control_points(control_points, old_a);
}); }, false, true); // override_locked: false, temporary: true
} else if (this.mouse_state == "scribble") { } else if (this.mouse_state == "scribble") {
$scope.update_selected_artifacts(function(a) { $scope.update_selected_artifacts(function(a) {
var old_a = a; var old_a = a;

View File

@@ -23,6 +23,9 @@ function vec2_angle(v) {
function render_vector_drawing(a, padding) { function render_vector_drawing(a, padding) {
var shape = a.shape || ""; var shape = a.shape || "";
var path = []; var path = [];
if(typeof a.control_points == 'string'){
a.control_points = JSON.parse(a.control_points);
}
var p = a.control_points[0]; var p = a.control_points[0];
if (!p) return ""; if (!p) return "";

File diff suppressed because one or more lines are too long

View File

@@ -5,7 +5,7 @@ const db = require('../../models/db');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render'); var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom'); var exporter = require('../../helpers/exporter');
var async = require('async'); var async = require('async');
var moment = require('moment'); 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) { 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 }}); 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"; var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) { 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) { router.get('/pdf', function(req, res, next) {
var s3_filename = make_export_filename(req.space, "pdf"); 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) { uploader.uploadFile(s3_filename, "application/pdf", local_path, function(err, url) {
res.status(201).json({ res.status(201).json({
url: url url: url

View File

@@ -9,7 +9,7 @@ var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render'); var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom'); var exporter = require('../../helpers/exporter');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');

View File

@@ -10,7 +10,7 @@ var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render'); var space_render = require('../../helpers/space-render');
var phantom = require('../../helpers/phantom'); var exporter = require('../../helpers/exporter');
var payloadConverter = require('../../helpers/artifact_converter'); var payloadConverter = require('../../helpers/artifact_converter');
var slug = require('slug'); var slug = require('slug');

View File

@@ -1,10 +1,10 @@
"use strict"; "use strict";
const puppeteer = require('puppeteer');
var config = require('config'); var config = require('config');
require('../../models/db'); require('../../models/db');
var fs = require('fs'); var fs = require('fs');
var phantom = require('node-phantom-simple');
var md5 = require('md5'); var md5 = require('md5');
var express = require('express'); 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] url: "+url);
console.log("[webgrabber] export_path: "+export_path); 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) { fs.stat(export_path, function(err, stat) {
if (!err) { if (!err) {
// file exists // file exists
console.log("[webgrabber] serving cached snapshot of url: "+url); console.log("[webgrabber] serving cached snapshot of url: "+url);
on_success(export_path); on_success(export_path);
} else { } else {
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) { (async () => {
return browser.createPage(function (err, page) { let browser;
page.set('settings.resourceTimeout',timeout); let page;
page.set('settings.javascriptEnabled',false); try {
browser = await puppeteer.launch(
return page.open(url, function(err, status) { {
console.log("[webgrabber] status: "+status); headless: true,
page.render(export_path, function() { args: ['--disable-dev-shm-usage', '--no-sandbox']
on_success_called = true; }
on_success(export_path); );
browser.exit(); page = await browser.newPage();
});
}); page.setDefaultTimeout(timeout);
}); await page.setJavaScriptEnabled(false);
}, { await page.goto(url, {waitUntil: 'networkidle2'});
onExit: on_exit await page.emulateMedia('screen');
}); await page.screenshot({path: export_path, printBackground: true});
await browser.close();
on_success(export_path);
} catch (error) {
console.error(error);
console.log("[webgrabber] puppeteer abnormal exit for url "+url);
on_error();
}
})();
} }
}); });
} }

View File

@@ -11,10 +11,12 @@
max-height: 100%; max-height: 100%;
} }
&.hide-text .text { opacity: 0 } &.hide-text .text {
opacity: 0;
}
&.locked.selected { &.locked.selected {
box-shadow: 0px 0px 0px 3px rgba(0,0,0,0.5) !important; box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.5) !important;
} }
.placeholder { .placeholder {
@@ -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"; content: "Double click to edit";
opacity: 0.25; opacity: 0.25;
} }
@@ -79,7 +83,6 @@
// padding-right: 20px; margin-right: -20px; // padding-right: 20px; margin-right: -20px;
//> * {pointer-events: auto; } //> * {pointer-events: auto; }
} }
&:not(.artifact-text) { &:not(.artifact-text) {
@@ -142,7 +145,9 @@
li { li {
margin-top: 0; margin-top: 0;
margin-bottom: 0.5em; margin-bottom: 0.5em;
&:last-child {margin-bottom: 0em !important; } &:last-child {
margin-bottom: 0em !important;
}
} }
h1 { h1 {
@@ -179,10 +184,14 @@
p { p {
margin-top: 0; margin-top: 0;
margin-bottom: 0.5em; // compatibility to old system 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 { ol {
padding: 0; padding: 0;
@@ -247,7 +256,7 @@
} }
.oembed, .oembed,
div[class*='oembed-'] { div[class*="oembed-"] {
height: 100%; height: 100%;
.play { .play {
position: absolute; position: absolute;
@@ -290,7 +299,9 @@
} }
} }
.title {font-size: 20px; } .title {
font-size: 20px;
}
.image { .image {
height: auto; height: auto;
top: 0; top: 0;
@@ -319,10 +330,17 @@
} }
} }
.video { .video {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-size: cover; background-size: cover;
background-color: black;
&.playing {
video {
z-index: 1;
}
}
.title { .title {
position: absolute; position: absolute;
@@ -340,21 +358,22 @@
video { video {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute;
left: 0;
} }
.tl-controls { .tl-controls {
position: absolute; position: absolute;
bottom: 10px; top: 10px;
left: 10px; left: 10px;
right: 10px; right: 10px;
text-align: center; text-align: center;
z-index: 2;
.btn { .btn {
margin-top: 20px; margin-top: 20px;
} }
} }
} }
// FIXME: fix later // FIXME: fix later
@@ -373,8 +392,8 @@
height: 100%; height: 100%;
.timeline { .timeline {
position: absolute; position: absolute;
left:0; left: 0;
top:0; top: 0;
width: 100%; width: 100%;
bottom: 54px; bottom: 54px;
background-size: 100% 100%; background-size: 100% 100%;
@@ -383,7 +402,7 @@
overflow: hidden; overflow: hidden;
.tl-current-time { .tl-current-time {
height:100%; height: 100%;
background: white; background: white;
opacity: 0.5; opacity: 0.5;
border-right: 1px solid #888; border-right: 1px solid #888;
@@ -419,12 +438,11 @@
.btn { .btn {
margin-top: 20px; margin-top: 20px;
} }
} }
.tl-title { .tl-title {
margin-left:10px; margin-left: 10px;
max-width: 55%; max-width: 55%;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
@@ -446,19 +464,41 @@
height: 100%; height: 100%;
} }
&.bold { font-weight: bold;} &.bold {
&.italic { font-style: italic;} font-weight: bold;
&.underline { text-decoration: underline;} }
&.strike { text-decoration: line-through;} &.italic {
font-style: italic;
}
&.underline {
text-decoration: underline;
}
&.strike {
text-decoration: line-through;
}
&.align-top .text-cell {vertical-align: top;} &.align-top .text-cell {
&.align-middle .text-cell {vertical-align: middle;} vertical-align: top;
&.align-bottom .text-cell {vertical-align: bottom;} }
&.align-middle .text-cell {
vertical-align: middle;
}
&.align-bottom .text-cell {
vertical-align: bottom;
}
&.align-left { text-align: left; } &.align-left {
&.align-center { text-align: center; } text-align: left;
&.align-right { text-align: right; } }
&.align-justify { text-align: justify; } &.align-center {
text-align: center;
}
&.align-right {
text-align: right;
}
&.align-justify {
text-align: justify;
}
audio { audio {
width: 100%; width: 100%;
@@ -473,10 +513,14 @@
font-size: 36px; font-size: 36px;
&.artifact-zone { &.artifact-zone {
background-color: rgba(0,0,0,0.05); background-color: rgba(0, 0, 0, 0.05);
border-radius: 10px; border-radius: 10px;
&:after {display: none; } &:after {
.shape {display: none; } display: none;
}
.shape {
display: none;
}
.zone { .zone {
height: 100%; height: 100%;
} }
@@ -488,32 +532,31 @@ body.present-mode {
.artifact-zone { .artifact-zone {
display: none; display: none;
} }
.artifact { .artifact {
cursor: default !important; cursor: default !important;
.text a { pointer-events: auto !important; } .text a {
pointer-events: auto !important;
}
} }
} }
} }
body:not(.present-mode) { body:not(.present-mode) {
#space { #space {
.Medium { .Medium {
cursor: text; cursor: text;
} }
.artifact { .artifact {
&.selected.text-editing, &.selected.text-editing,
&.text-editing { &.text-editing {
cursor: text; cursor: text;
&:before { &:before {
border: 1px solid rgba(255,255,255,0.25); border: 1px solid rgba(255, 255, 255, 0.25);
} }
&:after { &:after {
border: 1px dotted rgba(40,140,215,0.5); border: 1px dotted rgba(40, 140, 215, 0.5);
background-color: transparent !important; background-color: transparent !important;
} }
} }
@@ -527,24 +570,27 @@ body:not(.present-mode) {
} }
&:not(.artifact-vector):after { &:not(.artifact-vector):after {
border: 1px solid rgba(40,140,215,1); border: 1px solid rgba(40, 140, 215, 1);
// overlay color removed for now (messes with colors) // overlay color removed for now (messes with colors)
//background-color: rgba(40,140,215,0.35); //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; cursor: crosshair !important;
.artifact { .artifact {
pointer-events: none !important; pointer-events: none !important;
} }
.artifact:after, .artifact:before { .artifact:after,
.artifact:before {
display: none !important; display: none !important;
} }
} }
@@ -558,21 +604,27 @@ body:not(.present-mode) {
} }
.artifact.state-idle { .artifact.state-idle {
.progress, .progress-text { .progress,
.progress-text {
display: none; display: none;
} }
} }
.artifact.state-processing, .artifact.state-uploading { .artifact.state-processing,
.artifact.state-uploading {
.progress { .progress {
height: 100%; height: 100%;
padding: 10px; padding: 4px;
background-color: $blue;
opacity: 0.9; opacity: 0.9;
text-align: center;
font-size: 14px; font-size: 14px;
} }
.progress-container {
background: $white;
width: 100%;
height: 100%;
padding-left: 10px;
padding-top: 10px;
}
.progress-text { .progress-text {
text-align: center; text-align: center;
padding: 8px; padding: 8px;
@@ -585,43 +637,48 @@ body:not(.present-mode) {
color: #888; color: #888;
font-size: 10px; font-size: 10px;
} }
video, audio, .tl-controls, .timeline { video,
audio,
.tl-controls,
.timeline {
display: none; display: none;
} }
background-color: white; background-color: white;
} }
.state-processing .spinner { .state-processing .spinner {
opacity: 1; opacity: 1;
background-image: url('/images/hourglass.gif'); background-image: url("/images/hourglass.gif");
} }
.state-uploading .spinner { .state-uploading .spinner {
opacity: 0.8; opacity: 0.8;
background-image: url('/images/hourglass.gif'); background-image: url("/images/hourglass.gif");
} }
.state-idle .spinner { .state-idle .spinner {
display: none; display: none;
} }
.artifact.image { .artifact.image {
background-color: transparent; background-color: transparent;
&.state-loading { background-color: rgba(40,140,215,0.05);} &.state-loading {
&.state-processing { background-color: rgba(107,195,111,0.05);} background-color: rgba(40, 140, 215, 0.05);
}
&.state-processing {
background-color: rgba(107, 195, 111, 0.05);
}
} }
.spinner { .spinner {
height:44px; height: 44px;
width:44px; width: 44px;
position:absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin: -22px; margin: -22px;
border-radius:100%; border-radius: 100%;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
} }

View File

@@ -3,12 +3,17 @@
@import "unicode"; @import "unicode";
@-webkit-keyframes appear { @-webkit-keyframes appear {
0% { opacity: 0;} 0% {
30% { opacity: 0;} opacity: 0;
100% { opacity: 1; } }
30% {
opacity: 0;
}
100% {
opacity: 1;
}
} }
.avatar { .avatar {
background-color: $blue; background-color: $blue;
font-family: $main-font; font-family: $main-font;
@@ -26,7 +31,9 @@
#user-root { #user-root {
margin-left: 5px !important; margin-left: 5px !important;
padding: 0px !important; padding: 0px !important;
.btn {margin-right: 10px; } .btn {
margin-right: 10px;
}
} }
#sidebar.folder-sidebar { #sidebar.folder-sidebar {
@@ -70,7 +77,9 @@
li { li {
display: inline-block; display: inline-block;
line-height: 44px; line-height: 44px;
&.active span {color: $light; } &.active span {
color: $light;
}
span { span {
color: $medium; color: $medium;
display: inline-block; display: inline-block;
@@ -82,8 +91,6 @@
} }
} }
#folder { #folder {
position: absolute; position: absolute;
height: 100%; height: 100%;
@@ -137,7 +144,7 @@
z-index: initial; z-index: initial;
} }
.folder .icon { .folder .icon {
color: $yellow; color: $yellow;
opacity: 1; opacity: 1;
} }
@@ -150,7 +157,7 @@
width: 100%; width: 100%;
} }
> .icon { > .icon {
margin-left: -15px margin-left: -15px;
} }
} }
@@ -176,30 +183,30 @@
// margin-top: -18px; // margin-top: -18px;
margin-right: -60px; margin-right: -60px;
// margin-bottom: -18px; // margin-bottom: -18px;
@include transition( all 0.125s ease-in-out); @include transition(all 0.125s ease-in-out);
.icon { .icon {
left: 0px; left: 0px;
top: 0px; top: 0px;
line-height: 60px; line-height: 60px;
font-size: 13px; font-size: 13px;
position: absolute; position: absolute;
@include scale(0,0); @include scale(0, 0);
@include transition( all 0.125s ease-in-out); @include transition(all 0.125s ease-in-out);
@include opacity(0.25); @include opacity(0.25);
} }
&:hover { &:hover {
margin-right: -12px; margin-right: -12px;
.icon { .icon {
@include scale(1,1); @include scale(1, 1);
} }
} }
} }
} }
#folder-header { #folder-header {
background-color: rgba(41,41,41,0.95); background-color: rgba(41, 41, 41, 0.95);
background-color: rgba(245,245,245,0.95); background-color: rgba(245, 245, 245, 0.95);
} }
#folder-wrapper { #folder-wrapper {
@@ -212,15 +219,22 @@
margin: auto; margin: auto;
} }
#folder-grid .item { #folder-grid .item {
float: left; float: left;
width: 100%; width: 100%;
@media screen and (min-width: 640px) {width: 50%; } @media screen and (min-width: 640px) {
@media screen and (min-width: 800px) {width: 33.333333%; } width: 50%;
@media screen and (min-width: 1000px) {width: 25%; } }
@media screen and (min-width: 1200px) {width: 20%; } @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 { .compact-hidden {
@@ -256,8 +270,8 @@
.item { .item {
display: inline-block; display: inline-block;
text-align: left; text-align: left;
padding-right: $folder-gutter*2; padding-right: $folder-gutter * 2;
padding-bottom: $folder-gutter*2; padding-bottom: $folder-gutter * 2;
margin-bottom: $folder-gutter; margin-bottom: $folder-gutter;
position: relative; position: relative;
@@ -265,36 +279,38 @@
cursor: pointer; cursor: pointer;
.folder-drop {display:none; } .folder-drop {
display: none;
}
&.appear > a, &.appear > a,
&.creating > a { &.creating > a {
opacity: 0; opacity: 0;
-webkit-animation: appear 0.25s ease-out 0.05s; -webkit-animation: appear 0.25s ease-out 0.05s;
-moz-animation: appear 0.25s ease-out 0.05s; -moz-animation: appear 0.25s ease-out 0.05s;
-ms-animation: appear 0.25s ease-out 0.05s; -ms-animation: appear 0.25s ease-out 0.05s;
-o-animation: appear 0.25s ease-out 0.05s; -o-animation: appear 0.25s ease-out 0.05s;
animation: appear 0.25s ease-out 0.05s; animation: appear 0.25s ease-out 0.05s;
-webkit-animation-fill-mode: forwards; -webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards; -moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards; -ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards; -o-animation-fill-mode: forwards;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&.deleting > a { &.deleting > a {
-webkit-animation: disappear 0.25s ease-out 0.05s; -webkit-animation: disappear 0.25s ease-out 0.05s;
-moz-animation: disappear 0.25s ease-out 0.05s; -moz-animation: disappear 0.25s ease-out 0.05s;
-ms-animation: disappear 0.25s ease-out 0.05s; -ms-animation: disappear 0.25s ease-out 0.05s;
-o-animation: disappear 0.25s ease-out 0.05s; -o-animation: disappear 0.25s ease-out 0.05s;
animation: disappear 0.25s ease-out 0.05s; animation: disappear 0.25s ease-out 0.05s;
-webkit-animation-fill-mode: forwards; -webkit-animation-fill-mode: forwards;
-moz-animation-fill-mode: forwards; -moz-animation-fill-mode: forwards;
-ms-animation-fill-mode: forwards; -ms-animation-fill-mode: forwards;
-o-animation-fill-mode: forwards; -o-animation-fill-mode: forwards;
animation-fill-mode: forwards; animation-fill-mode: forwards;
} }
&.tag-important > a { &.tag-important > a {
@@ -308,7 +324,7 @@
&.dropping { &.dropping {
.folder-drop { .folder-drop {
display:block; display: block;
} }
} }
@@ -349,7 +365,13 @@
background-color: $darker; background-color: $darker;
position: absolute; position: absolute;
bottom: 2px; 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; background-color: black;
pointer-events: none; pointer-events: none;
} }
@@ -360,7 +382,7 @@
.fav-toggle { .fav-toggle {
margin-right: -12px; margin-right: -12px;
.icon { .icon {
@include scale(1,1); @include scale(1, 1);
@include opacity(1); @include opacity(1);
} }
} }
@@ -384,7 +406,7 @@
> a { > a {
/* aspect ratio without spacer image */ /* aspect ratio without spacer image */
&:before{ &:before {
content: ""; content: "";
display: block; display: block;
padding-top: 100%; /* initial ratio of 1:1*/ padding-top: 100%; /* initial ratio of 1:1*/
@@ -392,9 +414,11 @@
} }
background-color: white; background-color: white;
border-radius: $radius*2; 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); box-shadow: 0 0 30px 1px rgba(0, 0, 0, 0.15);
border: 1px solid black; border: 1px solid black;
@@ -406,7 +430,7 @@
font-weight: 400; font-weight: 400;
display: block; display: block;
text-decoration: none; text-decoration: none;
border-radius: $radius*2; border-radius: $radius * 2;
position: relative; position: relative;
.item-thumbnail { .item-thumbnail {
@@ -420,8 +444,9 @@
overflow: hidden; overflow: hidden;
background-color: transparent; background-color: transparent;
border-top-left-radius: $radius*2; background-size: cover;
border-top-right-radius: $radius*2; border-top-left-radius: $radius * 2;
border-top-right-radius: $radius * 2;
border-bottom-left-radius: 10px; border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px; border-bottom-right-radius: 10px;
@@ -431,7 +456,7 @@
.item-title { .item-title {
display: block; display: block;
border-top: 1px solid rgba(0,0,0,0.05); border-top: 1px solid rgba(0, 0, 0, 0.05);
background-color: white; background-color: white;
font-size: 18px; font-size: 18px;
width: 100%; width: 100%;
@@ -439,15 +464,15 @@
left: 0px; left: 0px;
bottom: 0px; bottom: 0px;
position: absolute; position: absolute;
border-bottom-left-radius: $radius*2; border-bottom-left-radius: $radius * 2;
border-bottom-right-radius: $radius*2; border-bottom-right-radius: $radius * 2;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
color: $dark; color: $dark;
text-align: left; text-align: left;
} }
.thumbnail-loading { .thumbnail-loading {
position: absolute; position: absolute;
z-index: 0; z-index: 0;
@@ -464,11 +489,11 @@
} }
.item-meta { .item-meta {
border-top: 2px solid rgba(0,0,0,0.025); border-top: 2px solid rgba(0, 0, 0, 0.025);
border-radius: 2*$radius; border-radius: 2 * $radius;
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
@include clearfix; @include clearfix;
color: $medium; color: $medium;
position: absolute; position: absolute;
@@ -493,8 +518,8 @@
left: 0px; left: 0px;
bottom: 0px; bottom: 0px;
position: relative; position: relative;
border-bottom-left-radius: $radius*2; border-bottom-left-radius: $radius * 2;
border-bottom-right-radius: $radius*2; border-bottom-right-radius: $radius * 2;
//white-space: nowrap; //white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -509,7 +534,7 @@
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: $folder-gutter*2; right: $folder-gutter * 2;
color: $medium; color: $medium;
font-size: 11px; font-size: 11px;
font-family: $main-font; font-family: $main-font;

View File

@@ -2,9 +2,8 @@
<div class="footer"> <div class="footer">
<p> <p>
<div class="col-xs-6"> <div class="col-xs-6">
&copy; 2020 <a href="https://mntre.com">MNT Research GmbH</a>, Fehlerstr. 8, 12161 Berlin, Germany<br> Spacedeck is Free and Open Source Software. The developers are not affiliated with this website.<br>
&copy; 20112020 Spacedeck GmbH (in liquidation)<br> &copy; 20112020 <a href="https://github.com/mntmn/spacedeck-open">Spacedeck Open Developers</a><br>
Source Code: <a href="https://github.com/mntmn/spacedeck-open">https://github.com/mntmn/spacedeck-open</a>
<br> <br>
Font: <a href="https://rsms.me/inter/">Inter by rsms</a> Font: <a href="https://rsms.me/inter/">Inter by rsms</a>
</div> </div>

View File

@@ -85,13 +85,13 @@
v-bind:class="{text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))}" v-bind:class="{text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))}"
id="artifact-{{a._id}}"> 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) || '')"> <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 v-if="a.locked && is_selected(a)" class="link-wrapper">
<span class="btn btn-sm btn-icon btn-round btn-primary"> <span class="btn btn-sm btn-icon btn-round btn-primary">
<span class="icon icon-lock-closed"></span> <span class="icon icon-lock-closed"></span>
</span>
</span> </span>
</span>
<!-- text --> <!-- text -->
<div v-if="a.view.major_type == 'text'" class="text" v-bind:style="a.view.inner_style"> <div v-if="a.view.major_type == 'text'" class="text" v-bind:style="a.view.inner_style">
<div class="text-table"> <div class="text-table">
@@ -151,30 +151,18 @@
</div> </div>
<!-- video --> <!-- video -->
<div v-if="a.view.major_type == 'video'" v-videoplayer="a" class="video" v-bind:style="a.view.inner_style"> <div v-if="a.view.major_type == 'video'" class="video">
<video preload="metadata" v-bind:poster="a.view.thumbnail_uri"> <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-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" /> <source v-if="a.view.payload_uri && a.view.mime" v-bind:src="a.view.payload_uri" v-bind:type="a.view.mime" />
</video> </video>
<div class="progress" v-bind:style="{width: a.description}">
<div class="tl-controls"> <div class="progress-container">
<div class="btn btn-md btn-toggle btn-round" v-bind:class="{alt:a.player_view.state=='playing'}"> <h3>
<span class="btn-option play"> {{a.description}}
<span class="icon icon-controls-play"></span> </h3>
</span>
<span class="btn-option pause">
<span class="icon icon-controls-pause"></span>
</span>
</div> </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>
<div class="spinner"></div>
<div class="progress" v-bind:style="{width: a.view.progress+'%'}">{{a.description}}</div>
</div> </div>
<!-- audio --> <!-- audio -->

View File

@@ -4,7 +4,7 @@
<div class="dialog-section"> <div class="dialog-section">
<label class="btn btn-xxl btn-transparent btn-icon"> <label class="btn btn-xxl btn-transparent btn-icon">
<span class="icon icon-picture-upload"></span> <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> </label>
<p>Click to Upload<br/> or drag file(s) anywhere.</p> <p>Click to Upload<br/> or drag file(s) anywhere.</p>
</div> </div>

View File

@@ -7,12 +7,12 @@
<button class="btn btn-divider"></button> <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 icon-lock-closed"></span>
<span class="icon-label"><%=__("lock")%></span> <span class="icon-label"><%=__("lock")%></span>
</button> </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 icon-lock-open"></span>
<span class="icon-label"><%=__("unlock")%></span> <span class="icon-label"><%=__("unlock")%></span>
</button> </button>

View File

@@ -1,18 +1,18 @@
<div class="toolbar toolbar-elements" v-bind:class="{in:toolbar_artifacts_in,out:!toolbar_artifacts_in}" v-show="!is_active_space_role('viewer') && active_space_loaded"> <div class="toolbar toolbar-elements" v-bind:class="{in:toolbar_artifacts_in,out:!toolbar_artifacts_in}" v-show="!is_active_space_role('viewer') && active_space_loaded">
<div class="btn-group light vertical"> <div class="btn-group light vertical">
<a class="btn btn-icon btn-transparent" <a class="btn btn-icon btn-transparent"
title="<%=__("home")%>" href="/spaces" title="<%=__("home")%>" href="/spaces"
v-if="(!active_space.parent_space_id && !guest_nickname && !embedded)"> v-if="(!active_space.parent_space_id && !guest_nickname && !embedded)">
<span class="icon icon-folder"></span> <span class="icon icon-folder"></span>
</a> </a>
<a class="btn btn-icon btn-dark" <a class="btn btn-icon btn-dark"
title="Parent Folder" title="Parent Folder"
href="/folders/{{active_space.parent_space_id}}" href="/folders/{{active_space.parent_space_id}}"
v-if="(active_space.parent_space_id && !guest_nickname && !embedded)"> v-if="(active_space.parent_space_id && !guest_nickname && !embedded)">
<span class="icon icon-sd6 icon-svg"></span> <span class="icon icon-sd6 icon-svg"></span>
</a> </a>
@@ -35,15 +35,16 @@
<span class="icon icon-tool-scribble"></span> <span class="icon icon-tool-scribble"></span>
<span class="icon-label"><%=__("tool_scribble")%></span> <span class="icon-label"><%=__("tool_scribble")%></span>
</button> </button>
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_arrow()" v-bind:class="{active:active_tool=='arrow'}" title="<%=__("tool_arrow")%>"> <button class="btn btn-icon-labeled btn-transparent" v-on:click="start_drawing_arrow()" v-bind:class="{active:active_tool=='arrow'}" title="<%=__("tool_arrow")%>">
<span class="icon icon-tool-arrow"></span> <span class="icon icon-tool-arrow"></span>
<span class="icon-label"><%=__("tool_arrow")%></span> <span class="icon-label"><%=__("tool_arrow")%></span>
</button> </button>
<div class="dropdown bottom light center"> <div class="dropdown bottom light center">
<div class="btn-collapse in"> <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 icon-upload"></span>
<span class="icon-label" ><%=__("media")%></span> <span class="icon-label" ><%=__("media")%></span>
</button> </button>
@@ -59,19 +60,6 @@
</div> </div>
</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="dropdown top left light" v-bind:class="{open:opened_dialog=='zones'}">
<div class="btn-collapse in"> <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")%>"> <button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='zones'}" v-on:click="open_dialog('zones')" title="<%=__("tool_zones")%>">
@@ -84,7 +72,7 @@
<%- include("./zones.html") %> <%- include("./zones.html") %>
</div> </div>
</div> </div>
<button class="btn btn-divider" v-show="logged_in"></button> <button class="btn btn-divider" v-show="logged_in"></button>
<div class="dropdown top left center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}"> <div class="dropdown top left center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">
@@ -107,8 +95,8 @@
<span class="icon icon-share"></span> <span class="icon icon-share"></span>
<span class="icon-label"><%= __('share') %></span> <span class="icon-label"><%= __('share') %></span>
</button> </button>
<!-- <!--
<li v-on:click="edit_space_title()" v-if="logged_in"> <li v-on:click="edit_space_title()" v-if="logged_in">
<span> <span>
<span class="icon icon-sm icon-tag"></span> <span class="icon icon-sm icon-tag"></span>

View File

@@ -40,7 +40,7 @@
<button class="btn btn-divider"></button> <button class="btn btn-divider"></button>
--> -->
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-styles'}"> <div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-styles'}">
<div class="btn-collapse in"> <div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-styles')" v-bind:class="{open : opened_dialog=='text-styles'}" title="<%=__("tool_styles")%>"> <button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-styles')" v-bind:class="{open : opened_dialog=='text-styles'}" title="<%=__("tool_styles")%>">
@@ -53,7 +53,7 @@
<%- include("./text-styles.html") %> <%- include("./text-styles.html") %>
</div> </div>
</div> </div>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='type-align'}"> <div class="dropdown top light right" v-bind:class="{open:opened_dialog=='type-align'}">
<div class="btn-collapse" v-bind:class="{in:selection_metrics.contains_text}"> <div class="btn-collapse" v-bind:class="{in:selection_metrics.contains_text}">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('type-align')" v-bind:class="{open : opened_dialog=='type-align'}" title="<%=__("tool_align")%>"> <button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('type-align')" v-bind:class="{open : opened_dialog=='type-align'}" title="<%=__("tool_align")%>">
@@ -80,9 +80,9 @@
<%- include("./layout.html") %> <%- include("./layout.html") %>
</div> </div>
</div> </div>
<div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-settings'}"> <div class="dropdown top light right" v-bind:class="{open:opened_dialog=='text-settings'}">
<div class="btn-collapse in"> <div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-settings')" v-bind:class="{open : opened_dialog=='text-settings'}" title="<%=__("tool_font")%>"> <button class="btn btn-transparent btn-icon-labeled" v-on:click="open_dialog('text-settings')" v-bind:class="{open : opened_dialog=='text-settings'}" title="<%=__("tool_font")%>">
<span class="icon icon-text-typeface"></span> <span class="icon icon-text-typeface"></span>
@@ -97,6 +97,17 @@
<button class="btn btn-divider"></button> <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'}"> <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'}"> <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> <span class="icon icon-cogwheel"></span>
@@ -107,6 +118,6 @@
<%- include("./object-options.html") %> <%- include("./object-options.html") %>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,21 +7,18 @@
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <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-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" /> <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="/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"> <link rel="stylesheet" href="/stylesheets/style.css">
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script> <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script> <script>
//window.browser_lang = '< %= locale %>';
var ENV = { var ENV = {
name: 'development', name: 'development',
webHost: location.host, webHost: location.host,
webEndpoint: location.origin, webEndpoint: location.origin,
apiEndpoint: 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> </script>
@@ -59,7 +56,7 @@
<script src="/javascripts/spacedeck_whiteboard.js"></script> <script src="/javascripts/spacedeck_whiteboard.js"></script>
<script src="/javascripts/spacedeck_directives.js"></script> <script src="/javascripts/spacedeck_directives.js"></script>
<script src="/javascripts/spacedeck_vue.js"></script> <script src="/javascripts/spacedeck_vue.js"></script>
<script>if (window.module) module = window.module;</script> <script>if (window.module) module = window.module;</script>
</head> </head>
@@ -86,6 +83,6 @@
window.locales.de.translation = <%- include("./../locales/de.js") %>; window.locales.de.translation = <%- include("./../locales/de.js") %>;
window.locales.fr.translation = <%- include("./../locales/fr.js") %>; window.locales.fr.translation = <%- include("./../locales/fr.js") %>;
window.locales.oc.translation = <%- include("./../locales/oc.js") %>; window.locales.oc.translation = <%- include("./../locales/oc.js") %>;
window.locales.es.translation = <%- include("./../locales/es.js") %>; window.locales.es.translation = <%- include("./../locales/es.js") %>;
</script> </script>
</html> </html>