mirror of
https://github.com/spacedeck/spacedeck-open.git
synced 2026-01-29 14:35:26 +01:00
Compare commits
5 Commits
d5cd829834
...
mnt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5914ab600 | ||
|
|
1d7a014fd4 | ||
|
|
43577337aa | ||
|
|
eb9c767afc | ||
|
|
cf8902f3c5 |
21
Dockerfile
21
Dockerfile
@@ -1,26 +1,7 @@
|
|||||||
FROM node:10-alpine3.11
|
FROM node:18-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# build audiowaveform from source
|
|
||||||
|
|
||||||
RUN apk add git make cmake gcc g++ libmad-dev libid3tag-dev libsndfile-dev gd-dev boost-dev libgd libpng-dev zlib-dev opus-dev
|
|
||||||
RUN apk add zlib-static libpng-static boost-static
|
|
||||||
|
|
||||||
RUN apk add autoconf automake libtool gettext
|
|
||||||
RUN wget https://github.com/xiph/flac/archive/1.3.3.tar.gz
|
|
||||||
RUN tar xzf 1.3.3.tar.gz
|
|
||||||
RUN cd flac-1.3.3/ && ./autogen.sh
|
|
||||||
RUN cd flac-1.3.3/ && ./configure --enable-shared=no
|
|
||||||
RUN cd flac-1.3.3/ && make
|
|
||||||
RUN cd flac-1.3.3/ && make install
|
|
||||||
|
|
||||||
RUN git clone https://github.com/bbc/audiowaveform.git
|
|
||||||
RUN mkdir audiowaveform/build/
|
|
||||||
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
|
# install chromium
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
chromium \
|
chromium \
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ We appreciate filed issues, pull requests and general discussion.
|
|||||||
|
|
||||||
Spacedeck requires:
|
Spacedeck requires:
|
||||||
|
|
||||||
- Node.js 10.x: Web Server / API. Download: https://nodejs.org
|
- Node.js 18.x: Web Server / API. Download: https://nodejs.org
|
||||||
- Graphicsmagick. On non-Linux, Download: http://www.graphicsmagick.org/ On Linux, install via package manager.
|
- Graphicsmagick. On non-Linux, Download: http://www.graphicsmagick.org/ On Linux, install via package manager.
|
||||||
- Optionally ffmpeg, audiowaveform and ghostscript. See "Optional Dependencies" below.
|
- Optionally ffmpeg, audiowaveform and ghostscript. See "Optional Dependencies" below.
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ const path = require('path')
|
|||||||
const db = require('../models/db')
|
const db = require('../models/db')
|
||||||
const Sequelize = require('sequelize')
|
const Sequelize = require('sequelize')
|
||||||
const Op = Sequelize.Op
|
const Op = Sequelize.Op
|
||||||
const uuidv4 = require('uuid/v4')
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
|
||||||
require('../models/db')
|
require('../models/db')
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,7 @@
|
|||||||
"tool_scribble": "Scribble",
|
"tool_scribble": "Scribble",
|
||||||
"tool_line": "Line",
|
"tool_line": "Line",
|
||||||
"tool_arrow": "Arrow",
|
"tool_arrow": "Arrow",
|
||||||
|
"tool_pan": "Pan",
|
||||||
"search_media_placeholder": "Search web media…",
|
"search_media_placeholder": "Search web media…",
|
||||||
"add_zone": "New Zone",
|
"add_zone": "New Zone",
|
||||||
"palette": "Palette",
|
"palette": "Palette",
|
||||||
@@ -329,4 +330,4 @@
|
|||||||
"tool_space_help_part_one": "Use the toolbar to add content.",
|
"tool_space_help_part_one": "Use the toolbar to add content.",
|
||||||
"tool_space_help_part_two": "You can also drop images or sound and video files.",
|
"tool_space_help_part_two": "You can also drop images or sound and video files.",
|
||||||
"tool_delete_space": "Really delete \"%s\"?"
|
"tool_delete_space": "Really delete \"%s\"?"
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ module.exports = (req, res, next) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.error((err) => {
|
.catch((err) => {
|
||||||
console.error("Session resolve error", err);
|
console.error("Session resolve error", err);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|||||||
15164
package-lock.json
generated
15164
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@@ -11,47 +11,47 @@
|
|||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "1.3.0",
|
"archiver": "5.3.1",
|
||||||
"async": "2.3.0",
|
"async": "3.2.4",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "^1.19.0",
|
"body-parser": "^1.19.0",
|
||||||
"cheerio": "0.22.0",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"config": "1.25.1",
|
"config": "3.3.9",
|
||||||
"cookie-parser": "~1.4.3",
|
"cookie-parser": "~1.4.3",
|
||||||
"ejs": "3.1.5",
|
"ejs": "3.1.9",
|
||||||
"execSync": "latest",
|
"execSync": "latest",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"file-type": "^7.6.0",
|
"file-type": "^16.5.0",
|
||||||
"glob": "7.1.1",
|
"glob": "10.3.3",
|
||||||
"gm": "^1.23.1",
|
"gm": "^1.23.1",
|
||||||
"helmet": "^3.5.0",
|
"helmet": "^7.0.0",
|
||||||
"i18n-2": "0.6.3",
|
"i18n-2": "0.7.3",
|
||||||
"log-timestamp": "latest",
|
"log-timestamp": "latest",
|
||||||
"mock-aws-s3": "^4.0.2",
|
"mock-aws-s3": "^4.0.2",
|
||||||
"moment": "^2.19.3",
|
"moment": "^2.19.3",
|
||||||
"morgan": "^1.9.1",
|
"morgan": "^1.9.1",
|
||||||
"nodemailer": "^6.6.0",
|
"nodemailer": "^6.6.0",
|
||||||
"puppeteer": "8.0.0",
|
"puppeteer": "^18",
|
||||||
"read-chunk": "^2.1.0",
|
"read-chunk": "^3",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"sanitize-html": "^2.3.3",
|
"sanitize-html": "^2.3.3",
|
||||||
"sequelize": "^4.37.6",
|
"sequelize": "^6.33.0",
|
||||||
"serve-favicon": "~2.4.2",
|
"serve-favicon": "~2.5.0",
|
||||||
"serve-static": "^1.13.1",
|
"serve-static": "^1.13.1",
|
||||||
"slug": "^1.1.0",
|
"slug": "^8.2.2",
|
||||||
"sqlite3": "^4.2.0",
|
"sqlite3": "5.*",
|
||||||
"umzug": "^2.1.0",
|
"umzug": "^2",
|
||||||
"underscore": "^1.13.1",
|
"underscore": "^1.13.1",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^9.0.0",
|
||||||
"validator": "7.0.0",
|
"validator": "13.9.0",
|
||||||
"ws": "5.2.3"
|
"ws": "8.13.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp-clean-css": "^4.3.0",
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
"gulp-sass": "^4.0.2",
|
"gulp-sass": "^5",
|
||||||
"nodemon": "^2.0.6"
|
"nodemon": "^3.0.1"
|
||||||
},
|
},
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -2562,6 +2562,17 @@ var SpacedeckSections = {
|
|||||||
this.opened_dialog = "none";
|
this.opened_dialog = "none";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
start_pan: function(evt) {
|
||||||
|
this.deselect();
|
||||||
|
if (this.active_tool == "pan") {
|
||||||
|
this.active_tool = "pointer";
|
||||||
|
} else {
|
||||||
|
this.active_tool = "pan";
|
||||||
|
}
|
||||||
|
this.opened_dialog = "none";
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
start_drawing_scribble: function(evt) {
|
start_drawing_scribble: function(evt) {
|
||||||
this.deselect();
|
this.deselect();
|
||||||
if (this.active_tool == "scribble") {
|
if (this.active_tool == "scribble") {
|
||||||
|
|||||||
@@ -733,7 +733,7 @@ function setup_whiteboard_directives() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.include(["zoom", "scribble"], $scope.active_tool)) {
|
if (_.include(["zoom", "scribble", "pan"], $scope.active_tool)) {
|
||||||
// tools that stay active after use
|
// tools that stay active after use
|
||||||
this.mouse_state = "idle";
|
this.mouse_state = "idle";
|
||||||
$scope.mouse_state = this.mouse_state;
|
$scope.mouse_state = this.mouse_state;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ var router = express.Router();
|
|||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
router.get('/:membership_id/accept', function(req, res, next) {
|
router.get('/:membership_id/accept', function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ router.post('/', function(req, res) {
|
|||||||
res.status(400).json({});
|
res.status(400).json({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var email = req.body.email.toLowerCase();
|
var email = req.body.email.toLowerCase();
|
||||||
var password = req.body["password"];
|
var password = req.body["password"];
|
||||||
|
|
||||||
db.User.findOne({where: {email: email}})
|
db.User.findOne({where: {email: email}})
|
||||||
.error(err => {
|
.catch(err => {
|
||||||
res.sendStatus(404);
|
res.sendStatus(404);
|
||||||
})
|
})
|
||||||
.then(user => {
|
.then(user => {
|
||||||
@@ -41,7 +41,7 @@ router.post('/', function(req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
db.Session.create(session)
|
db.Session.create(session)
|
||||||
.error(err => {
|
.catch(err => {
|
||||||
console.error("Error creating Session:",err);
|
console.error("Error creating Session:",err);
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const os = require('os');
|
|||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
var payloadConverter = require('../../helpers/artifact_converter');
|
var payloadConverter = require('../../helpers/artifact_converter');
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var config = require('config');
|
|||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var config = require('config');
|
|||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const os = require('os');
|
|||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
var redis = require('../../helpers/redis');
|
var redis = require('../../helpers/redis');
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
@@ -81,13 +81,13 @@ function listSpacesInFolder(req, res, parent_space_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.get('/', function(req, res, next) {
|
router.get('/', function(req, res, next) {
|
||||||
|
|
||||||
if (req.query.parent_space_id && req["spaceAuth"]) {
|
if (req.query.parent_space_id && req["spaceAuth"]) {
|
||||||
// list subspaces of a space authorized anonymously
|
// list subspaces of a space authorized anonymously
|
||||||
listSpacesInFolder(req, res, req.query.parent_space_id);
|
listSpacesInFolder(req, res, req.query.parent_space_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
res.status(403).json({
|
res.status(403).json({
|
||||||
error: "auth required"
|
error: "auth required"
|
||||||
@@ -127,11 +127,11 @@ router.get('/', function(req, res, next) {
|
|||||||
|
|
||||||
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
|
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
|
||||||
// list spaces in a folder
|
// list spaces in a folder
|
||||||
|
|
||||||
listSpacesInFolder(req, res, req.query.parent_space_id);
|
listSpacesInFolder(req, res, req.query.parent_space_id);
|
||||||
} else {
|
} else {
|
||||||
// list home folder and spaces/folders that the user is a member of
|
// list home folder and spaces/folders that the user is a member of
|
||||||
|
|
||||||
db.Membership.findAll({ where: {
|
db.Membership.findAll({ where: {
|
||||||
user_id: req.user._id
|
user_id: req.user._id
|
||||||
}}).then(memberships => {
|
}}).then(memberships => {
|
||||||
@@ -186,10 +186,8 @@ router.post('/', function(req, res, next) {
|
|||||||
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
||||||
attrs.edit_slug = attrs.edit_slug || slug(attrs.name);
|
attrs.edit_slug = attrs.edit_slug || slug(attrs.name);
|
||||||
attrs.access_mode = "private";
|
attrs.access_mode = "private";
|
||||||
|
|
||||||
db.Space.create(attrs).then(createdSpace => {
|
db.Space.create(attrs).then(createdSpace => {
|
||||||
res.status(201).json(createdSpace);
|
|
||||||
|
|
||||||
// create initial admin membership
|
// create initial admin membership
|
||||||
var membership = {
|
var membership = {
|
||||||
_id: uuidv4(),
|
_id: uuidv4(),
|
||||||
@@ -198,7 +196,7 @@ router.post('/', function(req, res, next) {
|
|||||||
role: "admin",
|
role: "admin",
|
||||||
state: "active"
|
state: "active"
|
||||||
};
|
};
|
||||||
|
|
||||||
db.Membership.create(membership).then(() => {
|
db.Membership.create(membership).then(() => {
|
||||||
res.status(201).json(createdSpace);
|
res.status(201).json(createdSpace);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
var config = require('config');
|
var config = require('config');
|
||||||
const db = require('../../models/db');
|
const db = require('../../models/db');
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
var mailer = require('../../helpers/mailer');
|
var mailer = require('../../helpers/mailer');
|
||||||
@@ -32,7 +33,7 @@ router.get('/current', function(req, res, next) {
|
|||||||
u.token = req.cookies['sdsession'];
|
u.token = req.cookies['sdsession'];
|
||||||
|
|
||||||
console.log(u);
|
console.log(u);
|
||||||
|
|
||||||
res.status(200).json(u);
|
res.status(200).json(u);
|
||||||
} else {
|
} else {
|
||||||
res.status(401).json({"error":"user_not_found"});
|
res.status(401).json({"error":"user_not_found"});
|
||||||
@@ -45,7 +46,7 @@ router.post('/', function(req, res) {
|
|||||||
res.status(400).json({"error":"email or password missing"});
|
res.status(400).json({"error":"email or password missing"});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var email = req.body["email"].toLowerCase();
|
var email = req.body["email"].toLowerCase();
|
||||||
var nickname = req.body["nickname"];
|
var nickname = req.body["nickname"];
|
||||||
var password = req.body["password"];
|
var password = req.body["password"];
|
||||||
@@ -56,17 +57,17 @@ router.post('/', function(req, res) {
|
|||||||
res.status(400).json({"error":"password_confirmation"});
|
res.status(400).json({"error":"password_confirmation"});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.invite_code && invite_code != config.invite_code) {
|
if (config.invite_code && invite_code != config.invite_code) {
|
||||||
res.status(400).json({"error":"Invalid Invite Code."});
|
res.status(400).json({"error":"Invalid Invite Code."});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validator.isEmail(email)) {
|
if (!validator.isEmail(email)) {
|
||||||
res.status(400).json({"error":"email_invalid"});
|
res.status(400).json({"error":"email_invalid"});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var createUser = function() {
|
var createUser = function() {
|
||||||
bcrypt.genSalt(10, function(err, salt) {
|
bcrypt.genSalt(10, function(err, salt) {
|
||||||
bcrypt.hash(password, salt, function(err, hash) {
|
bcrypt.hash(password, salt, function(err, hash) {
|
||||||
@@ -84,7 +85,7 @@ router.post('/', function(req, res) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
db.User.create(u)
|
db.User.create(u)
|
||||||
.error(err => {
|
.catch(err => {
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
})
|
})
|
||||||
.then(u => {
|
.then(u => {
|
||||||
@@ -95,7 +96,7 @@ router.post('/', function(req, res) {
|
|||||||
creator_id: u._id
|
creator_id: u._id
|
||||||
};
|
};
|
||||||
db.Space.create(homeFolder)
|
db.Space.create(homeFolder)
|
||||||
.error(err => {
|
.catch(err => {
|
||||||
res.sendStatus(400);
|
res.sendStatus(400);
|
||||||
})
|
})
|
||||||
.then(homeFolder => {
|
.then(homeFolder => {
|
||||||
@@ -112,9 +113,9 @@ router.post('/', function(req, res) {
|
|||||||
"state": "pending"
|
"state": "pending"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
res.status(201).json({});
|
res.status(201).json({});
|
||||||
})
|
})
|
||||||
.error(err => {
|
.catch(err => {
|
||||||
res.status(400).json(err);
|
res.status(400).json(err);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -123,7 +124,7 @@ router.post('/', function(req, res) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
db.User.findAll({where: {email: email}})
|
db.User.findAll({where: {email: email}})
|
||||||
.then(users => {
|
.then(users => {
|
||||||
if (users.length == 0) {
|
if (users.length == 0) {
|
||||||
@@ -193,7 +194,7 @@ router.delete('/:id', (req, res, next) => {
|
|||||||
// TODO: this doesn't currently work.
|
// TODO: this doesn't currently work.
|
||||||
// all objects (indirectly) belonging to the user have
|
// all objects (indirectly) belonging to the user have
|
||||||
// to be walked and deleted first.
|
// to be walked and deleted first.
|
||||||
|
|
||||||
user.destroy().then(err => {
|
user.destroy().then(err => {
|
||||||
if(err)res.status(400).json(err);
|
if(err)res.status(400).json(err);
|
||||||
else res.sendStatus(204);
|
else res.sendStatus(204);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const _ = require('underscore');
|
|||||||
const db = require('../models/db');
|
const db = require('../models/db');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
const Op = Sequelize.Op;
|
const Op = Sequelize.Op;
|
||||||
const uuidv4 = require('uuid/v4');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
res.render('index', { config:config, user:req.user });
|
res.render('index', { config:config, user:req.user });
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
|
|
||||||
<button class="btn btn-divider"></button>
|
<button class="btn btn-divider"></button>
|
||||||
|
|
||||||
|
<button class="btn btn-icon-labeled btn-transparent" v-on:click="start_pan()" v-bind:class="{active:active_tool=='pan'}" title="<%=__("tool_pan")%>">
|
||||||
|
<span class="icon icon-move"></span>
|
||||||
|
<span class="icon-label"><%=__("tool_pan")%></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<div class="dropdown top left light" v-bind:class="{open:opened_dialog=='shapes'}">
|
<div class="dropdown top left light" v-bind:class="{open:opened_dialog=='shapes'}">
|
||||||
<div class="btn-collapse in" title="<%=__("tool_shape")%>">
|
<div class="btn-collapse in" title="<%=__("tool_shape")%>">
|
||||||
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='shapes'}" v-on:click="open_dialog('shapes')">
|
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='shapes'}" v-on:click="open_dialog('shapes')">
|
||||||
|
|||||||
Reference in New Issue
Block a user