Compare commits

...

7 Commits

@ -0,0 +1,82 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"plugins": [
"react",
"react-hooks"
],
"rules": {
/* Things that should effectively be syntax errors. */
"indent": [ "error", "tab", {
SwitchCase: 1
}],
"linebreak-style": [ "error", "unix" ],
"semi": [ "error", "always" ],
/* Things that are always mistakes. */
"getter-return": [ "error" ],
"no-compare-neg-zero": [ "error" ],
"no-dupe-args": [ "error" ],
"no-dupe-keys": [ "error" ],
"no-duplicate-case": [ "error" ],
"no-empty": [ "error" ],
"no-empty-character-class": [ "error" ],
"no-ex-assign": [ "error" ],
"no-extra-semi": [ "error" ],
"no-func-assign": [ "error" ],
"no-invalid-regexp": [ "error" ],
"no-irregular-whitespace": [ "error" ],
"no-obj-calls": [ "error" ],
"no-sparse-arrays": [ "error" ],
"no-undef": [ "error" ],
"no-unreachable": [ "error" ],
"no-unsafe-finally": [ "error" ],
"use-isnan": [ "error" ],
"valid-typeof": [ "error" ],
"curly": [ "error" ],
"no-caller": [ "error" ],
"no-fallthrough": [ "error" ],
"no-extra-bind": [ "error" ],
"no-extra-label": [ "error" ],
"array-callback-return": [ "error" ],
"prefer-promise-reject-errors": [ "error" ],
"no-with": [ "error" ],
"no-useless-concat": [ "error" ],
"no-unused-labels": [ "error" ],
"no-unused-expressions": [ "error" ],
"no-unused-vars": [ "error" , {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
} ],
"no-return-assign": [ "error" ],
"no-self-assign": [ "error" ],
"no-new-wrappers": [ "error" ],
"no-redeclare": [ "error" ],
"no-loop-func": [ "error" ],
"no-implicit-globals": [ "error" ],
"strict": [ "error", "global" ],
/* Make JSX not cause 'unused variable' errors. */
"react/jsx-uses-react": ["error"],
"react/jsx-uses-vars": ["error"],
/* Development code that should be removed before deployment. */
"no-console": [ "warn" ],
"no-constant-condition": [ "warn" ],
"no-debugger": [ "warn" ],
"no-alert": [ "warn" ],
"no-warning-comments": ["warn", {
terms: ["fixme"]
}],
/* Common mistakes that can *occasionally* be intentional. */
"no-template-curly-in-string": ["warn"],
"no-unsafe-negation": [ "warn" ],
}
};

11
.gitignore vendored

@ -1,12 +1,3 @@
config.json
.sass-cache
sasswatch.log
sasswatch.err
sasswatch.pid
node_modules
src/scss/*.css
src/scss/*.css.map
src/elements/*.css
src/elements/*.css.map
/bower_components/
/old/
/public/js/bundle.js

@ -1,20 +0,0 @@
'use strict';
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
let app = express();
app.set("view engine", "jade");
app.set("views", path.join(__dirname, "views"));
app.use(express.static(path.join(__dirname, "public")));
app.use(bodyParser.urlencoded({
extended: true
}));
app.use("/", require("./lib/routes/index"));
app.listen(3000);

@ -0,0 +1,47 @@
"use strict";
const path = require("path");
const app = require("../src/server/app.js")();
if (process.env.NODE_ENV === "development") {
const budo = require("budo");
function projectPath(targetPath) {
return path.resolve(__dirname, "..", targetPath);
}
budo(projectPath("src/client/index.jsx"), {
watchGlob: projectPath("public/css/*.css"),
live: "**/*.css",
stream: process.stdout,
port: 8000,
/* NOTE: If the below is not set to match watchGlob, then livereloads will silently fail */
dir: projectPath("public"),
serve: "js/bundle.js",
debug: true,
browserify: {
extensions: [".jsx"],
plugin: [
"browserify-hmr"
],
transform: [
["babelify", {
presets: ["@babel/preset-env", "@babel/preset-react"],
plugins: ["react-hot-loader/babel"]
}]
]
},
middleware: function (req, res, next) {
app.handle(req, res, (err) => {
if (err != null && err instanceof Error) {
res.send("<pre>" + err.stack + "</pre>");
} else {
next(err);
}
});
}
});
} else {
app.listen(3000);
}

@ -0,0 +1,7 @@
"use strict";
const createError = require("create-error");
module.exports = {
SyntaxError: createError("SyntaxError", {isFatal: false})
};

@ -0,0 +1,25 @@
"use strict";
const errors = require("./errors");
module.exports = function createSyntaxError({line, column, error}) {
if (line == null) {
throw new Error("Must specify a line number");
} else if (column == null) {
throw new Error("Must specify a column number");
} else if (error == null) {
throw new Error("Must specify what caused the syntax error");
} else if (typeof line !== "number") {
throw new Error("Line number must be numeric");
} else if (typeof column !== "number") {
throw new Error("Column number must be numeric");
} else if (typeof error !== "string") {
throw new Error("Syntax error cause must be a string");
} else {
return new errors.SyntaxError("A syntax error occurred", {
line: line,
column: column,
error: error
});
}
};

@ -0,0 +1,52 @@
"use strict";
const Promise = require("bluebird");
const createEventEmitter = require("create-event-emitter");
const defaultValue = require("default-value");
const mergeMetadata = require("./util/merge-metadata");
module.exports = function createFileSink(options) {
let emitter = createEventEmitter({
__buildStep: "sink",
displayName: options.displayName,
supportsAcknowledgment: options.supportsAcknowledgment,
supportsStreams: options.supportsStreams,
sink: function (inputFile) {
let {metadata} = inputFile;
try {
let result = options.sink(inputFile);
if (result != null) {
return Promise.try(() => {
return result;
}).then((extraMetadata = {}) => {
emitter.emit("sunk", {
metadata: mergeMetadata(extraMetadata, {
inputFileMetadata: metadata
}),
stream: inputFile.stream,
contents: inputFile.contents
});
}).catch((err) => {
reportError(inputFile, err);
});
}
} catch (err) {
reportError(inputFile, err);
}
}
});
function reportError(inputFile, error) {
emitter.emit("error", {
file: inputFile,
error: error,
isFatal: error.isFatal,
step: emitter
});
}
return emitter;
};

@ -0,0 +1,55 @@
"use strict";
const createEventEmitter = require("create-event-emitter");
const path = require("path");
const mergeMetadata = require("./util/merge-metadata");
module.exports = function createFileSource(options) {
function resolvePath(relativePath) {
return path.resolve("", relativePath);
}
let emitter = createEventEmitter({
__buildStep: "source",
displayName: options.displayName,
supportsTeardown: options.supportsTeardown,
basePath: options.basePath,
initialize: function (initializationOptions) {
try {
return options.initialize({
options: initializationOptions,
resolvePath: resolvePath,
pushFile: newFile,
reportError: reportError
});
} catch (error) {
reportError(error);
}
},
teardown: options.teardown
});
function newFile({metadata, contents, stream}) {
let augmentedMetadata = mergeMetadata(metadata, {
transform: emitter,
sourcedDate: new Date()
});
emitter.emit("file", {
metadata: augmentedMetadata,
contents: contents,
stream: stream
});
}
function reportError(error, isFatal) {
emitter.emit("error", {
error: error,
isFatal: isFatal,
step: emitter
});
}
return emitter;
};

@ -0,0 +1,13 @@
"use strict";
module.exports = {
defineTask: function (func) {
return func;
}, // callback
pipeline: require("./pipeline"),
watch: require("./watch"),
saveToDirectory: require("./save-to-directory"), // folder path
createFileSource: require("./file-source"),
createFileSink: require("./file-sink"),
createSingleFileTransform: require("./single-file-transform")
};

@ -0,0 +1,104 @@
"use strict";
const path = require("path");
const defaultValue = require("default-value");
const createEventEmitter = require("create-event-emitter");
const mergeMetadata = require("./util/merge-metadata");
module.exports = function pipeline(steps) {
if (steps.length < 2) {
throw new Error("A pipeline must consist of at least two steps");
} else if (steps[0].__buildStep !== "source") {
throw new Error("The first transform in a pipeline must be a file source");
} else if (steps[steps.length - 1].__buildStep !== "sink") {
throw new Error("The last transform in a pipeline must be a file sink");
} else {
let basePath = steps[0].basePath;
let sources = steps.slice(0, -1);
let sinks = steps.slice(1);
let firstSource = sources[0];
let finalSink = sinks[sinks.length - 1];
let emitter = createEventEmitter({
basePath: basePath, /* QUESTION: Should sources be required to provide this? */
steps: steps, /* FIXME: Make private? */
initialize: function () {
let allSinksSupportStreams = sinks.every((sink) => sink.supportsStreams === true);
/* This will start the flow. */
firstSource.initialize({
supportsStreams: allSinksSupportStreams
});
}
});
steps.forEach((step) => {
step.on("error", (data) => {
if (data instanceof Error) {
/* FIXME: This is because any error within an event handler will automatically result in an `error` event. Probably should rename the transform error event to avoid interfering with this. */
process.nextTick(() => {
throw data;
});
} else {
let error = Object.assign({
isFatal: true
}, data);
emitter.emit("error", error);
}
});
});
sources.forEach((source, i) => {
source.on("file", ({metadata, contents, stream}) => {
/* FIXME: Add stream-to-contents conversion? */
let augmentedMetadata;
if (metadata.isVirtual === false && metadata.relativePath == null) {
/* FIXME: Add check that 1) absolute path is set, and 2) it lies within the basePath */
augmentedMetadata = mergeMetadata(metadata, {
relativePath: path.relative(basePath, metadata.path)
});
} else {
augmentedMetadata = metadata;
}
augmentedMetadata.basePath = basePath;
/* NOTE: The below indexes into `steps`, not `sources`, because the last source sends to the final sink. Both `steps` and `sources` arrays start at the same point. */
let nextSink = steps[i + 1];
try {
let sinkInput = {
metadata: augmentedMetadata,
contents: contents,
stream: stream
};
if (nextSink.__buildStep === "transform") {
nextSink.transform(sinkInput);
} else if (nextSink.__buildStep === "sink") {
nextSink.sink(sinkInput);
}
} catch (error) {
emitter.emit("error", {
step: nextSink,
file: augmentedMetadata,
error: error,
isFatal: defaultValue(error.isFatal, true)
});
}
});
});
finalSink.on("sunk", ({metadata}) => {
emitter.emit("done", {
metadata: metadata,
acknowledged: finalSink.supportsAcknowledgment
});
});
return emitter;
}
};

@ -0,0 +1,45 @@
"use strict";
function midBranch(line) {
return `${line}`;
}
function midPipe(line) {
return `${line}`;
}
function destinationBranch(line) {
return ` └🠲 ${line}`;
}
function destinationPipe(line) {
return ` ${line}`;
}
function item(text, branchFunc, pipeFunc) {
let lines = text.split("\n");
let firstLine = branchFunc(lines[0]);
if (lines.length === 1) {
return firstLine;
} else {
let otherLines = lines.slice(1).map((line) => pipeFunc(line));
return [firstLine].concat(otherLines).join("\n");
}
}
module.exports = function drawTransformTree(history) {
let firstItem = history[0];
let lastItem = item(history[history.length - 1], destinationBranch, destinationPipe);
let otherItems;
if (history.length > 2) {
otherItems = history.slice(1, -1).map((historyItem) => {
return item(historyItem, midBranch, midPipe);
});
} else {
otherItems = [];
}
return [firstItem].concat(otherItems).concat([lastItem]).join("\n");
};

