Merge branch 'master' of https://github.com/Microsoft/Prague
This commit is contained in:
Коммит
fa6c11842e
|
@ -0,0 +1,23 @@
|
|||
FROM node:6.9.4-alpine
|
||||
|
||||
# Install gulp for building services
|
||||
RUN npm install -g gulp
|
||||
|
||||
# Copy over and build ot-ink
|
||||
COPY ./ot-ink /usr/src/ot-ink
|
||||
WORKDIR /usr/src/ot-ink
|
||||
RUN npm install
|
||||
RUN gulp
|
||||
|
||||
# Copy over and build the server
|
||||
COPY ./server /usr/src/server
|
||||
WORKDIR /usr/src/server
|
||||
RUN npm install
|
||||
RUN gulp
|
||||
|
||||
# Expose the port the app runs under
|
||||
EXPOSE 3000
|
||||
EXPOSE 5858
|
||||
|
||||
# And set the default command to start the server
|
||||
CMD ["npm", "start"]
|
|
@ -4,12 +4,14 @@ services:
|
|||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "5858:5858"
|
||||
volumes:
|
||||
- .:/usr/src/server
|
||||
- .:/usr/src/
|
||||
depends_on:
|
||||
- mongodb
|
||||
- redis
|
||||
command: node --debug /usr/src/server/dist/www.js
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
mongodb:
|
||||
image: "mongo:3.4.1"
|
||||
image: "mongo:3.4.1"
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
dist/
|
|
@ -0,0 +1,36 @@
|
|||
var clean = require("gulp-clean");
|
||||
var gulp = require("gulp");
|
||||
var path = require('path');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var ts = require("gulp-typescript");
|
||||
var tslint = require("gulp-tslint");
|
||||
var tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
// Cleans up generated files
|
||||
gulp.task("clean", function () {
|
||||
return gulp.src(['dist'], { read: false }).pipe(clean());
|
||||
})
|
||||
|
||||
// Builds the server JavaScript code
|
||||
gulp.task("build", function () {
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(tsProject())
|
||||
.pipe(sourcemaps.write('.', {
|
||||
includeContent: false, sourceRoot: function (file) {
|
||||
return path.join(file.cwd, './src');
|
||||
}
|
||||
}))
|
||||
.pipe(gulp.dest("dist"))
|
||||
});
|
||||
|
||||
// Linting to validate style guide
|
||||
gulp.task("tslint", () =>
|
||||
gulp.src(['src/**/*.ts'])
|
||||
.pipe(tslint({
|
||||
formatter: "verbose"
|
||||
}))
|
||||
.pipe(tslint.report())
|
||||
);
|
||||
|
||||
gulp.task("default", ["tslint", "build"]);
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "ot-ink",
|
||||
"version": "0.1.0",
|
||||
"description": "Ink OT type",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"author": "Microsoft",
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-clean": "^0.3.2",
|
||||
"gulp-sourcemaps": "^1.9.1",
|
||||
"gulp-tslint": "^7.0.1",
|
||||
"gulp-typescript": "^3.1.3",
|
||||
"tslint": "^4.0.2",
|
||||
"typescript": "^2.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": "^6.0.52"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/**
|
||||
* Kind of MixInk action which is used to decide if current action is for draring, moving or clearing canvas
|
||||
*/
|
||||
export enum MixInkActionKind {
|
||||
Move = 0,
|
||||
Draw = 1,
|
||||
Clear = 2,
|
||||
}
|
||||
|
||||
/**
|
||||
* The action which is used to draw strokes
|
||||
*/
|
||||
export interface IMixInkAction {
|
||||
// Milliseconds since start of MixInking when this stroke should be drawn
|
||||
time: number;
|
||||
|
||||
// Move or darw
|
||||
kind: MixInkActionKind;
|
||||
|
||||
// x coordinate
|
||||
x: number;
|
||||
|
||||
// y coordinate
|
||||
y: number;
|
||||
|
||||
// TODO - should the ink pressure exist here
|
||||
|
||||
// Pen data if the pen has changed with this stroke
|
||||
pen?: IPen;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pen data for the current stroke
|
||||
*/
|
||||
export interface IPen {
|
||||
// Color in web format #rrggbb
|
||||
color: string;
|
||||
|
||||
// Thickness of pen in pixels
|
||||
thickness: number;
|
||||
|
||||
// Width and height for highlighter brush type
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
||||
// Brush type, by default b is 0
|
||||
brush?: number;
|
||||
}
|
||||
|
||||
export enum MixInkBlush {
|
||||
Pen = 0,
|
||||
Eraser = 1,
|
||||
Highlighter = 2,
|
||||
}
|
||||
|
||||
export enum SegmentCircleInclusive {
|
||||
None,
|
||||
Both,
|
||||
Start,
|
||||
End,
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
// Export the ink definition
|
||||
import * as ink from "./ink";
|
||||
export { ink as type };
|
||||
|
||||
export * from "./definitions";
|
|
@ -0,0 +1,48 @@
|
|||
// This is a really simple OT type.
|
||||
//
|
||||
// Its included for demonstration purposes and its used in the meta unit tests.
|
||||
//
|
||||
// This defines a really simple text OT type which only allows inserts. (No deletes).
|
||||
//
|
||||
// Ops look like:
|
||||
// {position:#, text:"asdf"}
|
||||
//
|
||||
// Document snapshots look like:
|
||||
// {str:string}
|
||||
|
||||
export const name = "simple";
|
||||
export const uri = "http://sharejs.org/types/simple";
|
||||
|
||||
// Create a new document snapshot. Initial data can be passed in.
|
||||
export function create(initial) {
|
||||
if (initial === null) {
|
||||
initial = "";
|
||||
}
|
||||
|
||||
return {str: initial};
|
||||
}
|
||||
|
||||
// Apply the given op to the document snapshot. Returns the new snapshot.
|
||||
export function apply(snapshot, op) {
|
||||
if (op.position < 0 || op.position > snapshot.str.length) {
|
||||
throw new Error("Invalid position");
|
||||
}
|
||||
|
||||
let str = snapshot.str;
|
||||
str = str.slice(0, op.position) + op.text + str.slice(op.position);
|
||||
return { str };
|
||||
}
|
||||
|
||||
// Transform op1 by op2. Returns transformed version of op1.
|
||||
// Sym describes the symmetry of the operation. Its either 'left' or 'right'
|
||||
// depending on whether the op being transformed comes from the client or the
|
||||
// server.
|
||||
export function transform(op1, op2, sym) {
|
||||
let pos = op1.position;
|
||||
|
||||
if (op2.position < pos || (op2.position === pos && sym === "left")) {
|
||||
pos += op2.text.length;
|
||||
}
|
||||
|
||||
return {position: pos, text: op1.text};
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"removeComments": false,
|
||||
"noImplicitAny": false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "tslint:recommended"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
FROM node:6.9.4-alpine
|
||||
|
||||
# Copy over the app source
|
||||
COPY . /usr/src/server
|
||||
WORKDIR /usr/src/server
|
||||
|
||||
# Install packages and then install the app
|
||||
RUN npm install -g gulp
|
||||
RUN npm install
|
||||
RUN gulp
|
||||
|
||||
# Expose the port the app runs under
|
||||
EXPOSE 3000
|
||||
|
||||
# And set the default command to start the server
|
||||
CMD ["npm", "start"]
|
|
@ -28,5 +28,8 @@
|
|||
"redis": {
|
||||
"host": "redis",
|
||||
"port": 6379
|
||||
},
|
||||
"mongo": {
|
||||
"connectionString": "mongodb://mongodb:27017"
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ let controllers = [
|
|||
{ name: "api", src: "src/api/index.ts", outFile: "api.js", folder: "public/dist/api", standalone: "pronet" },
|
||||
{ name: "calendar", src: "src/calendar/driver.ts", outFile: "driver.js", folder: "public/dist/views/calendar", standalone: "calendar" },
|
||||
{ name: "collab", src: "src/collab/collab.ts", outFile: "collab.js", folder: "public/dist/collab", standalone: "collab" },
|
||||
{ name: "canvas", src: "src/canvas/canvas.ts", outFile: "canvas.js", folder: "public/dist/canvas", standalone: "canvas" },
|
||||
{ name: "canvas", src: "src/canvas/index.ts", outFile: "canvas.js", folder: "public/dist/canvas", standalone: "canvas" },
|
||||
];
|
||||
|
||||
// Generate tasks for the browserified code
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
"morgan": "~1.6.1",
|
||||
"nconf": "^0.8.4",
|
||||
"node-uuid": "^1.4.7",
|
||||
"ot-ink": "file:../ot-ink",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-google-oauth": "^1.0.0",
|
||||
|
@ -65,7 +66,7 @@
|
|||
"serve-favicon": "~2.3.0",
|
||||
"sharedb": "^1.0.0-beta.6",
|
||||
"sharedb-mingo-memory": "^1.0.0-beta",
|
||||
"sharedb-mongo": "^1.0.0-beta.3",
|
||||
"sharedb-mongo": "git://github.com/kurtb/sharedb-mongo.git",
|
||||
"sharedb-redis-pubsub": "^1.0.0-beta.1",
|
||||
"socket.io-redis": "^1.1.1",
|
||||
"systemjs": "0.19.40",
|
||||
|
|
|
@ -39,7 +39,7 @@ export function createOrGetUser(
|
|||
details: IUserDetails): Promise<any> {
|
||||
|
||||
return accounts.getAccount(provider, providerId).then((account) => {
|
||||
// Check to see if there is an account - if not we need to create a new user
|
||||
// Check to see if there is an account - if not we need to create a new user
|
||||
let userIdP;
|
||||
if (account === null) {
|
||||
// Create a user first and then link this account to it
|
||||
|
|
|
@ -30,7 +30,7 @@ export class PostMessageHost implements IHost {
|
|||
|
||||
public start() {
|
||||
this.host = postMessageSockets.getOrCreateHost(this.window);
|
||||
// TODO for security we may need to define a set of allowed hosts -
|
||||
// TODO for security we may need to define a set of allowed hosts -
|
||||
// especially if the iframe conveys secret information to the host
|
||||
this.socketP = this.host.connect(window.parent, "*");
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ if (nconf.get("redis")) {
|
|||
servername: nconf.get("redis:host"),
|
||||
};
|
||||
|
||||
// Azure seems to lose our Redis client for SSL connections - we ping it to keep it alive.
|
||||
// Azure seems to lose our Redis client for SSL connections - we ping it to keep it alive.
|
||||
setInterval(() => {
|
||||
redisClient.ping((error, result) => {
|
||||
if (error) {
|
||||
|
@ -301,7 +301,7 @@ app.use((request, response, next) => {
|
|||
if (!request.session) {
|
||||
return next(new Error("Session not available"));
|
||||
} else {
|
||||
next(); // otherwise continue
|
||||
next(); // otherwise continue
|
||||
}
|
||||
});
|
||||
app.use(passport.initialize());
|
||||
|
|
|
@ -15,8 +15,8 @@ import {
|
|||
} from "../api/index";
|
||||
import { ICalendar, ICalendarEvent } from "./interfaces";
|
||||
|
||||
// The TypeScript compiler will elide these two libraries since they aren"t directly accessed.
|
||||
// They are jquery plugins and so internally attach themselves to the $ object. We do the import
|
||||
// The TypeScript compiler will elide these two libraries since they aren"t directly accessed.
|
||||
// They are jquery plugins and so internally attach themselves to the $ object. We do the import
|
||||
// below to force their inclusion while also keeping the version above to get the typing information
|
||||
import "fullcalendar";
|
||||
import "qtip2";
|
||||
|
@ -155,7 +155,7 @@ class CalendarViewModel {
|
|||
}
|
||||
|
||||
this.tableP.then((table) => {
|
||||
// Longer term we should standardize on some format here so there
|
||||
// Longer term we should standardize on some format here so there
|
||||
// isn't a disconnect between Office and moment
|
||||
const columnTimeFormatString = "m/d/yy h:mm AM/PM";
|
||||
let columns = [
|
||||
|
@ -168,7 +168,7 @@ class CalendarViewModel {
|
|||
{ name: "responseStatus", format: null },
|
||||
];
|
||||
|
||||
// Get and store the rows we will bind to the pnhost table -
|
||||
// Get and store the rows we will bind to the pnhost table -
|
||||
// we convert times to a format easier to parse by hosts (i.e. Excel)
|
||||
const formatString = "M/D/YY h:mm A";
|
||||
this.tableBoundRows = [];
|
||||
|
@ -190,7 +190,7 @@ class CalendarViewModel {
|
|||
// Load the rows into the hosted table
|
||||
table.loadData(columns, this.tableBoundRows);
|
||||
|
||||
// Setup a table listener if it doesn"t already exist
|
||||
// Setup a table listener if it doesn"t already exist
|
||||
if (!this.tableListener) {
|
||||
this.tableListener = new TableListener(this);
|
||||
table.addListener(this.tableListener);
|
||||
|
@ -200,7 +200,7 @@ class CalendarViewModel {
|
|||
|
||||
private loadAndCacheCalendars(): Promise<ICalendar[]> {
|
||||
return this.calendar.getCalendars().then((calendars) => {
|
||||
// Clear any pending deletes - a reload resets any interactions
|
||||
// Clear any pending deletes - a reload resets any interactions
|
||||
this.pendingDeletes = [];
|
||||
|
||||
// Initialize the custom UI once we load the first batch of data
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import App from "./canvas";
|
||||
import Canvas from "./canvas";
|
||||
import * as utils from "./utils";
|
||||
|
||||
export default class BackBoard {
|
||||
|
@ -8,16 +8,20 @@ export default class BackBoard {
|
|||
private div: HTMLElement;
|
||||
private gesture: MSGesture;
|
||||
|
||||
constructor(private appObject: App, htmlId: string) {
|
||||
constructor(private appObject: Canvas, htmlId: string) {
|
||||
this.div = utils.id(htmlId);
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
this.div["sysObject"] = this;
|
||||
|
||||
this.gesture = new MSGesture();
|
||||
this.gesture.target = this.div;
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
if (window["MSGesture"]) {
|
||||
this.gesture = new MSGesture();
|
||||
this.gesture.target = this.div;
|
||||
|
||||
this.div.addEventListener("MSGestureChange", this.gestureListener, false);
|
||||
this.div.addEventListener("MSGestureTap", this.gestureListener, false);
|
||||
}
|
||||
|
||||
this.div.addEventListener("MSGestureChange", this.gestureListener, false);
|
||||
this.div.addEventListener("MSGestureTap", this.gestureListener, false);
|
||||
this.div.addEventListener("pointerdown", this.eventListener, false);
|
||||
}
|
||||
|
||||
|
@ -30,7 +34,9 @@ export default class BackBoard {
|
|||
} else {
|
||||
// so.pointerId = evt.pointerId;
|
||||
if (evt.type === "pointerdown") {
|
||||
so.gesture.addPointer(evt.pointerId);
|
||||
if (so.gesture) {
|
||||
so.gesture.addPointer(evt.pointerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,145 +1,141 @@
|
|||
// The main app code
|
||||
import * as $ from "jquery";
|
||||
import * as otInk from "ot-ink";
|
||||
import * as sharedb from "sharedb/lib/client";
|
||||
import BackBoard from "./backBoard";
|
||||
import InkCanvas from "./inkCanvas";
|
||||
import StickyNote from "./stickyNote";
|
||||
import * as utils from "./utils";
|
||||
|
||||
let appObject;
|
||||
let sticky;
|
||||
let mainBoard;
|
||||
// Register the use of the rich text OT format
|
||||
sharedb.types.register(otInk.type);
|
||||
|
||||
// App Class
|
||||
export default class App {
|
||||
public ink: InkCanvas;
|
||||
// tslint:disable:no-console
|
||||
|
||||
public handleKeys: boolean = true;
|
||||
public stickyCount: number = 0;
|
||||
/**
|
||||
* Canvas app
|
||||
*/
|
||||
export default class Canvas {
|
||||
public ink: InkCanvas;
|
||||
|
||||
constructor() {
|
||||
public handleKeys: boolean = true;
|
||||
public stickyCount: number = 0;
|
||||
|
||||
// register all of the different handlers
|
||||
let p: HTMLElement = utils.id("hitPlane");
|
||||
this.ink = new InkCanvas(p);
|
||||
constructor() {
|
||||
// register all of the different handlers
|
||||
let p = document.getElementById("hitPlane");
|
||||
this.ink = new InkCanvas(p);
|
||||
|
||||
window.addEventListener("keydown", this.keyPress, false);
|
||||
window.addEventListener("keyup", this.keyRelease, false);
|
||||
window.addEventListener("keydown", (evt) => this.keyPress(evt), false);
|
||||
window.addEventListener("keyup", (evt) => this.keyRelease(evt), false);
|
||||
|
||||
// toolbar buttons
|
||||
document.querySelector("#strokeColors").addEventListener("click", (e) => { appObject.ink.inkColor(); }, false);
|
||||
document.querySelector("#clearButton").addEventListener("click", (e) => { appObject.clear(); }, false);
|
||||
document.querySelector("#undoButton").addEventListener("click", (e) => { appObject.ink.undo(); }, false);
|
||||
document.querySelector("#redoButton").addEventListener("click", (e) => { appObject.ink.redo(); }, false);
|
||||
document.querySelector("#testButton").addEventListener("click", (e) => { appObject.test(e); }, false);
|
||||
document.querySelector("#turnOnInk").addEventListener("click", (e) => { appObject.test(e); }, false);
|
||||
|
||||
}
|
||||
|
||||
// Key Handlers:
|
||||
// Escape
|
||||
// ^C Copy
|
||||
// ^V Paste
|
||||
// ^F Find
|
||||
// ^O Load
|
||||
// ^S Save
|
||||
// ^R Recognize
|
||||
// ^Q Quit (shuts down the sample app)
|
||||
// tslint:disable-next-line:no-empty
|
||||
public keyRelease(evt) {
|
||||
}
|
||||
|
||||
public keyPress(evt) {
|
||||
if (this.handleKeys === false) {
|
||||
return false;
|
||||
// toolbar buttons
|
||||
document.querySelector("#strokeColors").addEventListener("click", (e) => { this.ink.inkColor(); }, false);
|
||||
document.querySelector("#clearButton").addEventListener("click", (e) => { this.clear(); }, false);
|
||||
document.querySelector("#undoButton").addEventListener("click", (e) => { this.ink.undo(); }, false);
|
||||
document.querySelector("#redoButton").addEventListener("click", (e) => { this.ink.redo(); }, false);
|
||||
document.querySelector("#testButton").addEventListener("click", (e) => { this.test(e); }, false);
|
||||
document.querySelector("#turnOnInk").addEventListener("click", (e) => { this.test(e); }, false);
|
||||
document.querySelector("#replay").addEventListener("click", (e) => { this.ink.replay(); }, false);
|
||||
}
|
||||
|
||||
if (evt.keyCode === 27) { // Escape
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("Escape");
|
||||
} else if (evt.ctrlKey === true && evt.keyCode !== 17) { // look for keys while control down
|
||||
utils.displayStatus("KeyCode: " + evt.keyCode);
|
||||
if (evt.keyCode === 67) { // Control c
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-C");
|
||||
} else if (evt.keyCode === 86) { // Control v
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-V");
|
||||
} else if (evt.keyCode === 79) { // Control o
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-O");
|
||||
} else if (evt.keyCode === 83) { // Control s
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-S");
|
||||
} else if (evt.keyCode === 82) { // Control r
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-R");
|
||||
} else if (evt.keyCode === 81) { // Control q
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Q");
|
||||
} else if (evt.keyCode === 89) { // Control y
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Y");
|
||||
} else if (evt.keyCode === 90) { // Control z
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Z");
|
||||
}
|
||||
// Key Handlers:
|
||||
// Escape
|
||||
// ^C Copy
|
||||
// ^V Paste
|
||||
// ^F Find
|
||||
// ^O Load
|
||||
// ^S Save
|
||||
// ^R Recognize
|
||||
// ^Q Quit (shuts down the sample app)
|
||||
// tslint:disable-next-line:no-empty
|
||||
public keyRelease(evt) {
|
||||
}
|
||||
}
|
||||
|
||||
// this method will try up the entire board
|
||||
public clear() {
|
||||
appObject.ink.clear();
|
||||
let board = utils.id("content");
|
||||
let stickies = document.querySelectorAll(".stickyNote");
|
||||
// tslint:disable-next-line:prefer-for-of:stickies is NodeListOf not an array
|
||||
for (let i = 0; i < stickies.length; i++) {
|
||||
board.removeChild(stickies[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// find all of the things that are selected and unselect them
|
||||
public unselectAll() {
|
||||
let sel = document.querySelectorAll(".stickySelected");
|
||||
let elem;
|
||||
if (sel.length > 0) {
|
||||
for (let i = 0; i < sel.length; i++) {
|
||||
elem = sel.item(i);
|
||||
if (elem.classList.contains("stickySelected")) {
|
||||
elem.classList.remove("stickySelected");
|
||||
elem.style.zIndex = "1";
|
||||
public keyPress(evt) {
|
||||
if (this.handleKeys === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public makeInkable() {
|
||||
let sel = document.querySelectorAll(".stickySelected");
|
||||
let elem;
|
||||
if (sel.length > 0) {
|
||||
for (let i = 0; i < sel.length; i++) {
|
||||
elem = sel.item(i);
|
||||
elem.classList.add("stickyInkable");
|
||||
let ic = new InkCanvas(elem);
|
||||
}
|
||||
if (evt.keyCode === 27) { // Escape
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("Escape");
|
||||
} else if (evt.ctrlKey === true && evt.keyCode !== 17) { // look for keys while control down
|
||||
utils.displayStatus("KeyCode: " + evt.keyCode);
|
||||
if (evt.keyCode === 67) { // Control c
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-C");
|
||||
} else if (evt.keyCode === 86) { // Control v
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-V");
|
||||
} else if (evt.keyCode === 79) { // Control o
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-O");
|
||||
} else if (evt.keyCode === 83) { // Control s
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-S");
|
||||
} else if (evt.keyCode === 82) { // Control r
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-R");
|
||||
} else if (evt.keyCode === 81) { // Control q
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Q");
|
||||
} else if (evt.keyCode === 89) { // Control y
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Y");
|
||||
} else if (evt.keyCode === 90) { // Control z
|
||||
evt.preventDefault();
|
||||
utils.displayStatus("CTRL-Z");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is the handler for the test tube
|
||||
public test(e) {
|
||||
if (e.target.id === "testButton") {
|
||||
this.unselectAll();
|
||||
let x = new StickyNote(utils.id("content"));
|
||||
// this method will try up the entire board
|
||||
public clear() {
|
||||
this.ink.clear();
|
||||
let board = utils.id("content");
|
||||
let stickies = document.querySelectorAll(".stickyNote");
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let i = 0; i < stickies.length; i++) {
|
||||
board.removeChild(stickies[i]);
|
||||
}
|
||||
}
|
||||
if (e.target.id === "turnOnInk") {
|
||||
this.makeInkable();
|
||||
}
|
||||
}
|
||||
|
||||
// find all of the things that are selected and unselect them
|
||||
public unselectAll() {
|
||||
let sel = document.querySelectorAll(".stickySelected");
|
||||
let elem;
|
||||
if (sel.length > 0) {
|
||||
for (let i = 0; i < sel.length; i++) {
|
||||
elem = sel.item(i);
|
||||
if (elem.classList.contains("stickySelected")) {
|
||||
elem.classList.remove("stickySelected");
|
||||
elem.style.zIndex = "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public makeInkable() {
|
||||
let sel = document.querySelectorAll(".stickySelected");
|
||||
let elem;
|
||||
if (sel.length > 0) {
|
||||
for (let i = 0; i < sel.length; i++) {
|
||||
elem = sel.item(i);
|
||||
elem.classList.add("stickyInkable");
|
||||
let ic = new InkCanvas(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is the handler for the test tube
|
||||
public test(e) {
|
||||
if (e.target.id === "testButton") {
|
||||
this.unselectAll();
|
||||
let x = new StickyNote(utils.id("content"));
|
||||
}
|
||||
if (e.target.id === "turnOnInk") {
|
||||
this.makeInkable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create the new app
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
appObject = new App();
|
||||
sticky = new StickyNote(utils.id("content"));
|
||||
mainBoard = new BackBoard(appObject, "hitPlane");
|
||||
// id("ToolBar").appendChild(new ToolBarButton("images/icons/pencil.svg").click(appObject.clear).elem());
|
||||
});
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./point";
|
||||
export * from "./vector";
|
|
@ -0,0 +1,10 @@
|
|||
export interface IPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export class Point implements IPoint {
|
||||
// Constructor
|
||||
constructor(public x: number, public y: number) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
export interface IVector {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
length(): number;
|
||||
}
|
||||
|
||||
export class Vector implements IVector {
|
||||
/**
|
||||
* Returns the vector resulting from rotating vector by angle
|
||||
*/
|
||||
public static rotate(vector: Vector, angle: number): Vector {
|
||||
return new Vector(
|
||||
vector.x * Math.cos(angle) - vector.y * Math.sin(angle),
|
||||
vector.x * Math.sin(angle) + vector.y * Math.cos(angle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the normalized form of the given vector
|
||||
*/
|
||||
public static normalize(vector: Vector): Vector {
|
||||
let length = vector.length();
|
||||
return new Vector(vector.x / length, vector.y / length);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
constructor(public x: number, public y: number) {
|
||||
}
|
||||
|
||||
public length(): number {
|
||||
return Math.sqrt(this.x * this.x + this.y * this.y);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// The main app code
|
||||
import * as $ from "jquery";
|
||||
import * as otInk from "ot-ink";
|
||||
import * as sharedb from "sharedb/lib/client";
|
||||
import BackBoard from "./backBoard";
|
||||
import Canvas from "./canvas";
|
||||
import InkCanvas from "./inkCanvas";
|
||||
import StickyNote from "./stickyNote";
|
||||
import * as utils from "./utils";
|
||||
|
||||
// tslint:disable:no-console
|
||||
|
||||
// Register the use of the rich text OT format
|
||||
sharedb.types.register(otInk.type);
|
||||
|
||||
// throttle resize events and replace with an optimized version
|
||||
utils.throttle("resize", "throttled-resize");
|
||||
|
||||
// TODO export the ability to get events?
|
||||
|
||||
export function initialize(id: string) {
|
||||
// Open WebSocket connection to ShareDB server
|
||||
let protocol = window.location.protocol.indexOf("https") !== -1 ? "wss" : "ws";
|
||||
let socket = new WebSocket(`${protocol}://${window.location.host}`);
|
||||
let connection = new sharedb.Connection(socket);
|
||||
let doc = connection.get("canvas", id);
|
||||
|
||||
// create the new app
|
||||
$("document").ready(() => {
|
||||
let canvas = new Canvas();
|
||||
|
||||
let sticky = new StickyNote(utils.id("content"));
|
||||
let mainBoard = new BackBoard(canvas, "hitPlane");
|
||||
// id("ToolBar").appendChild(new ToolBarButton("images/icons/pencil.svg").click(appObject.clear).elem());
|
||||
|
||||
doc.subscribe((err) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
// If there is no type we need to create the document
|
||||
if (!doc.type) {
|
||||
console.log("Creating new document");
|
||||
doc.create("Hello", otInk.type.name);
|
||||
}
|
||||
|
||||
console.log(doc.data);
|
||||
|
||||
// To write more data
|
||||
doc.submitOp(
|
||||
{ position: 3, text: "World, " },
|
||||
{ source: canvas });
|
||||
|
||||
doc.on("op", (op, source) => {
|
||||
if (source === canvas) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the canvas
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -1,216 +1,390 @@
|
|||
import * as ink from "ot-ink";
|
||||
import * as geometry from "./geometry/index";
|
||||
import { Circle, IShape, Polygon } from "./shapes/index";
|
||||
import * as utils from "./utils";
|
||||
|
||||
// TODO split classes into separate files
|
||||
// tslint:disable:max-classes-per-file
|
||||
|
||||
// TODO remove before commit
|
||||
// tslint:disable:no-console
|
||||
|
||||
interface IPtrEvtPoint {
|
||||
x: number;
|
||||
y: number;
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
interface IPointerPointProps {
|
||||
isEraser: boolean;
|
||||
isEraser: boolean;
|
||||
}
|
||||
|
||||
class EventPoint {
|
||||
public rawPosition: IPtrEvtPoint;
|
||||
public properties: IPointerPointProps;
|
||||
public rawPosition: IPtrEvtPoint;
|
||||
public properties: IPointerPointProps;
|
||||
|
||||
constructor(evt: PointerEvent) {
|
||||
this.rawPosition = { x: evt.x, y: evt.y };
|
||||
this.properties = { isEraser: false };
|
||||
}
|
||||
constructor(evt: PointerEvent) {
|
||||
this.rawPosition = { x: evt.x, y: evt.y };
|
||||
this.properties = { isEraser: false };
|
||||
}
|
||||
}
|
||||
|
||||
export default class InkCanvas {
|
||||
public canvas: HTMLCanvasElement;
|
||||
public context: CanvasRenderingContext2D;
|
||||
public penID: number = -1;
|
||||
public gesture: MSGesture;
|
||||
public canvas: HTMLCanvasElement;
|
||||
public context: CanvasRenderingContext2D;
|
||||
public penID: number = -1;
|
||||
public gesture: MSGesture;
|
||||
|
||||
// constructor
|
||||
constructor(parent: HTMLElement) {
|
||||
// setup canvas
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.classList.add("drawSurface");
|
||||
parent.appendChild(this.canvas);
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
this.canvas["inkCanvas"] = this;
|
||||
// get context
|
||||
this.context = this.canvas.getContext("2d");
|
||||
private strokes: ink.IMixInkAction[] = [];
|
||||
|
||||
let w: number = this.canvas.offsetWidth;
|
||||
let h: number = this.canvas.offsetHeight;
|
||||
// constructor
|
||||
constructor(parent: HTMLElement) {
|
||||
// setup canvas
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.canvas.classList.add("drawSurface");
|
||||
parent.appendChild(this.canvas);
|
||||
|
||||
// set the width and height specified through CSS
|
||||
this.canvas.setAttribute("width", w.toString());
|
||||
this.canvas.setAttribute("height", h.toString());
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
window["strokes"] = this.strokes;
|
||||
|
||||
let bb = false;
|
||||
this.canvas.addEventListener("pointerdown", this.handlePointerDown, bb);
|
||||
this.canvas.addEventListener("pointermove", this.handlePointerMove, bb);
|
||||
this.canvas.addEventListener("pointerup", this.handlePointerUp, bb);
|
||||
}
|
||||
// tslint:disable:no-empty
|
||||
// Stubs for bunch of functions that are being called in the code below
|
||||
// this will make it easier to fill some code in later or just delete them
|
||||
// get context
|
||||
this.context = this.canvas.getContext("2d");
|
||||
|
||||
public tempEraseMode() {
|
||||
}
|
||||
let bb = false;
|
||||
this.canvas.addEventListener("pointerdown", (evt) => this.handlePointerDown(evt), bb);
|
||||
this.canvas.addEventListener("pointermove", (evt) => this.handlePointerMove(evt), bb);
|
||||
this.canvas.addEventListener("pointerup", (evt) => this.handlePointerUp(evt), bb);
|
||||
|
||||
public restoreMode() {
|
||||
}
|
||||
// Set the initial size of hte canvas and then register for resize events to be able to update it
|
||||
this.resize(this.canvas.offsetWidth, this.canvas.offsetHeight);
|
||||
window.addEventListener("throttled-resize", (event) => {
|
||||
this.resize(this.canvas.offsetWidth, this.canvas.offsetHeight);
|
||||
});
|
||||
}
|
||||
// tslint:disable:no-empty
|
||||
// Stubs for bunch of functions that are being called in the code below
|
||||
// this will make it easier to fill some code in later or just delete them
|
||||
|
||||
public renderAllStrokes() {
|
||||
}
|
||||
|
||||
public anchorSelection() {
|
||||
}
|
||||
|
||||
public selectAll() {
|
||||
}
|
||||
|
||||
public inkMode() {
|
||||
}
|
||||
|
||||
public inkColor() {
|
||||
}
|
||||
|
||||
public undo() {
|
||||
}
|
||||
|
||||
public redo() {
|
||||
}
|
||||
|
||||
// tslint:enable:no-empty
|
||||
|
||||
public anySelected(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We will accept pen down or mouse left down as the start of a stroke.
|
||||
// We will accept touch down or mouse right down as the start of a touch.
|
||||
public handlePointerDown(evt) {
|
||||
let ic = this.getInkCanvas();
|
||||
ic.penID = evt.pointerId;
|
||||
|
||||
if (evt.pointerType === "touch") {
|
||||
// ic.gesture.addPointer(evt.pointerId);
|
||||
public tempEraseMode() {
|
||||
}
|
||||
|
||||
if ((evt.pointerType === "pen") || ((evt.pointerType === "mouse") && (evt.button === 0))) {
|
||||
// Anchor and clear any current selection.
|
||||
ic.anchorSelection();
|
||||
let pt = new EventPoint(evt);
|
||||
|
||||
if (pt.properties.isEraser) { // The back side of a pen, which we treat as an eraser
|
||||
ic.tempEraseMode();
|
||||
} else {
|
||||
ic.restoreMode();
|
||||
}
|
||||
|
||||
ic.context.beginPath();
|
||||
ic.context.moveTo(pt.rawPosition.x, pt.rawPosition.y);
|
||||
|
||||
let pressureWidth = evt.pressure * 15;
|
||||
ic.context.lineWidth = pressureWidth;
|
||||
evt.returnValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
public handlePointerMove(evt) {
|
||||
let ic = this.getInkCanvas();
|
||||
if (evt.pointerId === ic.penID) {
|
||||
let pt = new EventPoint(evt);
|
||||
let w = 8;
|
||||
let h = 8;
|
||||
|
||||
if (evt.pointerType === "touch") {
|
||||
ic.context.strokeStyle = "gray";
|
||||
w = evt.width;
|
||||
h = evt.height;
|
||||
// context.strokeRect(evt.x - w/2 - 1, evt.y - h/2 -1 , w+1, h+1);
|
||||
ic.context.clearRect(evt.x - w / 4, evt.y - h / 4, w / 2, h / 2);
|
||||
evt.returnValue = false;
|
||||
|
||||
return false; // we"re going to clearRect instead
|
||||
}
|
||||
|
||||
if (evt.pointerType === "pen") {
|
||||
ic.context.strokeStyle = "rgba(0, 50, 0, 1)";
|
||||
w = w * (0.1 + evt.pressure);
|
||||
h = h * (0.1 + evt.pressure);
|
||||
} else { // just mouse
|
||||
ic.context.strokeStyle = "rgba(250, 0, 0, 0.5)";
|
||||
}
|
||||
|
||||
ic.context.lineWidth = w;
|
||||
ic.context.lineTo(evt.clientX, evt.clientY);
|
||||
ic.context.stroke();
|
||||
evt.returnValue = false;
|
||||
|
||||
// let pts = evt.intermediatePoints;
|
||||
// for (let i = pts.length - 1; i >= 0 ; i--) {
|
||||
// }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public handlePointerUp(evt) {
|
||||
let ic = this.getInkCanvas();
|
||||
if (evt.pointerId === ic.penID) {
|
||||
ic.penID = -1;
|
||||
let pt = new EventPoint(evt);
|
||||
// ic.context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
|
||||
// ic.context.stroke();
|
||||
ic.context.closePath();
|
||||
ic.renderAllStrokes();
|
||||
evt.returnValue = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// We treat the event of the pen leaving the canvas as the same as the pen lifting;
|
||||
// it completes the stroke.
|
||||
public handlePointerOut(evt) {
|
||||
let ic = this.getInkCanvas();
|
||||
if (evt.pointerId === ic.penID) {
|
||||
let pt = new EventPoint(evt);
|
||||
ic.context.lineTo(pt.rawPosition.x, pt.rawPosition.y);
|
||||
ic.context.stroke();
|
||||
ic.context.closePath();
|
||||
ic.penID = -1;
|
||||
ic.renderAllStrokes();
|
||||
public restoreMode() {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public handleTap(evt) {
|
||||
// Anchor and clear any current selection.
|
||||
let ic = this.getInkCanvas();
|
||||
if (ic.anySelected()) {
|
||||
ic.anchorSelection();
|
||||
ic.renderAllStrokes();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
if (!this.anySelected()) {
|
||||
this.selectAll();
|
||||
this.inkMode();
|
||||
public anchorSelection() {
|
||||
}
|
||||
|
||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
public selectAll() {
|
||||
}
|
||||
|
||||
this.renderAllStrokes();
|
||||
utils.displayStatus("");
|
||||
utils.displayError("");
|
||||
}
|
||||
public inkMode() {
|
||||
}
|
||||
|
||||
private getInkCanvas(): InkCanvas {
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
return this["inkCanvas"] as InkCanvas;
|
||||
}
|
||||
public inkColor() {
|
||||
}
|
||||
|
||||
public undo() {
|
||||
}
|
||||
|
||||
public redo() {
|
||||
}
|
||||
|
||||
// tslint:enable:no-empty
|
||||
|
||||
public anySelected(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We will accept pen down or mouse left down as the start of a stroke.
|
||||
// We will accept touch down or mouse right down as the start of a touch.
|
||||
public handlePointerDown(evt) {
|
||||
this.penID = evt.pointerId;
|
||||
|
||||
if (evt.pointerType === "touch") {
|
||||
// ic.gesture.addPointer(evt.pointerId);
|
||||
}
|
||||
|
||||
if ((evt.pointerType === "pen") || ((evt.pointerType === "mouse") && (evt.button === 0))) {
|
||||
// Anchor and clear any current selection.
|
||||
this.anchorSelection();
|
||||
let pt = new EventPoint(evt);
|
||||
|
||||
if (pt.properties.isEraser) { // The back side of a pen, which we treat as an eraser
|
||||
this.tempEraseMode();
|
||||
} else {
|
||||
this.restoreMode();
|
||||
}
|
||||
|
||||
this.addAndDrawStroke(pt.rawPosition, ink.MixInkActionKind.Move, evt.pressure);
|
||||
|
||||
evt.returnValue = false;
|
||||
}
|
||||
}
|
||||
|
||||
public handlePointerMove(evt) {
|
||||
if (evt.pointerId === this.penID) {
|
||||
// if (evt.pointerType === "touch") {
|
||||
// if (evt.pointerType === "pen") {
|
||||
// } else {
|
||||
// }
|
||||
|
||||
this.addAndDrawStroke({ x: evt.clientX, y: evt.clientY }, ink.MixInkActionKind.Draw, evt.pressure);
|
||||
|
||||
evt.returnValue = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public handlePointerUp(evt) {
|
||||
if (evt.pointerId === this.penID) {
|
||||
this.penID = -1;
|
||||
let pt = new EventPoint(evt);
|
||||
evt.returnValue = false;
|
||||
|
||||
this.addAndDrawStroke(pt.rawPosition, ink.MixInkActionKind.Draw, evt.pressure);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// We treat the event of the pen leaving the canvas as the same as the pen lifting;
|
||||
// it completes the stroke.
|
||||
public handlePointerOut(evt) {
|
||||
if (evt.pointerId === this.penID) {
|
||||
let pt = new EventPoint(evt);
|
||||
this.penID = -1;
|
||||
|
||||
this.addAndDrawStroke(pt.rawPosition, ink.MixInkActionKind.Draw, evt.pressure);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public handleTap(evt) {
|
||||
// Anchor and clear any current selection.
|
||||
if (this.anySelected()) {
|
||||
this.anchorSelection();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
if (!this.anySelected()) {
|
||||
this.selectAll();
|
||||
this.inkMode();
|
||||
}
|
||||
|
||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
utils.displayStatus("");
|
||||
utils.displayError("");
|
||||
}
|
||||
|
||||
public replay() {
|
||||
this.clearCanvas();
|
||||
|
||||
if (this.strokes.length > 0) {
|
||||
this.animateStroke(0);
|
||||
}
|
||||
}
|
||||
|
||||
private animateStroke(index: number) {
|
||||
// Draw the requested stroke
|
||||
let currentStroke = this.strokes[index];
|
||||
let previousStroke = index - 1 >= 0 ? this.strokes[index - 1] : null;
|
||||
this.drawStroke(currentStroke, previousStroke);
|
||||
|
||||
// And then ask for the next one
|
||||
let nextStroke = index + 1 < this.strokes.length ? this.strokes[index + 1] : null;
|
||||
if (nextStroke) {
|
||||
let time = nextStroke.time - currentStroke.time;
|
||||
setTimeout(() => this.animateStroke(index + 1), time);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the canvas
|
||||
*/
|
||||
private clearCanvas() {
|
||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
private redraw() {
|
||||
this.clearCanvas();
|
||||
|
||||
let previousStroke: ink.IMixInkAction = null;
|
||||
for (let stroke of this.strokes) {
|
||||
this.drawStroke(stroke, previousStroke);
|
||||
previousStroke = stroke;
|
||||
}
|
||||
}
|
||||
|
||||
private drawStroke(stroke: ink.IMixInkAction, previous: ink.IMixInkAction) {
|
||||
let shapes: IShape[];
|
||||
|
||||
switch (stroke.kind) {
|
||||
case ink.MixInkActionKind.Move:
|
||||
shapes = this.getShapes(stroke, stroke, ink.SegmentCircleInclusive.End);
|
||||
break;
|
||||
|
||||
case ink.MixInkActionKind.Draw:
|
||||
shapes = this.getShapes(previous, stroke, ink.SegmentCircleInclusive.End);
|
||||
break;
|
||||
|
||||
case ink.MixInkActionKind.Clear:
|
||||
this.clearCanvas();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (shapes) {
|
||||
for (let shape of shapes) {
|
||||
this.context.beginPath();
|
||||
shape.render(this.context);
|
||||
this.context.closePath();
|
||||
this.context.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the canvas
|
||||
*/
|
||||
private resize(width: number, height: number) {
|
||||
// Updates the size of the canvas
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
// And then redraw the canvas
|
||||
this.redraw();
|
||||
}
|
||||
|
||||
private addAndDrawStroke(
|
||||
pt: IPtrEvtPoint,
|
||||
kind: ink.MixInkActionKind,
|
||||
pressure: number,
|
||||
color: string = "rgba(0, 50, 0, 1)") {
|
||||
|
||||
let thickness = pressure * 15;
|
||||
|
||||
// store the stroke command
|
||||
let pen: ink.IPen = {
|
||||
brush: ink.MixInkBlush.Pen,
|
||||
color,
|
||||
thickness,
|
||||
};
|
||||
|
||||
let stroke: ink.IMixInkAction = {
|
||||
kind,
|
||||
pen,
|
||||
time: new Date().getTime(),
|
||||
x: pt.x,
|
||||
y: pt.y,
|
||||
};
|
||||
|
||||
this.strokes.push(stroke);
|
||||
let lastStroke = this.strokes.length > 1 ? this.strokes[this.strokes.length - 2] : null;
|
||||
|
||||
this.drawStroke(stroke, lastStroke);
|
||||
}
|
||||
|
||||
/***
|
||||
* given start point and end point, get MixInk shapes to render. The returned MixInk
|
||||
* shapes may contain one or two circles whose center is either start point or end point.
|
||||
* Enum SegmentCircleInclusive determins whether circle is in the return list.
|
||||
* Besides circles, a trapezoid that serves as a bounding box of two stroke point is also returned.
|
||||
*/
|
||||
private getShapes(
|
||||
startPoint: ink.IMixInkAction,
|
||||
endPoint: ink.IMixInkAction,
|
||||
circleInclusive: ink.SegmentCircleInclusive): IShape[] {
|
||||
|
||||
let dirVector = new geometry.Vector(endPoint.x - startPoint.x,
|
||||
endPoint.y - startPoint.y);
|
||||
let len = dirVector.length();
|
||||
|
||||
let shapes = new Array<IShape>();
|
||||
let trapezoidP0: geometry.IPoint;
|
||||
let trapezoidP1: geometry.IPoint;
|
||||
let trapezoidP2: geometry.IPoint;
|
||||
let trapezoidP3: geometry.IPoint;
|
||||
let normalizedLateralVector: geometry.IVector;
|
||||
let widthAtStart = startPoint.pen.thickness / 2;
|
||||
let widthAtEnd = endPoint.pen.thickness / 2;
|
||||
|
||||
// Just draws a circle on small values??
|
||||
if (len + Math.min(widthAtStart, widthAtEnd) <= Math.max(widthAtStart, widthAtEnd)) {
|
||||
let center = widthAtStart >= widthAtEnd ? startPoint : endPoint;
|
||||
shapes.push(new Circle({ x: center.x, y: center.y }, center.pen.thickness / 2));
|
||||
return shapes;
|
||||
}
|
||||
|
||||
if (len === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (widthAtStart !== widthAtEnd) {
|
||||
let angle = Math.acos(Math.abs(widthAtStart - widthAtEnd) / len);
|
||||
|
||||
if (widthAtStart < widthAtEnd) {
|
||||
angle = Math.PI - angle;
|
||||
}
|
||||
|
||||
normalizedLateralVector = geometry.Vector.normalize(geometry.Vector.rotate(dirVector, -angle));
|
||||
trapezoidP0 = new geometry.Point(
|
||||
startPoint.x + widthAtStart * normalizedLateralVector.x,
|
||||
startPoint.y + widthAtStart * normalizedLateralVector.y);
|
||||
trapezoidP3 = new geometry.Point(
|
||||
endPoint.x + widthAtEnd * normalizedLateralVector.x,
|
||||
endPoint.y + widthAtEnd * normalizedLateralVector.y);
|
||||
|
||||
normalizedLateralVector = geometry.Vector.normalize(geometry.Vector.rotate(dirVector, angle));
|
||||
trapezoidP2 = new geometry.Point(
|
||||
endPoint.x + widthAtEnd * normalizedLateralVector.x,
|
||||
endPoint.y + widthAtEnd * normalizedLateralVector.y);
|
||||
trapezoidP1 = new geometry.Point(
|
||||
startPoint.x + widthAtStart * normalizedLateralVector.x,
|
||||
startPoint.y + widthAtStart * normalizedLateralVector.y);
|
||||
} else {
|
||||
normalizedLateralVector = new geometry.Vector(-dirVector.y / len, dirVector.x / len);
|
||||
|
||||
trapezoidP0 = new geometry.Point(
|
||||
startPoint.x + widthAtStart * normalizedLateralVector.x,
|
||||
startPoint.y + widthAtStart * normalizedLateralVector.y);
|
||||
trapezoidP1 = new geometry.Point(
|
||||
startPoint.x - widthAtStart * normalizedLateralVector.x,
|
||||
startPoint.y - widthAtStart * normalizedLateralVector.y);
|
||||
|
||||
trapezoidP2 = new geometry.Point(
|
||||
endPoint.x - widthAtEnd * normalizedLateralVector.x,
|
||||
endPoint.y - widthAtEnd * normalizedLateralVector.y);
|
||||
trapezoidP3 = new geometry.Point(
|
||||
endPoint.x + widthAtEnd * normalizedLateralVector.x,
|
||||
endPoint.y + widthAtEnd * normalizedLateralVector.y);
|
||||
}
|
||||
|
||||
let polygon = new Polygon([trapezoidP0, trapezoidP3, trapezoidP2, trapezoidP1]);
|
||||
shapes.push(polygon);
|
||||
|
||||
switch (circleInclusive) {
|
||||
case ink.SegmentCircleInclusive.None:
|
||||
break;
|
||||
case ink.SegmentCircleInclusive.Both:
|
||||
shapes.push(new Circle({ x: startPoint.x, y: startPoint.y }, startPoint.pen.thickness / 2));
|
||||
shapes.push(new Circle({ x: endPoint.x, y: endPoint.y }, endPoint.pen.thickness / 2));
|
||||
break;
|
||||
case ink.SegmentCircleInclusive.Start:
|
||||
shapes.push(new Circle({ x: startPoint.x, y: startPoint.y }, startPoint.pen.thickness / 2));
|
||||
break;
|
||||
case ink.SegmentCircleInclusive.End:
|
||||
shapes.push(new Circle({ x: endPoint.x, y: endPoint.y }, endPoint.pen.thickness / 2));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
// private clearCanvas() {
|
||||
// this._context.clearRect(0, 0, this._width, this._height);
|
||||
// }
|
||||
|
||||
// private draw() {
|
||||
// //nothing to do if we are already drawing
|
||||
// if (this._drawing || !this._active || this._index >= this._inkData.length) return;
|
||||
|
||||
// this._drawing = true;
|
||||
|
||||
// var strokeTime = this._inkData[this._index].t;
|
||||
|
||||
// //draw MixInk until we reach lesson time
|
||||
// while (strokeTime <= this._currentTime && this._index < this._inkData.length) {
|
||||
// this.processAction();
|
||||
|
||||
// if (++this._index >= this._inkData.length) {
|
||||
// //MixInking finished
|
||||
// this._drawing = false;
|
||||
// //this.stop();
|
||||
// return;
|
||||
// }
|
||||
|
||||
// strokeTime = this._inkData[this._index].t;
|
||||
// }
|
||||
|
||||
// this._drawing = false;
|
||||
// }
|
||||
|
||||
// /***
|
||||
// * processes the MixInk action from MixInk timeline for current index
|
||||
// * returns: false if the MixInk has ended or doesn't need to be processed, true otherwise
|
||||
// */
|
||||
// private processAction() {
|
||||
// var action = this._inkData[this._index];
|
||||
|
||||
// // Prepare pen
|
||||
// this.updatePen(action.p, action.k === MixPlayerModels.MixInkActionKind.Draw);
|
||||
// if (!this._pen) {
|
||||
// this._logger.warn("MixInk.pen is not set on processAction");
|
||||
// // bad pen data.
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var shapes: Array<MixInk.IShape>;
|
||||
// var scaledDrawPoint: MixInk.IPoint;
|
||||
// var stylusPoint: MixInk.IStylusPoint;
|
||||
|
||||
// if (this._pen.b === MixPlayerModels.MixInkBlush.Highlighter) {
|
||||
// var scaledWidth = Math.max(1, this._pen.w * this._scaleX);
|
||||
// var scaledHeight = Math.max(2, this._pen.h * this._scaleY);
|
||||
|
||||
// scaledDrawPoint = { x: this._scaleX * action.x, y: this._scaleY * action.y };
|
||||
// stylusPoint = new MixInk.StylusPoint(scaledDrawPoint, 1, scaledWidth, scaledHeight);
|
||||
|
||||
// //process MixInk action as per the kind
|
||||
// switch (action.k) {
|
||||
// case MixPlayerModels.MixInkActionKind.Draw:
|
||||
// shapes = this.getHighliterShapes(this._lastStylusPoint, stylusPoint, false);
|
||||
// this._lastStylusPoint = stylusPoint;
|
||||
// break;
|
||||
// case MixPlayerModels.MixInkActionKind.Move:
|
||||
// shapes = this.getHighliterShapes(stylusPoint, stylusPoint, true);
|
||||
// this._lastStylusPoint = stylusPoint;
|
||||
// break;
|
||||
// case MixPlayerModels.MixInkActionKind.Clear:
|
||||
// this.clearCanvas();
|
||||
// this._lastStylusPoint = null;
|
||||
// break;
|
||||
// default:
|
||||
// this._logger.warn("MixInk.unsupported MixInk action. " + action.k);
|
||||
// break;
|
||||
// }
|
||||
// } else {
|
||||
// var scaledThickness = Math.max(1, this._pen.th * Math.max(0.5, this._scaleX));
|
||||
|
||||
// scaledDrawPoint = { x: this._scaleX * action.x, y: this._scaleY * action.y };
|
||||
// stylusPoint = new MixInk.StylusPoint(
|
||||
// scaledDrawPoint, scaledThickness,
|
||||
// MixInkPlayer.defaultHighlighterTipWidth, MixInkPlayer.defaultHighlighterTipHeight);
|
||||
// //process MixInk action as per the kind
|
||||
// switch (action.k) {
|
||||
// case MixPlayerModels.MixInkActionKind.Draw:
|
||||
// shapes = this.getShapes(this._lastStylusPoint, stylusPoint, SegmentCircleInclusive.End);
|
||||
// this._lastStylusPoint = stylusPoint;
|
||||
// break;
|
||||
// case MixPlayerModels.MixInkActionKind.Move:
|
||||
// shapes = this.getShapes(stylusPoint, stylusPoint, SegmentCircleInclusive.End);
|
||||
// this._lastStylusPoint = stylusPoint;
|
||||
// break;
|
||||
// case MixPlayerModels.MixInkActionKind.Clear:
|
||||
// //clear the canvas
|
||||
// this.clearCanvas();
|
||||
// this._lastStylusPoint = null;
|
||||
// break;
|
||||
// default:
|
||||
// this._logger.warn("MixInk.unsupported MixInk action. " + action.k);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Render shapes if there is any
|
||||
// if (shapes) {
|
||||
// shapes.forEach((item: MixInk.IShape) => {
|
||||
// this._context.beginPath();
|
||||
// item.render(this._context);
|
||||
// this._context.closePath();
|
||||
// this._context.fill();
|
||||
// });
|
||||
// }
|
||||
// }
|
|
@ -0,0 +1,17 @@
|
|||
import * as geometry from "../geometry/index";
|
||||
import { IShape } from "./shape";
|
||||
|
||||
export interface ICircle extends IShape {
|
||||
center: geometry.IPoint;
|
||||
radius: number;
|
||||
}
|
||||
|
||||
export class Circle implements ICircle {
|
||||
constructor(public center: geometry.IPoint, public radius: number) {
|
||||
}
|
||||
|
||||
public render(context2D: CanvasRenderingContext2D) {
|
||||
context2D.moveTo(this.center.x, this.center.y);
|
||||
context2D.arc(this.center.x, this.center.y, this.radius, 0, Math.PI * 2);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./circle";
|
||||
export * from "./polygon";
|
||||
export * from "./shape";
|
|
@ -0,0 +1,34 @@
|
|||
import * as geometry from "../geometry/index";
|
||||
import { IShape } from "./shape";
|
||||
|
||||
export interface IPolygon extends IShape {
|
||||
points: geometry.IPoint[];
|
||||
}
|
||||
|
||||
export class Polygon implements IPolygon {
|
||||
private isDirty: boolean = true;
|
||||
|
||||
/**
|
||||
* Constructs a new polygon composed of the given points. The polygon
|
||||
* takes ownership of the passed in array of points.
|
||||
*/
|
||||
constructor(public points: geometry.IPoint[]) {
|
||||
}
|
||||
|
||||
public render(context: CanvasRenderingContext2D) {
|
||||
if (this.points.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to the first point
|
||||
context.moveTo(this.points[0].x, this.points[0].y);
|
||||
|
||||
// Draw the rest of the segments
|
||||
for (let i = 1; i < this.points.length; i++) {
|
||||
context.lineTo(this.points[i].x, this.points[i].y);
|
||||
}
|
||||
|
||||
// And then close the shape
|
||||
context.lineTo(this.points[0].x, this.points[0].y);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export interface IShape {
|
||||
render(context2D: CanvasRenderingContext2D);
|
||||
}
|
|
@ -18,12 +18,16 @@ export default class StickyNote {
|
|||
this.div.style.top = "100px";
|
||||
this.div.style.left = "100px";
|
||||
|
||||
this.gesture = new MSGesture();
|
||||
this.gesture.target = this.div;
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
if (window["MSGesture"]) {
|
||||
this.gesture = new MSGesture();
|
||||
this.gesture.target = this.div;
|
||||
|
||||
this.div.addEventListener("MSGestureChange", this.eventListener, false);
|
||||
this.div.addEventListener("MSGestureTap", this.eventListener, false);
|
||||
this.div.addEventListener("pointerdown", this.eventListener, false);
|
||||
this.div.addEventListener("MSGestureChange", (evt) => this.eventListener(evt), false);
|
||||
this.div.addEventListener("MSGestureTap", (evt) => this.eventListener(evt), false);
|
||||
}
|
||||
|
||||
this.div.addEventListener("pointerdown", (evt) => this.eventListener(evt), false);
|
||||
|
||||
// insert the child into the DOM
|
||||
parent.appendChild(this.div);
|
||||
|
@ -49,7 +53,7 @@ export default class StickyNote {
|
|||
}
|
||||
|
||||
public manipulateElement(e) {
|
||||
// Uncomment the following code if you want to disable the built-in inertia
|
||||
// Uncomment the following code if you want to disable the built-in inertia
|
||||
// provided by dynamic gesture recognition
|
||||
|
||||
// if (false && (e.detail == e.MSGESTURE_FLAG_INERTIA))
|
||||
|
@ -57,10 +61,10 @@ export default class StickyNote {
|
|||
|
||||
// manipulate only with touch
|
||||
if (1 || e.pointerType === "touch") {
|
||||
// Get the latest CSS transform on the element
|
||||
// Get the latest CSS transform on the element
|
||||
let m;
|
||||
|
||||
// Get the latest CSS transform on the element in MS Edge
|
||||
// Get the latest CSS transform on the element in MS Edge
|
||||
m = new WebKitCSSMatrix(window.getComputedStyle(this.gesture.target, null).transform);
|
||||
|
||||
if (m) {
|
||||
|
|
|
@ -141,3 +141,23 @@ export function parseURL(url) {
|
|||
source: url,
|
||||
};
|
||||
}
|
||||
|
||||
// Following recomendations of https://developer.mozilla.org/en-US/docs/Web/Events/resize to
|
||||
// throttle computationally expensive events
|
||||
export function throttle(type: string, name: string, obj?: any) {
|
||||
obj = obj || window;
|
||||
let running = false;
|
||||
obj.addEventListener(
|
||||
type,
|
||||
() => {
|
||||
if (running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
requestAnimationFrame(() => {
|
||||
obj.dispatchEvent(new CustomEvent(name));
|
||||
running = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -70,7 +70,7 @@ function _inherits(subClass, superClass) {
|
|||
}
|
||||
}
|
||||
|
||||
// tslint:disable:only-arrow-functions:simplify to work around es6 classes to typescript difficulties
|
||||
// tslint:disable:only-arrow-functions
|
||||
// tslint:disable-next-line:variable-name
|
||||
let VideoBlot: any = function (_BlockEmbed3) {
|
||||
// tslint:disable-next-line:no-var-keyword no-shadowed-variable
|
||||
|
@ -126,7 +126,7 @@ let host = new ivy.Host({
|
|||
secret: "IvyBearerToken",
|
||||
});
|
||||
|
||||
// tslint:disable:only-arrow-functions:simplify to work around es6 classes to typescript difficulties
|
||||
// tslint:disable:only-arrow-functions
|
||||
// tslint:disable-next-line:variable-name
|
||||
let ChartBlot: any = function (_BlockEmbed3) {
|
||||
// tslint:disable-next-line:no-var-keyword no-shadowed-variable
|
||||
|
@ -220,7 +220,7 @@ export function connect(id: string, sync: boolean) {
|
|||
let range = quill.getSelection(true);
|
||||
quill.insertText(range.index, "\n", Quill.sources.USER);
|
||||
|
||||
// Disable rules for simplicity with Ivy input format
|
||||
// Disable rules for simplicity with Ivy input format
|
||||
let chartDef = {
|
||||
chartTitleEdge: 1,
|
||||
chartTitlePosition: 1,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as _ from "lodash";
|
||||
import * as nconf from "nconf";
|
||||
import * as ink from "ot-ink";
|
||||
import * as redis from "redis";
|
||||
import * as richText from "rich-text";
|
||||
import * as ShareDB from "sharedb";
|
||||
|
@ -25,10 +26,12 @@ subOptions.return_buffers = true;
|
|||
let client = redis.createClient(redisPort, redisHost, pubOptions);
|
||||
let observer = redis.createClient(redisPort, redisHost, subOptions);
|
||||
|
||||
// Register rich type as one of our OT formats
|
||||
// Register our OT formats
|
||||
ShareDB.types.register(richText.type);
|
||||
ShareDB.types.register(ink.type);
|
||||
|
||||
let db = new ShareDBMongo("mongodb://mongodb:27017");
|
||||
let mongoConnectionString = nconf.get("mongo:connectionString");
|
||||
let db = new ShareDBMongo(mongoConnectionString);
|
||||
let pubsub = new ShareDBRedisPub({ client, observer });
|
||||
let shareDb = new ShareDB({
|
||||
db,
|
||||
|
|
|
@ -72,7 +72,7 @@ class TableService implements ITableService {
|
|||
templateUrl: "templates/document.component.html",
|
||||
})
|
||||
export class DocumentComponent implements OnInit {
|
||||
// Loading flag for the document
|
||||
// Loading flag for the document
|
||||
public loaded: boolean = false;
|
||||
|
||||
public url: string;
|
||||
|
|
|
@ -17,7 +17,7 @@ export class InteractiveDocumentFrameComponent implements AfterViewInit {
|
|||
}
|
||||
|
||||
public ngAfterViewInit() {
|
||||
// Load in the bound view
|
||||
// Load in the bound view
|
||||
let iframe = this.elementRef.nativeElement.querySelector("iframe");
|
||||
iframe.setAttribute("src", this.url);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// The require here is deliberate. The TypeScript browserify will elide this line if
|
||||
// The require here is deliberate. The TypeScript browserify will elide this line if
|
||||
// a standard import given no modules are used. We are including the polyfills so we pick
|
||||
// them up with our browserify package.
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ export class PostMessageHost implements IPostMessageHost {
|
|||
* Client is requesting to connect to the server
|
||||
*/
|
||||
private processConnect(event: MessageEvent, message: IPacket): void {
|
||||
// Ignore connection events if we aren"t listening
|
||||
// Ignore connection events if we aren"t listening
|
||||
if (!this.connectionCallback) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log("Client is attempting to connect but the server is not listening");
|
||||
|
@ -106,7 +106,7 @@ export class PostMessageHost implements IPostMessageHost {
|
|||
}
|
||||
|
||||
/**
|
||||
* Retrieves a new number to represent a connection
|
||||
* Retrieves a new number to represent a connection
|
||||
*/
|
||||
private getConnectionId(): number {
|
||||
return this.nextConnectionId++;
|
||||
|
|
|
@ -55,7 +55,7 @@ export class PostMessageSocket implements IPostMessageSocket {
|
|||
|
||||
public processMessageReceipt(message: IMessage) {
|
||||
// reject the message if no listener is defined.
|
||||
// Alternatively if needed we could buffer messages until one is defined. But the former is simpler.
|
||||
// Alternatively if needed we could buffer messages until one is defined. But the former is simpler.
|
||||
if (!this.listener) {
|
||||
this.postMessage(MessageType.Failure, { message: "No handler defined" });
|
||||
return;
|
||||
|
|
|
@ -61,7 +61,7 @@ router.get("/", (req: express.Request, response: express.Response) => {
|
|||
if (error) {
|
||||
return reject(error);
|
||||
} else {
|
||||
// MSFT strings are in UTC but don"t place the UTC marker in the date string -
|
||||
// MSFT strings are in UTC but don"t place the UTC marker in the date string -
|
||||
// convert to this format to standardize the input to CalendarEvent
|
||||
let microsoftResults: ICalendarEvent[] = body.value.map((item) => {
|
||||
let loc = item.location ? item.location.displayName : "";
|
||||
|
@ -136,7 +136,8 @@ router.get("/", (req: express.Request, response: express.Response) => {
|
|||
});
|
||||
|
||||
router.delete("/:provider/:id", (req: express.Request, response: express.Response) => {
|
||||
// tslint:disable:no-string-literal:easier access to param data
|
||||
// easier access to param data
|
||||
// tslint:disable:no-string-literal
|
||||
let provider = req.params["provider"];
|
||||
let eventId = req.params["id"];
|
||||
// tslint:enable:no-string-literal
|
||||
|
|
|
@ -4,12 +4,15 @@ import { defaultPartials } from "./partials";
|
|||
let router = express.Router();
|
||||
|
||||
router.get("/:id", (req: express.Request, response: express.Response) => {
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
let id = req.params["id"];
|
||||
|
||||
response.render(
|
||||
"canvas",
|
||||
{
|
||||
// tslint:disable-next-line:no-string-literal
|
||||
id: `Canvas - ${req.params["id"]}`,
|
||||
id,
|
||||
partials: defaultPartials,
|
||||
title: `Canvas - ${id}`,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ import * as passport from "passport";
|
|||
import * as accounts from "../accounts";
|
||||
import * as authOptions from "./authOptions";
|
||||
|
||||
// tslint:disable-next-line:no-var-requires:simpler code path and module not setup for import
|
||||
// simpler code path and module not setup for import
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
let ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn;
|
||||
|
||||
let router = express.Router();
|
||||
|
|
|
@ -8,8 +8,8 @@ import * as request from "request";
|
|||
import * as accounts from "../accounts";
|
||||
import { defaultPartials } from "./partials";
|
||||
|
||||
// tslint:disable:no-console:Server side code
|
||||
// tslint:disable:max-line-length:TODO split get into helper functions
|
||||
// tslint:disable:no-console
|
||||
// tslint:disable:max-line-length
|
||||
|
||||
let router = express.Router();
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as express from "express";
|
||||
import { defaultPartials } from "./partials";
|
||||
|
||||
// tslint:disable-next-line:no-var-requires:simpler code path and module not setup for import
|
||||
// simpler code path and module not setup for import
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
let ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn;
|
||||
|
||||
let router = express.Router();
|
||||
|
|
|
@ -3,7 +3,8 @@ import * as _ from "lodash";
|
|||
import { IUser } from "../accounts";
|
||||
import { defaultPartials } from "./partials";
|
||||
|
||||
// tslint:disable-next-line:no-var-requires:simpler code path and module not setup for import
|
||||
// simpler code path and module not setup for import
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
let ensureLoggedIn = require("connect-ensure-login").ensureLoggedIn;
|
||||
let router = express.Router();
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class View implements IView {
|
|||
}
|
||||
|
||||
class Views implements IViews {
|
||||
// tslint:disable:variable-name:JSON format contains _
|
||||
// tslint:disable:variable-name
|
||||
public _links: { [rel: string]: ILink | ILink[] };
|
||||
public _embedded: { [rel: string]: View[] };
|
||||
// tslint:enable:variable-name
|
||||
|
|
|
@ -5,7 +5,7 @@ import * as nconf from "nconf";
|
|||
import * as path from "path";
|
||||
|
||||
// Setup the configuration system - pull arguments, then environment letiables
|
||||
nconf.argv().env().file(path.join(__dirname, "../config.json")).use("memory");
|
||||
nconf.argv().env(<any> "__").file(path.join(__dirname, "../config.json")).use("memory");
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
|
|
|
@ -33,10 +33,14 @@
|
|||
<button id="clearButton" value="clear">Clear</button>
|
||||
<button id="testButton">Reco</button>
|
||||
<button id="turnOnInk">ink</button>
|
||||
<button id="replay">Replay</button>
|
||||
</div>
|
||||
{{/content}}
|
||||
|
||||
{{$scripts}}
|
||||
<script src="/dist/canvas/canvas.js"></script>
|
||||
<script>
|
||||
canvas.initialize("{{id}}");
|
||||
</script>
|
||||
{{/scripts}}
|
||||
{{/layout}}
|
Загрузка…
Ссылка в новой задаче