浏览代码

Added ability to upload custom theme

Dmitriy Slipak 4 年前
父节点
当前提交
bd381d2999
共有 12 个文件被更改,包括 5010 次插入116 次删除
  1. 1 1
      audiogram/draw-frames.js
  2. 2 1
      audiogram/initialize-canvas.js
  3. 95 1
      client/index.js
  4. 30 2
      client/preview.js
  5. 21 0
      editor/index.html
  6. 4806 0
      package-lock.json
  7. 3 3
      package.json
  8. 46 1
      server/index.js
  9. 二进制
      settings/backgrounds/nyp.png
  10. 2 0
      settings/index.js
  11. 1 105
      settings/themes.json
  12. 3 2
      test/frame-test.js

+ 1 - 1
audiogram/draw-frames.js 查看文件

@@ -9,7 +9,7 @@ function drawFrames(renderer, options, cb) {
9 9
       canvases = [];
10 10
 
11 11
   for (var i = 0; i < 10; i++) {
12
-    canvases.push(new Canvas(options.width, options.height));
12
+    canvases.push(Canvas.createCanvas(options.width, options.height));
13 13
   }
14 14
 
15 15
   for (var i = 0; i < options.numFrames; i++) {

+ 2 - 1
audiogram/initialize-canvas.js 查看文件

@@ -19,7 +19,8 @@ function initializeCanvas(theme, cb) {
19 19
       return cb(err);
20 20
     }
21 21
 
22
-    var bg = new Canvas.Image;
22
+    const canvas = Canvas.createCanvas();
23
+    var bg = canvas.Image;
23 24
     bg.src = raw;
24 25
     renderer.backgroundImage(bg);
25 26
 

+ 95 - 1
client/index.js 查看文件

@@ -2,7 +2,8 @@ var d3 = require("d3"),
2 2
     $ = require("jquery"),
3 3
     preview = require("./preview.js"),
4 4
     video = require("./video.js"),
5
-    audio = require("./audio.js");
5
+    audio = require("./audio.js"),
6
+    newTheme = null;
6 7
 
7 8
 d3.json("/settings/themes.json", function(err, themes){
8 9
 
@@ -90,6 +91,73 @@ function submitted() {
90 91
 
91 92
 }
92 93
 
94
+function uploadTheme() {
95
+  var formData = new FormData();
96
+  var file = preview.newTheme();
97
+
98
+  formData.append("newTheme", file);
99
+
100
+  var newCaption = preview.newCaption();
101
+  console.log('new theme', newCaption);
102
+  formData.append("newCaption", newCaption);
103
+  console.log(formData);
104
+  $.ajax({
105
+    url: "/theme/upload",
106
+    type: "POST",
107
+    data: formData,
108
+    contentType: false,
109
+    cache: false,
110
+    processData: false,
111
+    success: function () {
112
+      d3.json("/settings/themes.json", function(err, themes){
113
+
114
+        var errorMessage;
115
+
116
+        // Themes are missing or invalid
117
+        if (err || !d3.keys(themes).filter(function(d){ return d !== "default"; }).length) {
118
+          if (err instanceof SyntaxError) {
119
+            errorMessage = "Error in settings/themes.json:<br/><code>" + err.toString() + "</code>";
120
+          } else if (err instanceof ProgressEvent) {
121
+            errorMessage = "Error: no settings/themes.json.";
122
+          } else if (err) {
123
+            errorMessage = "Error: couldn't load settings/themes.json.";
124
+          } else {
125
+            errorMessage = "No themes found in settings/themes.json.";
126
+          }
127
+          d3.select("#loading-bars").remove();
128
+          d3.select("#loading-message").html(errorMessage);
129
+          if (err) {
130
+            throw err;
131
+          }
132
+          return;
133
+        }
134
+
135
+        for (var key in themes) {
136
+          themes[key] = $.extend({}, themes.default, themes[key]);
137
+        }
138
+
139
+        preloadImages(themes);
140
+
141
+        d3.select("#input-theme")
142
+          .selectAll("option")
143
+          .each(function (d) {
144
+            if (d.name === newCaption) {
145
+              this["selected"] = "selected";
146
+              d3.select("#input-new-theme").property("value", "");
147
+              d3.select("#input-new-caption").property("value", "");
148
+              return;
149
+            }
150
+          });
151
+
152
+      });
153
+    },
154
+    error: function (error) {
155
+      console.log('error', error);
156
+    }
157
+  });
158
+
159
+}
160
+
93 161
 function poll(id) {
94 162
 
95 163
   setTimeout(function(){
@@ -180,6 +248,12 @@ function initialize(err, themesWithImages) {
180 248
     setClass(null);
181 249
   });
182 250
 
251
+  d3.select("#btn-new-theme").on("click", uploadTheme);
252
+
253
+  d3.select("#input-new-theme").on("change", updateNewThemeFile).each(updateNewThemeFile);
254
+
255
+  d3.select("#input-new-caption").on("change keyup", updateNewCaption).each(updateNewCaption);
256
+
183 257
   d3.select("#submit").on("click", submitted);
184 258
 
185 259
 }
@@ -218,6 +292,22 @@ function updateAudioFile() {
218 292
 
219 293
 }
220 294
 
295
+function updateNewThemeFile() {
296
+  if (!this.files || !this.files[0]) {
297
+    preview.newTheme(null);
298
+    return true;
299
+  }
300
+
301
+  newTheme = this.files[0];
302
+  preview.loadNewTheme(newTheme, function (err) {
303
+    if (err) {
304
+      setClass("error", "Error updating new theme file");
305
+    } else {
306
+      setClass(null);
307
+    }
308
+  });
309
+}
310
+
221 311
 function updateCaption() {
222 312
   preview.caption(this.value);
223 313
 }
@@ -226,6 +316,10 @@ function updateTheme() {
226 316
   preview.theme(d3.select(this.options[this.selectedIndex]).datum());
227 317
 }
228 318
 
319
+function updateNewCaption() {
320
+  preview.newCaption(this.value);
321
+}
322
+
229 323
 function preloadImages(themes) {
230 324
 
231 325
   // preload images

+ 30 - 2
client/preview.js 查看文件

@@ -11,7 +11,9 @@ var context = d3.select("canvas").node().getContext("2d");
11 11
 var theme,
12 12
     caption,
13 13
     file,
14
-    selection;
14
+    selection,
15
+    newTheme,
16
+    newCaption;
15 17
 
16 18
 function _file(_) {
17 19
   return arguments.length ? (file = _) : file;
@@ -29,6 +31,14 @@ function _selection(_) {
29 31
   return arguments.length ? (selection = _) : selection;
30 32
 }
31 33
 
34
+function _newTheme(_) {
35
+  return arguments.length ? (newTheme = _) : newTheme;
36
+}
37
+
38
+function _newCaption(_) {
39
+  return arguments.length ? (newCaption = _) : newCaption;
40
+}
41
+
32 42
 minimap.onBrush(function(extent){
33 43
 
34 44
   var duration = audio.duration();
@@ -106,10 +116,28 @@ function loadAudio(f, cb) {
106 116
 
107 117
 }
108 118
 
119
+function loadNewTheme(f, cb) {
120
+  d3.queue()
121
+  .await(function(err, data){
122
+
123
+      if (err) {
124
+        return cb(err);
125
+      }
126
+
127
+      newTheme = f;
128
+
129
+      cb(err);
130
+
131
+    });
132
+}
133
+
109 134
 module.exports = {
110 135
   caption: _caption,
111 136
   theme: _theme,
112 137
   file: _file,
113 138
   selection: _selection,
114
-  loadAudio: loadAudio
139
+  loadAudio: loadAudio,
140
+  newTheme: _newTheme,
141
+  newCaption: _newCaption,
142
+  loadNewTheme: loadNewTheme
115 143
 };

+ 21 - 0
editor/index.html 查看文件

@@ -36,6 +36,27 @@
36 36
           </label>
37 37
           <input id="input-caption" name="caption" type="text" autocomplete="off" placeholder="Add a caption" />
38 38
         </div>
39
+
40
+        <div class="row form-row">
41
+          <hr>
42
+        </div>
43
+        <div class="row form-row" id="row-new-theme">
44
+          <label for="input-new-theme">New theme</label>
45
+          <input id="input-new-theme" name="new-theme" type="file" />
46
+        </div>
47
+        <div class="row form-row" id="row-caption">
48
+          <label for="input-caption">
49
+            Name
50
+          </label>
51
+          <input id="input-new-caption" name="new-caption" type="text" autocomplete="off" placeholder="Add a new theme name" />
52
+        </div>
53
+        <div class="row form-row">
54
+          <button class="btn btn-outline-primary" name="btn-new-theme" id="btn-new-theme">Add</button>
55
+        </div>
56
+        <div class="row form-row">
57
+          <hr>
58
+        </div>
59
+
39 60
         <div id="preview">
40 61
           <div style="background-color: black;">
41 62
             <div id="canvas">

文件差异内容过多而无法显示
+ 4806 - 0
package-lock.json


+ 3 - 3
package.json 查看文件

@@ -25,12 +25,12 @@
25 25
   "dependencies": {
26 26
     "aws-sdk": "^2.2.39",
27 27
     "browserify": "^13.0.0",
28
-    "canvas": "git+https://github.com/chearon/node-canvas.git#12971f64a66b",
28
+    "canvas": "^2.6.1",
29 29
     "compression": "^1.6.1",
30 30
     "d3": "4.10.0",
31 31
     "dotenv": "^2.0.0",
32 32
     "express": "^4.13.3",
33
-    "jquery": "^2.2.1",
33
+    "jquery": "^3.4.1",
34 34
     "mkdirp": "^0.5.1",
35 35
     "morgan": "^1.7.0",
36 36
     "multer": "^1.1.0",
@@ -44,7 +44,7 @@
44 44
     "winston": "^2.2.0"
45 45
   },
46 46
   "devDependencies": {
47
-    "supertest": "^1.2.0",
47
+    "supertest": "^4.0.0",
48 48
     "tape": "^4.6.0",
49 49
     "watchify": "^3.7.0"
50 50
   }

+ 46 - 1
server/index.js 查看文件

@@ -11,7 +11,8 @@ var logger = require("../lib/logger/"),
11 11
     render = require("./render.js"),
12 12
     status = require("./status.js"),
13 13
     fonts = require("./fonts.js"),
14
-    errorHandlers = require("./error.js");
14
+    errorHandlers = require("./error.js"),
15
+    fs = require("fs");;
15 16
 
16 17
 // Settings
17 18
 var serverSettings = require("../lib/settings/");
@@ -38,15 +39,59 @@ var fileOptions = {
38 39
   })
39 40
 };
40 41
 
42
+var newThemeFileOptions = {
43
+  storage: multer.diskStorage({
44
+    destination: function(req, file, cb) {
45
+
46
+      var dir = path.join(serverSettings.themeStoragePath);
47
+
48
+      mkdirp(dir, function(err) {
49
+        return cb(err, dir);
50
+      });
51
+    },
52
+    filename: function(req, file, cb) {
53
+      cb(null, file.originalname);
54
+    }
55
+  })
56
+};
57
+
41 58
 if (serverSettings.maxUploadSize) {
42 59
   fileOptions.limits = {
43 60
     fileSize: +serverSettings.maxUploadSize
44 61
   };
62
+  newThemeFileOptions.limits = {
63
+    fileSize: +serverSettings.maxUploadSize
64
+  };
45 65
 }
46 66
 
47 67
 // On submission, check upload, validate input, and start generating a video
48 68
 app.post("/submit/", [multer(fileOptions).single("audio"), render.validate, render.route]);
49 69
 
70
+// Upload new theme
71
+app.post("/theme/upload/", [multer(newThemeFileOptions).single("newTheme"), function (req, res) {
72
+  var themesFile = path.join(serverSettings.settingsPath, "themes.json");
73
+  fs.readFile(themesFile, "utf8", function readFileCallback(err, data) {
74
+    if (err) {
75
+      console.log('err', err);
76
+      return null;
77
+    } else {
78
+      var caption = req.body.newCaption;
79
+      var themes = JSON.parse(data);
80
+      themes[caption] = {
81
+        "backgroundImage": req.file.filename
82
+      };
83
+      var jt = JSON.stringify(themes);
84
+      fs.writeFile(themesFile, jt, "utf8", function (err) {
85
+        if (err) {
86
+          console.log(err);
87
+          return null;
88
+        }
89
+      });
90
+    }
91
+  });
92
+  res.end();
93
+}]);
94
+
50 95
 // If not using S3, serve videos locally
51 96
 if (!serverSettings.s3Bucket) {
52 97
   app.use("/video/", express.static(path.join(serverSettings.storagePath, "video")));

二进制
settings/backgrounds/nyp.png 查看文件


+ 2 - 0
settings/index.js 查看文件

@@ -21,6 +21,8 @@ var path = require("path");
21 21
 module.exports = {
22 22
   workingDirectory: path.join(__dirname, "..", "tmp"),
23 23
   storagePath: path.join(__dirname, "..", "media"),
24
+  themeStoragePath: path.join(__dirname, "backgrounds"),
25
+  settingsPath: path.join(__dirname, "..", "settings"),
24 26
   fonts: [
25 27
     { family: "Source Sans Pro", file: path.join(__dirname, "fonts", "SourceSansPro-Regular.ttf") },
26 28
     { family: "Source Sans Pro", file: path.join(__dirname, "fonts", "SourceSansPro-Light.ttf"), weight: 300 },

文件差异内容过多而无法显示
+ 1 - 105
settings/themes.json


+ 3 - 2
test/frame-test.js 查看文件

@@ -71,7 +71,7 @@ tape.test("Square frame", tester({
71 71
 
72 72
 function checkFrame(test, options) {
73 73
 
74
-  var testCanvas = new Canvas(options.width, options.height),
74
+  var testCanvas = Canvas.createCanvas(options.width, options.height),
75 75
       context = testCanvas.getContext("2d");
76 76
 
77 77
   d3.queue()
@@ -81,7 +81,8 @@ function checkFrame(test, options) {
81 81
 
82 82
       test.error(e);
83 83
 
84
-      var img = new Canvas.Image;
84
+      const canvas = Canvas.createCanvas();
85
+      var img = canvas.Image;
85 86
       img.src = f1;
86 87
 
87 88
       var bg = getColor(options.backgroundColor || "#fff"),