@ -0,0 +1,231 @@
"use strict";
const chalk = require("chalk");
const moment = require("moment");
const path = require("path");
const traverseTransformHistory = require("../util/traverse-transform-history");
const drawTransformTree = require("./draw-transform-tree");
const errors = require("../errors/errors");
function formatDate(date) {
return chalk.dim(`[${moment(date).format("LTS")}]`);
}
function formatSuccessfulSourcePath(sourcePath) {
return chalk.bgGreen.black(`${sourcePath} `);
}
function formatFailedSourcePath(sourcePath) {
return chalk.bgRed.white(`${sourcePath} `);
}
function formatDuration(ms) {
return chalk.bold(`${ms}ms`);
}
function formatTransformDisplayName(displayName) {
return displayName;
}
function formatTransformPath(relativePath) {
return chalk.dim(`[${relativePath}]`);
}
function formatSinkPath(sinkPath) {
return chalk.bold(sinkPath);
}
function formatSuccessfulSourceStep(metadata) {
return `${formatSuccessfulSourcePath(metadata.relativePath)} ${formatDate(metadata.sourcedDate)}`;
}
function formatSuccessfulTransformStep(metadata) {
return `${formatDuration(metadata.stepDuration)} ${formatTransformDisplayName(metadata.step.displayName)} ${formatTransformPath(metadata.relativePath)}`;
}
function formatSuccessfulSinkStep(metadata) {
return formatSinkPath(metadata.targetPath);
}
function formatMissingTransformStep(step) {
return chalk.dim(`${chalk.bold("not run:")} ${step.displayName}`);
}
function formatMissingSinkStep() {
return chalk.dim("(no output file produced)");
}
function formatFailedSourceStep(source) {
return `${formatFailedSourcePath(source.relativePath)} ${formatDate(source.sourcedDate)}`;
}
function formatFailedTransformStepName(step, duration) {
return chalk.red(`${formatDuration(duration)} ${step.displayName}`);
}
function formatErrorDetail(name, description) {
return chalk.red(`${chalk.bold(`${name}:`)} ${description}`);
}
function formatFailedTransformStep(step, file, error, pipeline, duration) {
if (error instanceof errors.SyntaxError) {
let prefix = formatErrorPrefix("Syntax Error");
let message = formatErrorMessage(error.error);
return [
formatFailedTransformStepName(step, duration),
`${prefix}${message}`,
formatErrorDetail("File", `${path.resolve(pipeline.basePath, file.metadata.relativePath)} ${file.metadata.isVirtual ? chalk.dim("(virtual)") : ""}`),
formatErrorDetail("Location", `Line ${error.line}, column ${error.column}`),
(file.metadata.isVirtual)
? chalk.bgRed.white.bold("This file is a virtual file. That means that the syntax error probably originates from another earlier transform, and there may be a bug in that transform.")
: ""
].join("\n");
} else {
let prefix = formatErrorPrefix("Error");
let message = formatErrorMessage(`An unexpected error occurred in the transform`);
/* FIXME: */
// if (error instanceof Error) {
// console.log(chalk.red(error.stack));
// } else {
// console.log(chalk.red(util.inspect(error, {colors: true, depth: null})));
// console.log(chalk.bgRed.white.bold("This error wasn't actually an error object - that's a bug in the transform! Here's a less precise stacktrace:"));
// console.log(chalk.red((new Error()).stack));
// }
return [
formatFailedTransformStepName(step),
`${prefix}${message}`,
error.stack
].join("\n");
}
}
// console.log(chalk.bgRed.white.bold(`A syntax error occurred in the transform '${transform.displayName}':`));
// console.log(chalk.red(` ${chalk.bold("Error:")} ${error.error}`));
// console.log(chalk.red(` ${chalk.bold("File:")} ${path.resolve(basePath, file.relativePath)} ${file.isVirtual ? chalk.dim("(virtual)") : ""}`));
// console.log(chalk.red(` ${chalk.bold("Location:")} Line ${error.line}, column ${error.column}`));
function formatFailedSinkStep(step, error) {
let prefix = formatErrorPrefix("Error");
let message = formatErrorMessage(`Failed to write file to destination`);
return [
formatFailedTransformStepName(step),
`${prefix}${message}`,
error.stack
].join("\n");
}
function formatErrorPrefix(text) {
return chalk.bgRed.white(` ${text} `);
}
function formatErrorMessage(message) {
return chalk.bgWhite.black(` ${message} `);
}
function formatCompletedItem(metadata) {
let history = traverseTransformHistory(metadata);
let sourceStep = formatSuccessfulSourceStep(history[0]);
let transformSteps = history.slice(1, -1).map((metadata) => {
return formatSuccessfulTransformStep(metadata);
});
let sinkStep = formatSuccessfulSinkStep(history[history.length - 1]);
return drawTransformTree([sourceStep].concat(transformSteps).concat([sinkStep]));
}
function formatFailedItem(pipeline, {error, step, file, duration}) {
let history = traverseTransformHistory(file.metadata);
let sourceStep = formatFailedSourceStep(history[0]);
let transformSteps, sinkStep;
if (step.__buildStep === "sink") {
transformSteps = history.slice(1, -1).map((metadata) => {
return formatSuccessfulTransformStep(metadata);
});
sinkStep = formatFailedSinkStep(history[history.length - 1], error);
} else {
let failedIndex = pipeline.steps.indexOf(step);
let successfulTransformSteps = history.slice(1, failedIndex).map((metadata) => {
return formatSuccessfulTransformStep(metadata);
});
let failedTransformStep = formatFailedTransformStep(pipeline.steps[failedIndex], file, error, pipeline, duration);
let missingTransformSteps = pipeline.steps.slice(failedIndex + 1, -1).map((step) => {
return formatMissingTransformStep(step);
});
transformSteps = successfulTransformSteps.concat([failedTransformStep]).concat(missingTransformSteps);
sinkStep = formatMissingSinkStep();
}
return drawTransformTree([sourceStep].concat(transformSteps).concat([sinkStep]));
}
module.exports = function runTask(task) {
let pipeline = task();
pipeline.on("error", (failure) => {
console.log(formatFailedItem(pipeline, failure));
if (failure.isFatal) {
console.log(chalk.bgRed.white.bold("This error is fatal, and the build process will now exit."));
/* FIXME: Do this more cleanly, with teardown and all. */
process.exit(1);
}
console.log("");
});
pipeline.on("done", ({metadata, acknowledged}) => {
console.log(formatCompletedItem(metadata));
if (!acknowledged) {
console.log(chalk.gray("(NOTE: The destination sink doesn't support acknowledgments, so the file may not actually be stored yet.)"));
}
console.log("");
});
pipeline.initialize();
};
// function reportError(transform, file, error, isFatal) {
// if (error instanceof errors.SyntaxError) {
// console.log(chalk.bgRed.white.bold(`A syntax error occurred in the transform '${transform.displayName}':`));
// console.log(chalk.red(` ${chalk.bold("Error:")} ${error.error}`));
// console.log(chalk.red(` ${chalk.bold("File:")} ${path.resolve(basePath, file.relativePath)} ${file.isVirtual ? chalk.dim("(virtual)") : ""}`));
// console.log(chalk.red(` ${chalk.bold("Location:")} Line ${error.line}, column ${error.column}`));
//
//
// } else {
// console.log(chalk.bgRed.white.bold(`An error occurred in the transform '${transform.displayName}':`));
//
//
// }
//
// }
/* FIXME: Move this stuff out, use the acknowledgment event instead for detecting which tasks have finished; also don't forget to add a workaround for sinks that cannot acknowledge */
// console.log("===============");
// console.log(metadata);
// console.log(`Wrote ${targetFilePath}`);

@ -0,0 +1,33 @@
"use strict";
const Promise = require("bluebird");
const fs = Promise.promisifyAll(require("fs"));
const mkdirpAsync = Promise.promisify(require("mkdirp"));
const path = require("path");
const createFileSink = require("./file-sink");
module.exports = function (targetPath) {
return createFileSink({
displayName: `Save to directory (${targetPath})`,
supportsAcknowledgment: true,
/* FIXME: Implement stream support */
supportsStreams: false,
sink: function ({metadata, contents}) {
return Promise.try(() => {
/* FIXME: Verify that all writes are within the targetPath */
let targetFilePath = path.join(targetPath, metadata.relativePath);
return Promise.try(() => {
return mkdirpAsync(path.dirname(targetFilePath));
}).then(() => {
return fs.writeFileAsync(targetFilePath, contents);
}).then(() => {
return {
targetPath: targetFilePath
};
});
});
}
});
};

@ -0,0 +1,51 @@
"use strict";
const Promise = require("bluebird");
const createEventEmitter = require("create-event-emitter");
const defaultValue = require("default-value");
const mergeMetadata = require("./util/merge-metadata");
/* FIXME: Associate output files with input files! */
module.exports = function createSingleFileTransform(options) {
let emitter = createEventEmitter({
__buildStep: "transform",
displayName: options.displayName,
supportsStreams: options.supportsStreams,
supportsVirtualFiles: options.supportsVirtualFiles,
supportsVirtualFilesystem: options.supportsVirtualFilesystem,
transform: function (input) {
let startTime = Date.now();
return Promise.try(() => {
return options.transform({
metadata: input.metadata,
contents: input.contents,
stream: input.stream
});
}).then(({metadata, contents, stream}) => {
emitter.emit("file", {
metadata: mergeMetadata(metadata, {
inputFileMetadata: input.metadata,
lastModified: defaultValue(metadata.lastModified, new Date()),
step: this,
stepDuration: Date.now() - startTime
}),
contents: contents,
stream: stream
});
}).catch((error) => {
emitter.emit("error", {
file: input,
step: this,
error: error,
isFatal: error.isFatal,
duration: Date.now() - startTime
});
});
}
});
return emitter;
};

@ -0,0 +1,27 @@
"use strict";
const path = require("path");
let platformDoubleSlashMatcher;
if (path.sep === "\\") {
/* Special case; even within a regex literal, a backslash still needs to be escaped itself. */
platformDoubleSlashMatcher = /\\+/g;
} else {
/* See: https://stackoverflow.com/questions/4547609/how-do-you-get-a-string-to-a-character-array-in-javascript/34717402#34717402 */
let escapedSeparator = Array.from(path.sep).map((character) => `\\${character}`).join("");
platformDoubleSlashMatcher = new RegExp(`(${escapedSeparator})+`, "g");
}
module.exports = function splitPath(targetPath, normalize = true) {
let normalized;
if (normalize) {
normalized = path.normalize(targetPath);
} else {
normalized = targetPath.replace(platformDoubleSlashMatcher, path.sep);
}
return normalized.split(path.sep);
};

@ -0,0 +1,6 @@
"use strict";
/* NOTE: This function will likely grow some more complex merging logic later on. */
module.exports = function mergeMetadata(original, newData) {
return Object.assign({}, original, newData);
};

@ -0,0 +1,9 @@
"use strict";
const path = require("path");
module.exports = function replaceExtension(originalPath, newExtension) {
let parsedPath = path.parse(originalPath);
return path.join(parsedPath.dir, `${parsedPath.name}.${newExtension}`);
};

@ -0,0 +1,13 @@
"use strict";
module.exports = function traverseTransformHistory(metadata) {
let history = [];
let current = metadata;
while (current != null) {
history.push(current);
current = current.inputFileMetadata;
}
return history.reverse();
};

@ -0,0 +1,98 @@
"use strict";
const Promise = require("bluebird");
const chokidar = require("chokidar");
const createFileSource = require("./file-source");
const fs = Promise.promisifyAll(require("fs"));
const path = require("path");
const splitPath = require("./split-path");
/* FIXME: Add deletion propagation support, based on correlating input files to output files */
module.exports = function watch(pattern, options = {}) {
let watcher;
let basePath;
if (options.basePath != null) {
/* FIXME: Verify that, if an explicit basePath is specified, it's not deeper in the path than the automatically-determined one (for any of the specified patterns); otherwise file-paths-relative-to-the-base-path no longer make any sense. */
basePath = options.basePath;
} else {
if (typeof pattern === "string") {
let pathSegments = splitPath(pattern);
let firstWildcardSegment = pathSegments.findIndex((segment) => {
return segment.includes("*");
});
if (firstWildcardSegment === -1) {
/* Assume an exact file path, and treat its folder as the base folder */
basePath = path.dirname(pattern);
} else {
/* Assume a path containing a wildcard, and treat the last non-wildcard segment as the base folder */
basePath = pathSegments.slice(0, firstWildcardSegment).join(path.sep);
}
} else {
throw new Error("When specifying multiple patterns to watch, you must explicitly specify a basePath in the options");
}
}
return createFileSource({
displayName: `Watch for changes (${pattern})`,
supportsTeardown: true,
basePath: basePath,
initialize: ({options, resolvePath, pushFile, reportError}) => {
function push(filePath, stats) {
return Promise.try(() => {
/* FIXME: Define more sensible resolvePath semantics */
let resolvedPath = resolvePath(filePath);
let metadata = {
isVirtual: false,
path: resolvedPath,
lastModified: stats.mtime
};
/* FIXME: File object format validation */
if (options.supportsStreams) {
pushFile({
metadata: {
isStream: true,
... metadata
},
stream: fs.createReadStream(resolvedPath)
});
} else {
return Promise.try(() => {
return fs.readFileAsync(resolvedPath);
}).then((contents) => {
pushFile({
metadata: {
isStream: false,
... metadata
},
contents: contents
});
});
}
}).catch((err) => {
reportError(err, false);
});
}
watcher = chokidar.watch(pattern, {alwaysStat: true})
.on("add", (path, stats) => {
push(path, stats);
})
.on("change", (path, stats) => {
push(path, stats);
})
.on("error", (err) => {
/* FIXME: Determine whether the error is fatal */
reportError(err, false);
});
},
teardown: function () {
watcher.close();
}
});
};

@ -0,0 +1,19 @@
"use strict";
const core = require("./core");
const scssTransform = require("./transforms/scss");
const dummyTransform = require("./transforms/dummy");
const runner = require("./core/runner");
let scssTask = core.defineTask(() => {
return core.pipeline([
core.watch("src/client/scss/**/*.scss"),
dummyTransform(),
scssTransform(),
core.saveToDirectory("public/css")
]);
});
// scssTask();
runner(scssTask);

@ -0,0 +1,79 @@
# TYPES
- event daemon(?) for eg. nodemon
- task
- pipeline
- step (source, transform, sink)
- file
- failure
## Task
Represents a task/job.
Contains:
- Pipeline or event daemon
## Pipeline
Represents a sequence of operations, from source to sink.
Contains:
- Array of steps
## Step
Can be either source, transform, or sink -- a transform conceptually counts as both source and sink, sort of.
Contains:
- __buildStep (type: source, transform, sink)
- displayName
- Source only:
- supportsTeardown
- basePath
- initialize (logic)
- teardown (logic)
- Transform only:
- supportsStreams
- supportsVirtualFiles
- supportsVirtualFilesystem
- transform (logic)
- Sink only:
- supportsStreams
- supportsAcknowledgment
- sink (logic)
## File
A file, real or virtual; equates to a successful event on a source/transform.
Contains:
- Metadata
- Contents or Stream
## Metadata
All the data that describes the file; nests to refer to parent input file's metadata.
Contains:
- inputFileMetadata (only when originating from transform or sink)
- step: The step that this file originates from
- stepDuration: How long the originating step took
- sourcedDate (only when originating from source)
- lastModified (optional)
- targetPath (save-to-directory sink only?)
- relativePath
- basePath
## Failure
Signifies that something went wrong.
Contains:
- error: Error object/data
- step: Step that it originated from
- file: Input file (only for sinks/transforms)
- isFatal
- duration (only for transforms)

@ -0,0 +1,18 @@
"use strict";
const core = require("../core");
module.exports = function () {
return core.createSingleFileTransform({
displayName: "Dummy transform",
// stream: where 'contents' is a stream
supportsStreams: true,
// virtual files: files with a contents buffer/stream but without a path
supportsVirtualFiles: true,
// virtual filesystem: not-on-disk, eg. for includes of transformed files
supportsVirtualFilesystem: true,
transform: (file) => {
return file;
}
});
};

@ -0,0 +1,111 @@
"use strict";
const Promise = require("bluebird");
const path = require("path");
const core = require("../core");
const nodeSass = Promise.promisifyAll(require("node-sass"));
const copyProps = require("copy-props");
const replaceExtension = require("../core/util/replace-extension");
const createSyntaxError = require("../core/errors/syntax-error");
let optionsToCopy = [
"functions",
"indentedSyntax",
"indentType",
"indentWidth",
"linefeed",
"omitSourceMapUrl", // FIXME: outFile stuff
"outputStyle",
"precision",
"sourceComments",
"sourceMap", // FIXME: outFile stuff
"sourceMapContents",
"sourceMapEmbed",
"sourceMapRoot"
];
module.exports = function (options = {}) {
return core.createSingleFileTransform({
displayName: "SCSS (node-sass)",
// stream: where 'contents' is a stream
supportsStreams: false,
// virtual files: files with a contents buffer/stream but without a path
supportsVirtualFiles: true,
// virtual filesystem: not-on-disk, eg. for includes of transformed files
supportsVirtualFilesystem: (options.virtualFilesystemSupport === true),
transform: ({metadata, contents}) => {
let includePaths;
if (!metadata.isVirtual) {
includePaths = [path.dirname(metadata.path)];
} else {
includePaths = [];
}
if (options.includePaths != null) {
includePaths = includePaths.concat(options.includePaths);
}
let importer;
if (options.virtualFilesystemSupport === true && metadata.virtualFilesystem != null) {
importer = function include(target, previousPath, done) {
if (/^[a-z]+:/i.test(target)) {
/* Probably a URL; don't handle this. */
return null;
} else {
return Promise.try(() => {
return metadata.virtualFilesystem.readFile(target);
}).then((contents) => {
done(contents.toString());
}).catch((err) => {
done(err);
});
}
};
}
return Promise.try(() => {
let stringContents = contents.toString();
if (stringContents.length > 0) {
let renderOptions = {
data: stringContents,
includePaths: includePaths,
importer: importer,
};
copyProps(options, renderOptions, optionsToCopy);
return nodeSass.renderAsync(renderOptions);
} else {
return {css: contents};
}
}).then((result) => {
return {
metadata: {
/* FIXME: Implement a check that throws errors when a transform returns isVirtual:false for a non-existent path on a non-virtual FS */
isVirtual: true,
/* FIXME: Verify that every transform returns a relativePath? */
relativePath: replaceExtension(metadata.relativePath, "css")
},
contents: result.css
};
}).catch({status: 1}, (err) => {
/* Syntax error */
throw createSyntaxError({
error: err.message,
line: err.line,
column: err.column
});
}).catch({status: 3}, (err) => {
// no input provided
throw new Error(err.message);
}).catch((err) => {
// console.log(require("util").inspect(err, {colors: true, depth: null}));
throw err;
}); /* FIXME: Error mapping! */
}
});
};

@ -1,102 +0,0 @@
var gulp = require('gulp');
var path = require('path');
var rename = require('gulp-rename');
var livereload = require('gulp-livereload');
var nodemon = require("gulp-nodemon");
var net = require("net");
var webpack = require("webpack-stream");
var webpackLib = require("webpack")
var presetES2015 = require("@joepie91/gulp-preset-es2015");4
var nodemonRestarting = false;
function tryReload() {
if (nodemonRestarting === false) {
livereload.changed.apply(null, arguments);
}
}
/* The following resolves JacksonGariety/gulp-nodemon#33 */
process.once("SIGINT", function() {
process.exit(0);
});
gulp.task("babel", function() {
return gulp.src("./src/**/*.js")
.pipe(presetES2015({
basePath: __dirname
}))
.pipe(gulp.dest("./lib"));
})
gulp.task('webpack', function(){
return gulp.src("./src/frontend/index.js")
.pipe(webpack({
watch: true,
module: {
preLoaders: [{
test: /\.tag$/,
loader: "riotjs-loader",
exclude: /node_modules/,
query: {
type: "babel",
template: "jade"
}
}],
loaders: [
{ test: /\.js$/, loader: "babel-loader" },
{ test: /\.json$/, loader: "json-loader" },
//{ loader: "logging-loader" }
]
},
plugins: [ new webpackLib.ProvidePlugin({riot: "riot"}) ],
resolveLoader: { root: path.join(__dirname, "node_modules") },
resolve: {
extensions: [
"",
".tag",
".web.js", ".js",
".web.json", ".json"
]
},
debug: false
}))
.pipe(rename("bundle.js"))
.pipe(gulp.dest("./public/js"));
});
function checkServerUp(){
setTimeout(function(){
var sock = new net.Socket();
sock.setTimeout(50);
sock.on("connect", function(){
nodemonRestarting = false;
console.log("Triggering page reload...");
tryReload("*");
sock.destroy();
})
.on("timeout", checkServerUp)
.on("error", checkServerUp)
.connect(3000);
}, 70);
}
gulp.task("nodemon", ["babel"], function() {
nodemon({
script: "./app.js",
delay: 500,
ignore: ["public"]
}).on("restart", function() {
nodemonRestarting = true;
}).on("start", checkServerUp);
})
gulp.task('watch', ["nodemon"], function () {
livereload.listen();
gulp.watch(['./**/*.css', 'views/**/*.jade', '!views/client/**/*.jade', 'package.json', "./public/js/**/*.js"]).on('change', tryReload);
gulp.watch(['public/views/**/*.html', 'public/elements/**/*']).on('change', function() { tryReload("*"); }); // We need to explicitly reload everything here; Polymer doesn't do partial reloading
});
gulp.task("default", ["watch", "webpack"]);

@ -1,9 +0,0 @@
'use strict';
require("../window");
require("../../views/sample-view");
require("../../views/node/create");
require("./component");

@ -1,12 +0,0 @@
'use strict';
var $ = require("jquery");
var riot = require("riot");
require("debug").enable("*");
require("../components/app");
$(function () {
riot.mount("*");
});

@ -1,8 +0,0 @@
'use strict';
module.exports = {
change: function change(data) {
Object.assign(this, data);
this.update();
}
};

@ -1,68 +0,0 @@
'use strict';
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var riotQuery = require("riot-query");
var arrayDiff = require("arraydiff");
module.exports = {
init: function init() {
var _this = this;
var knownTags = [];
var listeners = {};
function triggerEvent(action, query, item) {
if (listeners[query] != null && listeners[query][action] != null) {
listeners[query][action].forEach(function (handler) {
handler(item);
});
}
}
this.on("updated", function () {
Object.keys(listeners).forEach(function (query) {
var currentTags = riotQuery(_this, query);
var diff = arrayDiff(knownTags, currentTags);
diff.forEach(function (item) {
if (item.type === "remove") {
for (var i = item.index; i < item.index + item.howMany; i++) {
triggerEvent("remove", query, knownTags[i]);
}
} else if (item.type === "move") {
for (var _i = item.index; _i < item.from + item.howMany; _i++) {
triggerEvent("move", query, knownTags[_i]);
}
} else if (item.type === "insert") {
item.values.forEach(function (value) {
triggerEvent("create", query, value);
});
}
});
knownTags = currentTags;
});
});
this.onChild = function (eventName, handler) {
var _eventName$split = eventName.split(":", 2);
var _eventName$split2 = _slicedToArray(_eventName$split, 2);
var action = _eventName$split2[0];
var query = _eventName$split2[1];
if (listeners[query] == null) {
listeners[query] = {};
}
if (listeners[query][action] == null) {
listeners[query][action] = [];
}
listeners[query][action].push(handler);
};
}
};

@ -1,153 +0,0 @@
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
var Promise = require("bluebird");
var pathToRegexp = require("path-to-regexp");
var url = require("url");
var xtend = require("xtend");
var defaultValue = require("default-value");
module.exports = function () {
var routes = [];
function addRoute(method, path, handler) {
// Mutable arguments? WTF.
var keys = [];
var regex = pathToRegexp(path, keys);
routes.push({ method: method, path: path, regex: regex, keys: keys, handler: handler });
}
function getRoute(method, path) {
var matches = void 0;
var matchingRoute = routes.find(function (route) {
return route.method === method && (matches = route.regex.exec(path));
});
if (matchingRoute == null) {
throw new Error("No matching routes found");
} else {
var _ret = function () {
var params = {};
matchingRoute.keys.forEach(function (key, i) {
params[key] = matches[i + 1];
});
return {
v: {
handler: matchingRoute.handler,
params: params
}
};
}();
if ((typeof _ret === "undefined" ? "undefined" : _typeof(_ret)) === "object") return _ret.v;
}
}
function handle(method, uri, data) {
return Promise.try(function () {
var _url$parse = url.parse(uri, true);
var path = _url$parse.path;
var query = _url$parse.query;
var route = getRoute(method, path);
var tasks = [];
var req = {
path: path,
query: query,
body: data,
params: route.params,
pass: function pass() {
// FIXME: window.fetch passthrough
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
},
passRender: function passRender(viewName) {
var _this = this;
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
return Promise.try(function () {
return _this.pass(options);
}).then(function (response) {
var locals = defaultValue(options.locals, {});
var combinedLocals = xtend(locals, response.body);
res.render(viewName, combinedLocals, options);
});
}
};
var res = {
render: function render(viewName) {
var locals = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var options = arguments.length <= 2 || arguments[2] === undefined ? {} : arguments[2];
tasks.push({
type: "render",
viewName: viewName, locals: locals, options: options
});
},
open: function open(path) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
tasks.push({
type: "open",
path: path, options: options
});
},
close: function close() {
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
tasks.push({
type: "close",
options: options
});
},
notify: function notify(message) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
tasks.push({
type: "notify",
message: message, options: options
});
},
error: function error(_error) {
var context = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
tasks.push({
type: "error",
error: _error, context: context
});
}
};
return Promise.try(function () {
return route.handler(req, res);
}).then(function (result) {
return {
result: result,
actions: tasks
};
});
});
}
var api = {
get: addRoute.bind(api, "get"),
post: addRoute.bind(api, "post"),
put: addRoute.bind(api, "put"),
delete: addRoute.bind(api, "delete"),
head: addRoute.bind(api, "head"),
patch: addRoute.bind(api, "patch"),
addRoute: addRoute,
handle: handle
};
return api;
};

@ -1,43 +0,0 @@
'use strict';
var debounce = require("debounce");
var defaultValue = require("default-value");
var selectable = require("./selectable");
module.exports = function ($) {
selectable($);
$.fn.draggable = function () {
var _this = this;
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var debounceInterval = defaultValue(options.debounce, 10);
this.on("mousedown", function (event) {
var startX = event.pageX;
var startY = event.pageY;
var moveHandler = debounce(function (event) {
_this.trigger("draggable:move", [{
offsetX: event.pageX - startX,
offsetY: event.pageY - startY
}]);
}, debounceInterval);
$(document).on("mousemove", moveHandler);
$(document).one("mouseup", function (event) {
$(document).off("mousemove", moveHandler);
$(document).enableSelection();
_this.trigger("draggable:end");
});
$(document).disableSelection();
_this.trigger("draggable:start");
});
};
};

@ -1,11 +0,0 @@
'use strict';
module.exports = function ($) {
$.fn.disableSelection = function () {
return this.attr("unselectable", "on").css("user-select", "none").on("selectstart", false);
};
$.fn.enableSelection = function () {
return this.removeAttr("unselectable").css("user-select", "auto").off("selectstart", false);
};
};

@ -1,140 +0,0 @@
'use strict';
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var elementSize = require("element-size");
var sortDockable = require("./util/sort-dockables");
function px(pixels) {
return pixels + "px";
}
module.exports = {
init: function init() {
var _this = this;
console.log("foo", this);
var dockableAPI = {
isActive: function isActive() {
return _this.opts.dockableContainer != null;
},
stack: [],
fillElement: null,
recalculateLayout: function recalculateLayout() {
dockableAPI.stack.forEach(function (item, i) {
item.stackIndex = i;
});
var orderedStack = dockableAPI.stack.slice().sort(sortDockable);
var reservedLeft = 0;
var reservedRight = 0;
var reservedTop = 0;
var reservedBottom = 0;
orderedStack.forEach(function (item) {
var element = item.tag.root;
/* We set the positioning to absolute *before* attempting
* to obtain the element size - this way, we can be sure
* that the element won't try to stretch to its container.
* Instead, it'll be auto-sized, which is exactly what we
* want. */
element.style.position = "absolute";
var _elementSize = elementSize(element);
var _elementSize2 = _slicedToArray(_elementSize, 2);
var width = _elementSize2[0];
var height = _elementSize2[1];
if (item.heightHint != null) {
height = item.heightHint;
}
if (item.widthHint != null) {
width = item.widthHint;
}
// FIXME: Should the following really be errors?
if ((item.side === "left" || item.side === "right") && width === 0) {
throw new Error("Cannot horizontally dock an element without a width; you may need to specify a dock-width");
} else if ((item.side === "top" || item.side === "bottom") && height === 0) {
throw new Error("Cannot vertically dock an element without a height; you may need to specify a dock-height");
}
if (item.side === "left") {
Object.assign(element.style, {
left: px(reservedLeft),
top: px(reservedTop),
bottom: px(reservedBottom)
});
delete element.style.right;
reservedLeft += width;
} else if (item.side === "right") {
Object.assign(element.style, {
right: px(reservedRight),
top: px(reservedTop),
bottom: px(reservedBottom)
});
delete element.style.left;
reservedRight += width;
} else if (item.side === "top") {
Object.assign(element.style, {
left: px(reservedLeft),
right: px(reservedRight),
top: px(reservedTop)
});
delete element.style.bottom;
reservedTop += height;
} else if (item.side === "bottom") {
Object.assign(element.style, {
left: px(reservedLeft),
right: px(reservedRight),
bottom: px(reservedBottom)
});
delete element.style.top;
reservedBottom += height;
}
console.log("reserved", reservedLeft, reservedRight, reservedTop, reservedBottom);
});
var item = dockableAPI.fillElement;
if (item != null) {
var element = item.root;
Object.assign(element.style, {
position: "absolute",
left: px(reservedLeft),
right: px(reservedRight),
top: px(reservedTop),
bottom: px(reservedBottom)
});
}
console.log("ordered stack", orderedStack);
}
};
this._uikitDockableContainer = dockableAPI;
this.on("mount", function () {
console.log("dockable-container mounted");
if (dockableAPI.isActive()) {
dockableAPI.recalculateLayout();
}
});
}
};

@ -1,52 +0,0 @@
'use strict';
var inArray = require("in-array");
function findContainer(tag) {
var lastTag = tag;
var dockableContainer = void 0;
while (dockableContainer == null && lastTag != null) {
var candidate = lastTag.parent;
if (candidate != null && candidate._uikitDockableContainer != null && candidate._uikitDockableContainer.isActive()) {
dockableContainer = candidate;
} else {
lastTag = candidate;
}
}
if (dockableContainer != null) {
return dockableContainer;
} else {
// FIXME: Better error reporting?
throw new Error("Could not find a dockable container for tag");
}
}
module.exports = {
init: function init() {
if (this.opts.dock != null) {
var dockableContainer = findContainer(this);
var containerData = dockableContainer._uikitDockableContainer;
if (inArray(["bottom", "top", "left", "right"], this.opts.dock)) {
containerData.stack.push({
tag: this,
side: this.opts.dock,
order: this.opts.dockOrder,
widthHint: this.opts.dockWidth,
heightHint: this.opts.dockHeight
});
} else if (this.opts.dock === "fill") {
if (containerData.fillElement != null) {
throw new Error("There can be only one tag with a `dock` setting of `fill` within a dockable container");
} else {
containerData.fillElement = this;
}
} else {
throw new Error("Invalid `dock` property; must be one of bottom, top, left, right, fill");
}
}
}
};

@ -1,36 +0,0 @@
'use strict';
module.exports = function (a, b) {
var aOrder = void 0,
bOrder = void 0;
if (a.order != null) {
aOrder = parseInt(a.order);
}
if (b.order != null) {
bOrder = parseInt(b.order);
}
if (aOrder != null && bOrder == null) {
return -1;
} else if (aOrder == null && bOrder != null) {
return 1;
} else if (aOrder != null && bOrder != null) {
if (aOrder < bOrder) {
return -1;
} else if (aOrder > bOrder) {
return 1;
} else {
return 0;
}
} else {
if (a.stackIndex < b.stackIndex) {
return -1;
} else if (a.stackIndex > b.stackIndex) {
return 1;
} else {
return 0;
}
}
};

@ -1,21 +0,0 @@
"use strict";
var router = require("express-promise-router")();
router.get("/", function (req, res) {
res.render("layout");
});
router.get("/node/:uuid", function (req, res) {
res.json({ uuid: req.params.uuid });
});
router.get("/test1", function (req, res) {
res.send("test ONE <a href='/test2'>go to 2 instead</a> <a href='/test2' target='_blank'>or in a new window</a>");
});
router.get("/test2", function (req, res) {
res.send("test TWO <a href='/test1'>go to 1 instead</a> <a href='/test1' target='_blank'>or in a new window</a>");
});
module.exports = router;

@ -1,14 +0,0 @@
'use strict';
require("../../../components/uikit/frame");
require("../../../components/uikit/toolbar");
require("../../../components/uikit/button");
require("../../../components/uikit/form-section");
require("../../../components/uikit/input-spawner");
require("../../../components/uikit/input");
require("../../../components/uikit/textarea");
require("../../../components/uikit/autocomplete");
require("../../../components/window-meta");
require("./component");

@ -0,0 +1,7 @@
#notification_area
.notification-popup
.notification-header
.notification-contents
.error-popup
.notification-header
.notification-contents

@ -0,0 +1,32 @@
'use strict';
// NOTE: NO LONGER USED
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackHotMiddleware = require("webpack-hot-middleware");
const webpack = require("webpack")(require("./webpack.config.js"));
let app = express();
app.use(webpackDevMiddleware(webpack, {
publicPath: "/"
}));
app.use(webpackHotMiddleware(webpack));
app.set("view engine", "pug");
app.set("views", path.join(__dirname, "views"));
app.use(express.static(path.join(__dirname, "public")));
app.use(bodyParser.urlencoded({
extended: true
}));
app.use("/", require("./routes/index"));
app.listen(3000);

@ -1,33 +1,30 @@
app
window(each="{windows}", router="{router}", window-title="{title}", width=640, height=480, x="{x}", y="{y}", z=0, resizable="true", url="{url}")
window(each="{windowItem in windows}", router="{parent.router}", window-title="{windowItem.title}", width=640, height=480, x="{windowItem.x}", y="{windowItem.y}", z=0, resizable="true", url="{windowItem.url}")
notification-box
style(type="scss").
notification-box {
position: absolute;
right: 0px;
bottom: 32px;
z-index: 2147483640;
}
script.
const stateRouter = require("../../frontend/riot-state-router");
this.mixin(require("../../frontend/riot-on-child"));
let createRouter = require("../../lib/frontend/router");
this.mixin(require("../../lib/riot/mixins/on-child"));
this.mixin(require("riot-query").mixin);
this.windows = [
{ title: "test one", x: 10, y: 10, url: "/" },
{ title: "test one", x: 10, y: 10, url: "/?target=Foo" },
{ title: "test two", x: 200, y: 200, url: "/" },
{ title: "", x: 390, y: 390, url: "/nodes/create" }
]
let currentZIndex = 0;
let router = stateRouter();
router.get("/", (req, res) => {
res.render("sample", {
text: "Hello World!"
});
});
router.get("/nodes/create", (req, res) => {
res.render("node-create");
});
this.router = router;
this.router = createRouter();
this.onChild("create:window", (window) => {
window.on("focused", () => {
@ -47,4 +44,4 @@ app
});
window.focus();
});
});

@ -0,0 +1,10 @@
'use strict';
require("../window");
require("../notification-box");
require("../../views/riot/sample-view");
require("../../views/riot/node/create");
require("./component");

@ -0,0 +1,47 @@
notification-box
notification(each="{notification in notifications}", notification="{notification}")
script.
const defaultValue = require("default-value");
Object.assign(this, {
notifications: [{
title: "Foo bar!",
//message: "Bar bar baz foo. Bar.",
type: "info",
icon: "info-circle"
}, {
title: "Whoopteedoo",
//message: "Boop boop woop whoop toop boop!",
type: "error",
icon: "exclamation-circle"
}],
add: function addNotification(title, options = {}) {
let notificationObject = {
title: title,
message: options.message,
type: defaultValue(options.type, "info"),
icon: options.icon,
options: options
};
this.notifications.push(notificationObject);
if (options.timeout != null) {
setTimeout(() => {
this.remove(notificationObject);
}, options.timeout);
}
this.update();
},
remove: function removeNotification(notificationObject) {
let notificationIndex = this.notifications.indexOf(notificationObject);
// TODO: Fade/collapse animation?
if (notificationIndex !== -1) {
this.notifications.splice(notificationIndex, 1);
this.update();
}
}
})

@ -0,0 +1,5 @@
'use strict';
require("../notification");
require("./component");

@ -0,0 +1,88 @@
notification(class="type-{opts.notification.type}")
.contents
.header
button.close: i.fa.fa-times
i.icon.fa(class="fa-{opts.notification.icon}")
| { opts.notification.title }
.message(if="{opts.notification.message != null}") { opts.notification.message }
style(type="scss").
:scope {
display: block;
text-align: right;
.close {
display: none; // FIXME
float: right;
background: none;
border: 0;
padding: 1px 3px;
font-size: 12px;
color: rgb(190, 190, 190);
border: 1px solid rgb(190, 190, 190);
border-radius: 4px;
&:hover {
color: white;
border-color: white;
}
}
.contents {
text-align: left;
display: inline-block;
border-radius: 6px;
margin-right: 19px;
margin-top: 10px;
padding: 9px 14px;
color: white;
font-size: 15px;
filter: alpha(opacity=85);
opacity: 0.85;
width: auto;
background-color: black;
.header {
margin-right: 6px;
font-weight: bold;
.icon {
font-size: 19px;
margin-right: 9px !important;
}
}
.message {
margin-top: 8px;
}
}
&.type-error {
.contents {
background-color: #371B1B;
.header .icon {
color: #FFD2D2;
}
}
}
&.type-info {
.contents {
background-color: #2D2D2D;
.header .icon {
color: #CBCAFF;
}
}
}
}
/* From old code, but purpose unclear:
.notification-popup ul, .error-popup ul
{
margin: 4px 0px;
padding-left: 48px;
}
*/

@ -0,0 +1,3 @@
'use strict';
require("./component");

@ -1,11 +1,11 @@
uikit-frame
.frame-wrapper
!= "<yield/>"
script.
this.mixin(require("../../../riot/mixins/dockable-container"));
this.mixin(require("../../../riot/mixins/dockable"));
this.mixin(require("../../../lib/riot/mixins/dockable-container"));
this.mixin(require("../../../lib/riot/mixins/dockable"));
style(type="scss").
uikit-frame {
position: absolute;
@ -13,4 +13,4 @@ uikit-frame
right: 0px;
top: 0px;
bottom: 0px;
}
}

@ -2,7 +2,9 @@ uikit-input
label {opts.label}
input(type="{opts.type}", name="{opts.name}")
style.
style(scoped, type="scss").
label {
margin-right: 8px;
}
script.

@ -0,0 +1,8 @@
uikit-toolbar
div
!= "<yield/>"
style.
script.
this.mixin(require("../../../lib/riot/mixins/dockable"));

@ -20,7 +20,7 @@ view-manager
this.trigger("switched");
},
navigate: function(method, uri, data = {}) {
navigate: function(method, uri, data = {}, options = {}) {
return Promise.try(() => {
this.trigger("navigating");
return opts.router.handle(method, uri, data);
@ -29,6 +29,8 @@ view-manager
if (action.type === "render") {
this.switchView(action.viewName, action.locals);
}
// MARKER: Emit notifications!
});
this.trigger("navigated");
@ -38,4 +40,17 @@ view-manager
["get", "post", "put", "delete", "patch", "head"].forEach((method) => {
this[method] = this.navigate.bind(this, method);
});
});
// FIXME: Get rid of jQuery here?
$(this.root).on("submit", "form:not(.no-intercept)", (event) => {
Promise.try(() => {
let form = event.target;
event.preventDefault();
event.stopPropagation();
return this.navigate(form.method, form.action, new FormData(form), {
multipart: (form.enctype === "multipart/form-data")
});
});
});

@ -7,18 +7,18 @@ window
.outer
.inner-wrapper
.inner
view-manager(router="{router}", view-prefix="openng-view")
view-manager(router="{opts.router}", view-prefix="openng-view")
.resizer(show="{opts.resizable}")
script.
const $ = require("jquery");
const debounce = require("debounce");
const query = require("riot-query");
require("../../jquery/draggable")($);
require("../../lib/jquery/draggable")($);
this.mixin(query.mixin);
this.mixin(require("../../frontend/riot-change"));
this.mixin(require("../../lib/riot/mixins/change"));
Object.assign(this, {
dragged: false,
@ -27,10 +27,10 @@ window
width: parseInt(opts.width),
height: parseInt(opts.height),
windowTitle: opts.windowTitle,
//x: parseInt(opts.x),
//y: parseInt(opts.y),
x: parseInt(opts.x),
y: parseInt(opts.y),
z: parseInt(opts.z),
focus: () => {
this.trigger("focused");
this.change({
@ -72,14 +72,13 @@ window
reportClose: () => {
this.trigger("closing");
},
_handleMouseDown: (event) => {
this.focus();
}
});
this.on("updated", () => {
//console.log("UPDATED", this, opts);
let updatePosition = () => {
$(this.queryOne("//.wrapper")).css({
left: `${this.x}px`,
top: `${this.y}px`,
@ -87,99 +86,105 @@ window
height: `${this.height}px`,
zIndex: this.z
});
}
this.on("updated", () => {
updatePosition();
});
this.on("mount", () => {
let viewManager = this.queryOne("view-manager");
let resizeHandle = $(this.queryOne("//.resizer"));
let titleBar = $(this.queryOne("//.title"));
let closeButton = $(this.queryOne("//.close"));
if (opts.url != null) {
viewManager.get(opts.url);
}
viewManager.on("switched", () => {
query(viewManager.currentView, "window-meta").forEach((windowMeta) => {
if (windowMeta.windowTitle != null) {
this.setTitle(windowMeta.windowTitle);
}
if (windowMeta.requestedWidth != null) {
this.setWidth(windowMeta.requestedWidth);
}
if (windowMeta.requestedHeight != null) {
this.setHeight(windowMeta.requestedHeight);
}
})
});
closeButton.on("click", (event) => {
event.stopPropagation();
event.preventDefault;
this.trigger("requestClose");
})
let startWidth, startHeight;
resizeHandle.draggable();
resizeHandle.on("draggable:start", (event) => {
startWidth = this.width;
startHeight = this.height;
this.change({
resized: true
});
});
resizeHandle.on("draggable:end", (event) => {
this.change({
resized: false
});
});
resizeHandle.on("draggable:move", (event, data) => {
this.change({
width: startWidth + data.offsetX,
height: startHeight + data.offsetY
});
});
let startX, startY;
titleBar.draggable();
titleBar.on("draggable:start", (event) => {
startX = this.x;
startY = this.y;
this.change({
dragged: true
});
});
titleBar.on("draggable:end", (event) => {
this.change({
dragged: false
});
});
titleBar.on("draggable:move", (event, data) => {
this.change({
x: startX + data.offsetX,
y: startY + data.offsetY
});
});
$(this.queryOne("//.wrapper")).on("mousedown", () => {
this._handleMouseDown();
});
updatePosition();
});
style(scoped, type="scss").
.noscroll
{
@ -203,70 +208,70 @@ window
.title
{
@include shadow;
position: absolute;
z-index: 2;
left: 0px;
right: 0px;
top: 0px;
cursor: default;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
height: 16px;
color: white;
font-size: 14px;
font-weight: bold;
padding: 4px;
padding-left: 7px;
border-top: 1px solid #959595;
border-right: 1px solid #959595;
border-left: 1px solid #959595;
background-image: -webkit-gradient(
linear,
left bottom,
left top,
color-stop(0, rgb(82,82,82)),
color-stop(1, rgb(145,172,190))
);
background-image: -moz-linear-gradient(
center bottom,
rgb(82,82,82) 0%,
rgb(145,172,190) 100%
);
filter:alpha(opacity=95);
opacity:0.95;
// @include shadow;
// position: absolute;
// z-index: 2;
// left: 0px;
// right: 0px;
// top: 0px;
// cursor: default;
// text-overflow: ellipsis;
// overflow: hidden;
// white-space: nowrap;
// border-top-left-radius: 10px;
// border-top-right-radius: 10px;
// height: 16px;
// color: white;
// font-size: 14px;
// font-weight: bold;
// padding: 4px;
// padding-left: 7px;
// border-top: 1px solid #959595;
// border-right: 1px solid #959595;
// border-left: 1px solid #959595;
// background-image: -webkit-gradient(
// linear,
// left bottom,
// left top,
// color-stop(0, rgb(82,82,82)),
// color-stop(1, rgb(145,172,190))
// );
// background-image: -moz-linear-gradient(
// center bottom,
// rgb(82,82,82) 0%,
// rgb(145,172,190) 100%
// );
// filter:alpha(opacity=95);
// opacity:0.95;
}
.outer
{
@include shadow;
position: absolute;
z-index: 3;
left: 0px;
right: 0px;
bottom: 0px;
font-size: 13px;
top: 25px;
border-bottom: 1px solid gray;
border-right: 1px solid gray;
border-left: 1px solid gray;
background-color: #F7F7F0;
filter:alpha(opacity=95);
opacity:0.95;
// @include shadow;
// position: absolute;
// z-index: 3;
// left: 0px;
// right: 0px;
// bottom: 0px;
// font-size: 13px;
// top: 25px;
// border-bottom: 1px solid gray;
// border-right: 1px solid gray;
// border-left: 1px solid gray;
// background-color: #F7F7F0;
// filter:alpha(opacity=95);
// opacity:0.95;
}
.inner-wrapper
{
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow-y: auto;
overflow-x: auto;
// position: absolute;
// top: 0px;
// bottom: 0px;
// left: 0px;
// right: 0px;
// overflow-y: auto;
// overflow-x: auto;
}
.inner
@ -341,10 +346,10 @@ window
-moz-box-shadow: none;
box-shadow: none;
}
@mixin unselectable {
cursor: default;
user-select: none;
user-select: none;
-webkit-user-select: none; /* Chrome/Safari/Opera */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
@ -371,7 +376,7 @@ window
visibility: hidden;
}
}
.resized {
@include unselectable;
}

@ -0,0 +1,67 @@
'use strict';
const Promise = require("bluebird");
const gulp = require("gulp");
const gulpNamedLog = require("gulp-named-log");
const gulpNodemon = require("gulp-nodemon");
const presetSCSS = require("@joepie91/gulp-preset-scss");
const awaitServer = require("await-server");
const gulpLivereload = require("gulp-livereload");
const patchLivereloadLogger = require("@joepie91/gulp-partial-patch-livereload-logger");
patchLivereloadLogger(gulpLivereload);
let config = {
scss: {
source: "./scss/**/*.scss",
destination: "./public/"
}
};
let serverLogger = gulpNamedLog("server");
gulp.task("nodemon", ["scss", "livereload"], () => {
gulpNodemon({
script: "app.js",
ignore: [
"gulpfile.js",
"node_modules",
"public",
"src/frontend"
],
ext: "js pug"
}).on("start", () => {
Promise.try(() => {
serverLogger.info("Starting...");
return awaitServer(3000);
}).then(() => {
serverLogger.info("Started!");
gulpLivereload.changed("*");
});
});
});
gulp.task("scss", () => {
return gulp.src(["./scss/style.scss", "./scss/components.scss"])
.pipe(presetSCSS({
livereload: gulpLivereload,
cacheKey: false
}))
.pipe(gulp.dest(config.scss.destination));
});
gulp.task("livereload", () => {
gulpLivereload.listen({
quiet: true
});
});
gulp.task("watch-css", () => {
gulp.watch(config.scss.source, ["scss"]);
});
gulp.task("watch", ["nodemon", "watch-css"]);
gulp.task("default", ["watch"]);

@ -0,0 +1,55 @@
'use strict';
function isObject(value) {
return (value === Object(value));
}
function makeArrayKey(key) {
if (key.length > 2 && key.lastIndexOf('[]') === key.length - 2) {
return key
} else {
return key + '[]'
}
}
function generateKey(prefix, key) {
if (prefix == null || prefix.length === 0) {
return key;
} else {
return `${prefix}[${key}]`
}
}
module.exports = function deconstructFormObject(object, prefix) {
return Object.keys(object).reduce((items, key) => {
let value = object[key];
let newItems = [];
function transformValue(value, isInArray) {
let valueKey;
if (isInArray) {
valueKey = makeArrayKey(generateKey(prefix, key));
} else {
valueKey = generateKey(prefix, key);
}
if (Array.isArray(value)) {
value.forEach((arrayItem) => {
transformValue(arrayItem, true);
});
} else if (isObject(value)) {
newItems = newItems.concat(deconstructFormObject(value, valueKey));
} else {
newItems.push({
name: valueKey,
value: value
});
}
}
transformValue(value);
return items.concat(newItems);
}, []);
};

@ -0,0 +1,32 @@
'use strict';
const qs = require("qs");
// FIXME: Can this also take a URLSearchParams?
module.exports = function formDataToObject(formData) {
let items = Array.from(formData.entries()).reduce((items, [key, value]) => {
if (key.match(/\[\]$/)) {
/* Array item */
if (items[key] == null) {
items[key] = [];
}
items[key].push(value);
} else {
/* Single item */
items[key] = value;
}
return items;
}, {});
console.log("ITEMS", qs.stringify(items));
/* This is a bit of a hack... we have an array of values that may or may not
involve array and object specifications, and the `items` object is an
object that contains a flat set of them as keys and values. By passing it
into `stringify` and `parse` consecutively, we get back a properly nested
version of it. */
return qs.parse(qs.stringify(items));
};

@ -5,8 +5,8 @@ const riot = require("riot");
require("debug").enable("*");
require("../components/app");
require("../../components/app");
$(() => {
riot.mount("*");
});
});

@ -0,0 +1,36 @@
'use strict';
const Promise = require("bluebird");
const stateRouter = require("../riot/state-router");
module.exports = function createRouter() {
let router = stateRouter();
router.get("/", (req, res) => {
let text;
if (req.query.target != null) {
text = req.query.target;
} else {
text = "World";
}
res.render("sample", {
text: `Hello ${text}!`
});
});
router.get("/nodes/create", (req, res) => {
res.render("node-create");
});
router.post("/nodes/create", (req, res) => {
return Promise.try(() => {
return req.pass();
}).then((response) => {
console.log(response.body);
});
});
return router;
};

@ -0,0 +1,13 @@
'use strict';
const deconstructFormObject = require("./deconstruct-form-object");
module.exports = function objectToFormData(object) {
let formData = new FormData();
deconstructFormObject(object).forEach((item) => {
formData.append(item.name, item.value)
});
return formData;
};

@ -0,0 +1,13 @@
'use strict';
const deconstructFormObject = require("./deconstruct-form-object");
module.exports = function objectToURLSearchParams(object) {
let params = new URLSearchParams();
deconstructFormObject(object).forEach((item) => {
params.append(item.name, item.value)
});
return params;
};

@ -9,7 +9,6 @@ function px(pixels) {
module.exports = {
init: function() {
console.log("foo", this);
let dockableAPI = {
isActive: () => {
return (this.opts.dockableContainer != null);
@ -96,8 +95,6 @@ module.exports = {
reservedBottom += height;
}
console.log("reserved", reservedLeft, reservedRight, reservedTop, reservedBottom);
})
let item = dockableAPI.fillElement;
@ -113,18 +110,15 @@ module.exports = {
bottom: px(reservedBottom)
});
}
console.log("ordered stack", orderedStack);
}
}
this._uikitDockableContainer = dockableAPI;
this.on("mount", () => {
console.log("dockable-container mounted");
if (dockableAPI.isActive()) {
dockableAPI.recalculateLayout();
}
})
}
}
}

Binary file not shown.

@ -1,9 +1,15 @@
"use strict";
const Promise = require("bluebird");
const pathToRegexp = require("path-to-regexp");
const url = require("url");
const xtend = require("xtend");
const defaultValue = require("default-value");
const formDataToObject = require("../formdata-to-object");
const objectToURLSearchParams = require("../object-to-urlsearchparams");
const objectToFormData = require("../object-to-formdata");
module.exports = function() {
let routes = [];
@ -35,28 +41,70 @@ module.exports = function() {
}
}
function handle(method, uri, data) {
function handle(method, uri, data, handleOptions = {}) {
return Promise.try(() => {
let {path, query} = url.parse(uri, true);
let route = getRoute(method, path);
// FIXME: Support relative paths?
let {pathname, query} = url.parse(uri, true);
let route = getRoute(method, pathname);
let tasks = [];
let body;
if (data instanceof FormData) {
body = formDataToObject(data);
} else if (data instanceof URLSearchParams) {
body = formDataToObject(data); // FIXME: Does this work?
} else {
body = data;
}
let req = {
path: path,
path: pathname,
query: query,
body: data,
body: body,
params: route.params,
pass: function(options = {}) {
return Promise.try(() => {
let body;
if (handleOptions.multipart) {
body = objectToFormData(this.body)
} else {
body = objectToURLSearchParams(this.body);
}
return window.fetch(uri, Object.assign({ // FIXME: Override URI but maintain query?
method: method,
credentials: true,
body: body
}, options));
}).then((response) => {
if (!response.ok) {
// FIXME: Is this what we want?
throw new Error(`Got a non-200 response: ${response.status}`, {response: response});
} else {
return Promise.try(() => {
return response.json();
}).then((json) => {
return {
status: response.status,
body: json
}
});
}
});
// FIXME: window.fetch passthrough
},
// MARKER: passActions?
passRender: function(viewName, options = {}) {
return Promise.try(() => {
return this.pass(options);
return this.pass(options.requestOptions);
}).then((response) => {
let locals = defaultValue(options.locals, {});
let combinedLocals = xtend(locals, response.body);
res.render(viewName, combinedLocals, options);
res.render(viewName, combinedLocals, options.renderOptions);
});
}
}
@ -117,4 +165,4 @@ module.exports = function() {
}
return api;
}
}

@ -20,8 +20,6 @@ openng-view-node-create
script.
this.mixin(require("riot-query").mixin);
console.log(this.query("**/uikit-button"));
style(scoped, type="scss").
uikit-toolbar.main {
padding: 6px;
@ -29,4 +27,4 @@ openng-view-node-create
uikit-frame.main {
padding: 8px;
}
}

@ -0,0 +1,14 @@
'use strict';
require("../../../../components/uikit/frame");
require("../../../../components/uikit/toolbar");
require("../../../../components/uikit/button");
require("../../../../components/uikit/form-section");
require("../../../../components/uikit/input-spawner");
require("../../../../components/uikit/input");
require("../../../../components/uikit/textarea");
require("../../../../components/uikit/autocomplete");
require("../../../../components/window-meta");
require("./component");

@ -0,0 +1,19 @@
[{
"name": "Domain Name"
}, {
"name": "IP Address"
}, {
"name": "Person"
}, {
"name": "News Article"
}, {
"name": "Event"
}, {
"name": "MAC Address"
}, {
"name": "Organization"
}, {
"name": "Product"
}, {
"name": "Software"
}]

@ -0,0 +1,20 @@
'use strict';
const fs = require("fs");
const Promise = require("bluebird");
const elasticSearch = new require("elasticsearch").Client({
host: "localhost:9200",
log: "trace"
});
let nodeTypes = JSON.parse(fs.readFileSync("./sample/elasticsearch-node-types.json"));
Promise.map(nodeTypes, (type) => {
return elasticSearch.create({
index: "node_types",
type: "node_type",
body: type
});
}).then((results) => {
console.log(results);
});

@ -0,0 +1,43 @@
'use strict';
const webpack = require("webpack");
const path = require("path");
module.exports = {
watch: true,
mode: "development",
entry: {
main: [
"webpack-hot-middleware/client?overlay=true&reload=true",
"./frontend/index.jsx"
]
},
output: {
publicPath: "/",
path: path.join(__dirname, "public/static/"),
filename: "bundle.js"
},
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules|src\/testcases/,
resolve: {
extensions: [".js", ".jsx"]
},
use: [{
loader: require.resolve("babel-loader"),
query: {
presets: [
"es2015",
"react"
].map(item => require.resolve(`babel-preset-${item}`))
}
}]
}]
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
devtool: "source-map"
};

@ -9,34 +9,54 @@
"arraydiff": "^0.1.3",
"bhttp": "^1.2.1",
"bluebird": "^3.0.6",
"body-parser": "^1.15.1",
"body-parser": "^1.18.3",
"chokidar": "^2.0.4",
"classnames": "^2.2.5",
"copy-props": "^2.0.4",
"create-error": "^0.3.1",
"create-event-emitter": "^1.0.0",
"create-react-class": "^15.6.3",
"debounce": "^1.1.0",
"debug": "^2.2.0",
"default-value": "0.0.3",
"express": "^4.13.3",
"default-value": "^1.0.0",
"document-ready-promise": "^3.0.1",
"euclidean-distance": "^1.0.0",
"express": "^4.16.3",
"express-promise-router": "^1.0.0",
"form-serialize": "^0.7.2",
"immutable": "^3.8.2",
"in-array": "^0.1.2",
"jade": "^1.11.0",
"jquery": "^2.1.1",
"node-sass": "^3.2.0",
"riot": "^2.4.0",
"riot-query": "0.0.3",
"uuid": "^2.0.1",
"mkdirp": "^0.5.1",
"moment": "^2.22.2",
"nanoid": "^2.0.0",
"object-to-formdata": "^1.0.9",
"pug": "^2.0.0-beta11",
"qs": "^6.4.0",
"react": "^16.7.0-alpha.0",
"react-dom": "^16.7.0-alpha.0",
"sse-channel": "^3.1.1",
"throttleit": "^1.0.0",
"unhandled-rejection": "^1.0.0",
"use-force-update": "^1.0.0",
"uuid": "^3.3.2",
"xtend": "^4.0.1"
},
"devDependencies": {
"@joepie91/gulp-preset-es2015": "^1.0.1",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.6.0",
"babel-preset-es2015-riot": "^1.1.0",
"debounce": "^1.0.0",
"@babel/core": "^7.1.5",
"@babel/preset-env": "^7.1.5",
"@babel/preset-react": "^7.0.0",
"await-server": "^1.0.0",
"babelify": "^10.0.0",
"browserify-hmr": "^0.3.6",
"budo": "^11.5.0",
"chalk": "^2.4.1",
"element-size": "^1.1.1",
"gulp": "^3.9.0",
"gulp-livereload": "^3.8.1",
"gulp-nodemon": "^2.0.4",
"gulp-rename": "^1.2.0",
"eslint": "^4.19.1",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-react-hooks": "^0.0.0",
"json-loader": "^0.5.4",
"node-sass": "^4.10.0",
"path-to-regexp": "^1.2.1",
"riotjs-loader": "^3.0.0",
"webpack-stream": "^3.2.0"
"react-hot-loader": "4.4.0-1"
}
}

@ -0,0 +1,76 @@
.windowManager {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px; }
.window {
position: absolute;
left: 0px;
top: 0px;
display: grid;
grid-template-rows: auto 1fr;
box-shadow: 5px 5px 10px #1a1a1a;
opacity: 0.95; }
.window, .window .titleBar {
border-top-left-radius: 10px;
border-top-right-radius: 10px; }
.window .titleBar {
display: grid;
grid-template-columns: 1fr auto;
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
cursor: default;
border: 1px solid #959595;
border-bottom: none;
padding: 4px;
padding-left: 7px;
background: linear-gradient(to bottom, #525252 0%, #91acbe 100%); }
.window .titleBar .title {
padding-top: 1px;
color: white;
font-size: 14px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.window .titleBar .buttons {
display: flex;
margin-left: 8px; }
.window .titleBar .buttons .button {
padding: 2px;
border: 1px solid #014D8C;
border-radius: 5px;
color: white;
font-weight: bold; }
.window .titleBar .buttons .button:hover {
background-color: #014D8C;
border: 1px solid white; }
.window .titleBar .buttons .button.close {
font-size: 12px;
line-height: 1; }
.window .body {
position: relative;
background-color: #F7F7F0;
border: 1px solid gray;
border-top: none; }
.window .body .content {
overflow-x: auto;
overflow-y: auto;
padding: 7px;
/* FIXME: Is this needed here? */
font-size: 13px; }
.window .body .resizer {
position: absolute;
/* FIXME: Make the below values configurable? */
bottom: -6px;
right: -6px;
width: 12px;
height: 12px;
cursor: se-resize; }
.window.active .titleBar {
background: linear-gradient(to bottom, #0057b3 0%, #0099ff 100%);
border-color: #6262FF; }

@ -0,0 +1,142 @@
.viewManager {
width: 100%; }
.windowManager {
position: absolute;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px; }
.window {
position: absolute;
left: 0px;
top: 0px;
display: grid;
grid-template-rows: auto 1fr;
box-shadow: 5px 5px 10px #1a1a1a;
opacity: 0.95; }
.window, .window .titleBar {
border-top-left-radius: 10px;
border-top-right-radius: 10px; }
.window .titleBar {
display: grid;
grid-template-columns: 1fr auto;
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
cursor: default;
border: 1px solid #959595;
border-bottom: none;
padding: 4px;
padding-left: 7px;
background: linear-gradient(to bottom, #525252 0%, #91acbe 100%); }
.window .titleBar .title {
padding-top: 1px;
color: white;
font-size: 14px;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.window .titleBar .buttons {
display: flex;
margin-left: 8px; }
.window .titleBar .buttons .button {
padding: 2px;
border: 1px solid #526371;
border-radius: 5px;
color: white;
font-weight: bold; }
.window .titleBar .buttons .button:hover {
background-color: #555f68;
border: 1px solid #ada8a8; }
.window .titleBar .buttons .button.close {
font-size: 12px;
line-height: 1; }
.window .body {
position: relative;
background-color: #F7F7F0;
border: 1px solid gray;
border-top: none; }
.window .body .content {
overflow-x: auto;
overflow-y: auto;
padding: 7px;
/* FIXME: Is this needed here? */
font-size: 13px; }
.window .body .resizer {
user-select: none;
-ms-user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
position: absolute;
/* FIXME: Make the below values configurable? */
bottom: -8px;
right: -8px;
width: 16px;
height: 16px;
cursor: se-resize; }
.window .body .resizer:hover .arrow {
position: relative;
left: -4px;
top: -4px;
width: 0px;
height: 0px;
border-style: solid;
border-width: 0px 0px 12px 12px;
border-color: transparent transparent rgba(0, 109, 201, 0.8) transparent; }
.window.active .titleBar {
background: linear-gradient(to bottom, #0057b3 0%, #0099ff 100%);
border-color: #6262FF; }
.window.active .titleBar .buttons .button {
border: 1px solid #014D8C; }
.window.active .titleBar .buttons .button:hover {
background-color: #0e5c9c;
border: 1px solid #7ab0e6; }
#notificationArea {
position: absolute;
right: 0px;
bottom: 32px;
z-index: 2147483640;
display: flex;
flex-direction: column; }
.notification {
display: inline-block;
border-radius: 6px;
margin-right: 19px;
margin-top: 10px;
padding: 9px 14px;
color: white;
font-size: 15px;
filter: alpha(opacity=85);
opacity: 0.85;
width: auto;
min-width: 220px; }
.notification .closeButton {
float: right;
margin-top: -4px;
margin-right: -6px;
margin-left: 6px;
margin-bottom: 4px;
padding: 4px 6px;
opacity: 0.6;
border-radius: 3px;
cursor: default; }
.notification .closeButton:hover {
opacity: 1;
background-color: rgba(30, 30, 30, 0.6); }
.notification .header {
margin-right: 6px;
font-weight: bold;
margin-bottom: 6px; }
.notification .contents ul {
margin: 4px 0px;
padding-left: 48px; }
.notification.type-info {
background-color: #2D2D2D; }
.notification.type-error {
background-color: #371B1B; }

@ -0,0 +1,616 @@
body {
background-image: url(/images/background.jpg); }
#logo {
z-index: 0;
display: inline;
position: relative;
top: -24px;
margin-left: 20px;
color: white;
font-size: 64px;
font-family: 'Istok Web';
font-weight: bold;
color: #E2FFFF;
text-shadow: 0px 0px 1px #CEE3F9;
-webkit-text-shadow: 0px 0px 1px #CEE3F9;
-moz-text-shadow: 0px 0px 1px #CEE3F9;
-o-text-shadow: 0px 0px 1px #CEE3F9;
-ms-text-shadow: 0px 0px 1px #CEE3F9; }
.clear {
clear: both; }
form.pure-form-inline {
display: inline; }
.autocompleter {
display: none;
position: absolute;
/* TODO: set this in the JS! */
z-index: 9999999;
-webkit-font-smoothing: antialiased;
font-family: 'Istok Web';
background-color: white;
border: 1px solid #5A6DBB;
border-radius: 0px 0px 3px 3px;
border-top: none; }
.autocompleter .entry {
color: #393939; }
.autocompleter .entry.selected {
background-color: #4253B6;
color: white; }
#autocomplete_search {
width: 400px; }
#autocomplete_search .entry {
border-bottom: 1px solid #C2C2C2;
padding: 7px 9px; }
#autocomplete_search .entry:last-child {
border-bottom: none; }
#autocomplete_search .entry .name {
font-size: 18px;
font-weight: bold; }
#autocomplete_search .entry .description, #autocomplete_search .entry .date {
font-size: 14px; }
#autocomplete_search .entry .date {
float: right;
color: gray; }
#autocomplete_search .entry.selected .date {
color: white; }
#autocomplete_search .loading, #autocomplete_search .noresults {
padding: 8px 10px;
font-size: 17px; }
#autocomplete_search .loading {
font-style: italic; }
.group-first, .group-middle {
border-top-right-radius: 0px !important;
border-bottom-right-radius: 0px !important; }
.group-middle, .group-last {
border-top-left-radius: 0px !important;
border-bottom-left-radius: 0px !important; }
#mainToolbar {
display: flex;
padding: 36px 24px; }
#mainToolbar, #mainToolbar button {
font-family: "Istok Web"; }
#mainToolbar a.add, #mainToolbar input, #mainToolbar button {
display: block;
box-sizing: border-box;
height: 40px; }
#mainToolbar a.add {
float: left; }
#mainToolbar form.search, #mainToolbar form.download {
display: inline-flex;
margin-left: 12px; }
#mainToolbar form.search input, #mainToolbar form.download input {
margin-left: 12px;
font-size: 16px;
padding: 7px 14px;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
width: 290px; }
#mainToolbar form.search button, #mainToolbar form.download button {
border-top-left-radius: 0px;
border-bottom-left-radius: 0px; }
#mainToolbar form.download {
margin-left: auto; }
.pure-button {
padding: 9px 14px 7px 14px; }
.pure-button.shadow {
text-shadow: 1px 1px 1px #424242;
-webkit-text-shadow: 1px 1px 1px #424242;
-moz-text-shadow: 1px 1px 2px #424242;
-o-text-shadow: 1px 1px 1px #424242;
-ms-text-shadow: 1px 1px 1px #424242; }
.pure-button.add {
background-color: #15BA31;
color: white; }
.pure-button.okay {
background-color: #148C29;
color: white; }
.pure-button.search {
background-color: #152DBA;
color: white; }
.pure-button.download {
background-color: #1591ba;
color: white; }
.pure-button i {
margin-right: 8px;
position: relative;
top: 1px; }
.element-group {
display: block;
float: left; }
.element-group * {
float: left; }
.formSection {
margin-bottom: 5px; }
.formSection h1.formSectionTitle {
font-size: 16px;
margin-top: 0px;
margin-bottom: 6px; }
.formField .labelWrapper {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
width: 33.3333%;
*width: 33.3023%;
padding-top: 4px; }
.formField .labelWrapper label {
font-size: 95%;
margin-left: 1px; }
.formField:not(.grouped):not(.unlabeled) .inputWrapper {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
width: 66.6667%;
*width: 66.6357%; }
.formField input, .formField textarea {
width: 100%; }
.formField input.halfWidth, .formField textarea.halfWidth {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
width: 50%;
*width: 49.9690%; }
.formField input.invalid, .formField textarea.invalid {
border-color: #B60202 !important; }
.formField textarea {
height: 90px; }
.formField.unlabeled:not(.grouped) .inputWrapper {
display: inline-block;
*display: inline;
zoom: 1;
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
text-rendering: auto;
width: 100%; }
.formField.grouped .inputWrapper {
float: left; }
.formField.grouped .inputWrapper input, .formField.grouped .inputWrapper textarea, .formField.grouped .inputWrapper button {
border-radius: 0px;
border-right-width: 0px; }
.formField.grouped .inputWrapper:first-child input, .formField.grouped .inputWrapper:first-child textarea, .formField.grouped .inputWrapper:first-child button {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px; }
.formField.grouped .inputWrapper:last-child input, .formField.grouped .inputWrapper:last-child textarea, .formField.grouped .inputWrapper:last-child button {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px; }
.formField.grouped .inputWrapper:last-child input, .formField.grouped .inputWrapper:last-child textarea {
border-right-width: 1px; }
.toolbarWindow.hasTop .toolbarWindowContents {
top: 38px; }
.toolbarWindow.hasBottom .toolbarWindowContents {
bottom: 38px; }
.toolbarWindow.hasLeft .toolbarWindowContents {
left: 38px; }
.toolbarWindow.hasRight .toolbarWindowContents {
right: 38px; }
.toolbarWindow .toolbarWindowContents {
position: absolute;
padding: 7px;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow-x: hidden;
overflow-y: auto; }
.toolbarWindow .toolbarWindowControls {
position: absolute;
padding: 4px;
text-align: right; }
.toolbarWindow .toolbarWindowControls.top, .toolbarWindow .toolbarWindowControls.bottom {
height: 30px;
left: 0px;
right: 0px; }
.toolbarWindow .toolbarWindowControls.top {
top: 0px; }
.toolbarWindow .toolbarWindowControls.bottom {
bottom: 0px; }
.toolbarWindow .toolbarWindowControls.left, .toolbarWindow .toolbarWindowControls.right {
width: 30px;
top: 0px;
bottom: 0px; }
.toolbarWindow .toolbarWindowControls.left {
left: 0px; }
.toolbarWindow .toolbarWindowControls.right {
right: 0px; }
.pure-button.style-okay {
background-color: #148C29;
color: white; }
div.formfield, div.property {
margin-bottom: 1px; }
.toolbarwindow-contents {
position: absolute;
padding: 7px;
top: 0px;
left: 0px;
right: 0px;
bottom: 38px;
overflow-x: hidden;
overflow-y: auto; }
.toolbarwindow-toolbar {
position: absolute;
padding: 4px;
height: 30px;
left: 0px;
right: 0px;
bottom: 0px;
text-align: right; }
i.required {
font-size: 10px;
margin-left: 4px;
color: #9f444a;
float: right;
margin-right: 7px;
margin-top: 2px; }
i.error, i.notification {
font-size: 19px;
margin-right: 9px !important; }
i.error {
color: #FFD2D2; }
i.notification {
color: #CBCAFF; }
#notification_area {
position: absolute;
right: 0px;
bottom: 32px;
z-index: 2147483640; }
.notification-header {
margin-right: 6px;
font-weight: bold; }
.notification-popup, .error-popup {
text-align: right; }
.notification-popup .notification-contents, .error-popup .notification-contents {
text-align: left;
display: inline-block;
border-radius: 6px;
margin-right: 19px;
margin-top: 10px;
padding: 9px 14px;
color: white;
font-size: 15px;
filter: alpha(opacity=85);
opacity: 0.85;
width: auto; }
.notification-popup .notification-contents {
background-color: #2D2D2D; }
.error-popup .notification-contents {
background-color: #371B1B; }
.notification-popup ul, .error-popup ul {
margin: 4px 0px;
padding-left: 48px; }
#autocomplete_propertyname {
font-size: 13px; }
#autocomplete_propertyname .entry, #autocomplete_propertyname .noresults, #autocomplete_propertyname .loading {
padding: 4px 6px; }
.window-inner .header, .window-inner h1, .window-inner h2 {
margin: 4px 0px; }
.window-inner .lower-header, .window-inner h2 {
margin-top: 7px; }
.window-inner h1 {
font-size: 20px; }
.window-inner h2 {
font-size: 16px; }
.window-inner .unit-content {
padding: 6px; }
.window-inner a.source-ref {
float: right;
margin-right: -4px;
text-decoration: none;
color: gray;
font-size: 13px; }
.window-inner a.source-ref:hover {
color: black; }
.window-inner table {
font-size: 13px;
width: 100%; }
.window-inner td.property-name {
width: 40%;
text-align: right;
font-weight: bold; }
.window-inner .node-lookup form.property-add input {
width: 100%; }
.window-inner .node-lookup form.property-add .property-name input {
text-align: right; }
html, body {
overflow: hidden; }
body {
position: fixed;
margin: 0px;
width: 100%;
height: 100%; }
#jsde_templates {
display: none; }
/* MDIWindow Styling | Generic */
div.window-wrapper {
position: absolute; }
div.window-title {
position: absolute;
z-index: 2;
left: 0px;
right: 0px;
top: 0px;
cursor: default;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap; }
div.window-outer {
position: absolute;
z-index: 3;
left: 0px;
right: 0px;
bottom: 0px; }
div.window-inner-wrapper {
position: absolute;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
overflow-y: auto;
overflow-x: auto; }
.window-noscroll {
overflow-x: hidden !important;
overflow-y: hidden !important; }
div.window-close {
float: right; }
div.window-close a {
position: absolute;
right: 3px;
top: 2px;
display: block;
padding: 1px 4px;
text-decoration: none;
font-size: 12px;
border-radius: 5px; }
/* MDIWindow Styling | Normal state */
div.window-styled div.window-inner {
visibility: visible; }
/* MDIWindow Styling | Dragging state */
div.window-dragged div.window-inner {
visibility: hidden; }
div.workspace-bar {
position: absolute;
bottom: 0px;
left: 0px;
right: 0px; }
a.workspace-tab {
display: block;
float: left; }
div.window-resizer {
position: absolute;
width: 12px;
height: 12px;
bottom: -6px;
right: -6px;
cursor: se-resize; }
html, body {
font-family: 'Varela Round', sans-serif, Trebuchet MS;
background-color: silver;
background-attachment: fixed;
background-position: center; }
/* Temporary styles */
div#make-window {
background-color: white;
border: 1px solid gray;
padding: 14px;
width: 350px;
filter: alpha(opacity=85);
opacity: 0.85; }
/* MDIWindow Styling | Generic */
div.window-title {
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 10px;
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
height: 16px;
color: white;
font-size: 14px;
font-weight: bold;
padding: 4px;
padding-left: 7px;
border-top: 1px solid #959595;
border-right: 1px solid #959595;
border-left: 1px solid #959595; }
div.window-focused div.window-title {
border-top: 1px solid #6262FF;
border-right: 1px solid #6262FF;
border-left: 1px solid #6262FF; }
div.window-outer {
font-size: 13px;
top: 25px;
border-bottom: 1px solid gray;
border-right: 1px solid gray;
border-left: 1px solid gray; }
div.window-inner {
padding: 7px; }
div.window-close a {
color: white;
border: 1px solid #014D8C; }
div.window-close a:hover {
background-color: #014D8C;
border: 1px solid white; }
/* MDIWindow Styling | Normal state */
div.window-styled div.window-title, div.window-styled div.window-outer {
-webkit-box-shadow: 5px 5px 10px #1a1a1a;
-moz-box-shadow: 5px 5px 10px #1a1a1a;
box-shadow: 5px 5px 10px #1a1a1a; }
div.window-styled div.window-title {
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #525252), color-stop(1, #91acbe));
background-image: -moz-linear-gradient(center bottom, #525252 0%, #91acbe 100%);
filter: alpha(opacity=95);
opacity: 0.95; }
div.window-styled div.window-outer {
background-color: #F7F7F0;
filter: alpha(opacity=95);
opacity: 0.95; }
div.window-focused.window-styled div.window-title {
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #0057b3), color-stop(1, #0099ff));
background-image: -moz-linear-gradient(center bottom, #0057b3 0%, #0099ff 100%);
filter: alpha(opacity=85);
opacity: 0.85; }
/* MDIWindow Styling | Dragging state */
div.window-dragged div.window-title {
background-color: #0070D5;
background-image: none; }
div.workspace-bar {
height: 32px; }
div.window-dragged div.window-outer {
background: none; }
div.window-dragged div.window-title, div.window-dragged div.window-outer {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none; }
a.workspace-tab {
height: 32px;
width: 48px;
-webkit-border-top-left-radius: 10px;
-webkit-border-top-right-radius: 10px;
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
border: 1px solid black;
background-color: #E9E9E9;
padding-top: 6px;
text-align: center;
text-decoration: none;
font-size: 14px;
color: black;
margin-top: 10px;
filter: alpha(opacity=95);
opacity: 0.95; }
a.workspace-tab:hover {
background-color: #DADADA;
margin-top: 0px; }
a.workspace-tab-active {
font-weight: bold;
background-color: #B9B9B9;
color: #BC0000; }
a.workspace-tab-popup {
margin-top: 0px; }
a.workspace-tab-add {
background-color: #C8C8C8;
filter: alpha(opacity=85);
opacity: 0.85; }
window {
/* In order for stacked z-index to work, and prevent the segments of a window from
* overlapping due to z-index conflicts, we *have* to specify positioning for the
* `window` wrapper element. This way a stacking context is created, and the z-index
* conflicts go away.
*/
position: absolute;
left: 0px;
top: 0px; }

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save