Browse Source

Refactor projects page to single-file component

Currently Django and Vue are somewhat coupled in that the project
intermixes templating constructs from both eco-systems.

Examples of this in `projects.html` include:
- Checking if the user is a superuser to modify the Vue template.
- Using Django filters to inject the capitalized username.

This makes it somewhat hard to follow the Vue code and also makes it
more difficult to modularize the Vue components and re-use them.

As such, this change improves the separation of concerns by having the
Django template only scafold the root element for Vue and serialize any
variables that are required from the Django context to Javascript so
that Vue can access them. The rest of the templating is then moved into
a Vue single file component. This will make it easier for the developer
to understand the Vue components and make it very clear what is rendered
by Django and what is rendered by Vue.
pull/148/head
Clemens Wolff 6 years ago
parent
commit
14f4949e36
6 changed files with 775 additions and 256 deletions
  1. 479
      app/server/package-lock.json
  2. 2
      app/server/package.json
  3. 1
      app/server/static/js/.eslintrc.js
  4. 119
      app/server/static/js/projects.js
  5. 281
      app/server/static/js/projects.vue
  6. 149
      app/server/templates/projects.html

479
app/server/package-lock.json

@ -1260,6 +1260,130 @@
"randomfill": "1.0.4"
}
},
"css-loader": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-2.1.1.tgz",
"integrity": "sha512-OcKJU/lt232vl1P9EEDamhoO9iKY3tIjY5GU+XDLblAykTdgs6Ux9P1hTHve8nFKy5KPpOXOsVI/hIwi3841+w==",
"dev": true,
"requires": {
"camelcase": "5.2.0",
"icss-utils": "4.1.0",
"loader-utils": "1.2.3",
"normalize-path": "3.0.0",
"postcss": "7.0.14",
"postcss-modules-extract-imports": "2.0.0",
"postcss-modules-local-by-default": "2.0.6",
"postcss-modules-scope": "2.1.0",
"postcss-modules-values": "2.0.0",
"postcss-value-parser": "3.3.1",
"schema-utils": "1.0.0"
},
"dependencies": {
"big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"dev": true
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"json5": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"dev": true,
"requires": {
"minimist": "1.2.0"
}
},
"loader-utils": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz",
"integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==",
"dev": true,
"requires": {
"big.js": "5.2.2",
"emojis-list": "2.1.0",
"json5": "1.0.1"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"schema-utils": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"dev": true,
"requires": {
"ajv": "6.5.1",
"ajv-errors": "1.0.1",
"ajv-keywords": "3.2.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true
},
"cyclist": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
@ -1272,6 +1396,12 @@
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
"dev": true
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@ -3146,6 +3276,12 @@
"minimalistic-assert": "1.0.1"
}
},
"he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -3260,6 +3396,71 @@
"safer-buffer": "2.1.2"
}
},
"icss-replace-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz",
"integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=",
"dev": true
},
"icss-utils": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.0.tgz",
"integrity": "sha512-3DEun4VOeMvSczifM3F2cKQrDQ5Pj6WKhkOq6HD4QTnDUAq8MQRxy5TX6Sy1iY6WPBe4gQ3p5vTECjbIkglkkQ==",
"dev": true,
"requires": {
"postcss": "7.0.14"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"ieee754": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
@ -4711,6 +4912,268 @@
}
}
},
"postcss-modules-extract-imports": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz",
"integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==",
"dev": true,
"requires": {
"postcss": "7.0.14"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss-modules-local-by-default": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-2.0.6.tgz",
"integrity": "sha512-oLUV5YNkeIBa0yQl7EYnxMgy4N6noxmiwZStaEJUSe2xPMcdNc8WmBQuQCx18H5psYbVxz8zoHk0RAAYZXP9gA==",
"dev": true,
"requires": {
"postcss": "7.0.14",
"postcss-selector-parser": "6.0.2",
"postcss-value-parser": "3.3.1"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"postcss-selector-parser": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
"integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
"dev": true,
"requires": {
"cssesc": "3.0.0",
"indexes-of": "1.0.1",
"uniq": "1.0.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss-modules-scope": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz",
"integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==",
"dev": true,
"requires": {
"postcss": "7.0.14",
"postcss-selector-parser": "6.0.2"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"postcss-selector-parser": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz",
"integrity": "sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==",
"dev": true,
"requires": {
"cssesc": "3.0.0",
"indexes-of": "1.0.1",
"uniq": "1.0.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss-modules-values": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-2.0.0.tgz",
"integrity": "sha512-Ki7JZa7ff1N3EIMlPnGTZfUMe69FFwiQPnVSXC9mnn3jozCRBYIxiZd44yJOV2AmabOo4qFf8s0dC/+lweG7+w==",
"dev": true,
"requires": {
"icss-replace-symbols": "1.1.0",
"postcss": "7.0.14"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.5.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.14.tgz",
"integrity": "sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==",
"dev": true,
"requires": {
"chalk": "2.4.2",
"source-map": "0.6.1",
"supports-color": "6.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"postcss-selector-parser": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz",
@ -4721,6 +5184,12 @@
"uniq": "1.0.1"
}
},
"postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -6238,6 +6707,16 @@
"loader-utils": "1.1.0"
}
},
"vue-template-compiler": {
"version": "2.5.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.16.tgz",
"integrity": "sha512-ZbuhCcF/hTYmldoUOVcu2fcbeSAZnfzwDskGduOrnjBiIWHgELAd+R8nAtX80aZkceWDKGQ6N9/0/EUpt+l22A==",
"dev": true,
"requires": {
"de-indent": "1.0.2",
"he": "1.2.0"
}
},
"vue-template-es2015-compiler": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz",

2
app/server/package.json

@ -22,9 +22,11 @@
},
"devDependencies": {
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"eslint": "^5.3.0",
"eslint-config-airbnb-base": "^13.0.0",
"eslint-plugin-import": "^2.13.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.12.0",
"webpack-bundle-tracker": "^0.4.2-beta",
"webpack-cli": "^3.2.3",

1
app/server/static/js/.eslintrc.js

@ -11,6 +11,7 @@ module.exports = {
"airbnb-base",
],
rules: {
"no-new": "off",
"no-param-reassign": "off",
"no-plusplus": "off",
"object-shorthand": "off",

119
app/server/static/js/projects.js

@ -1,120 +1,17 @@
import Vue from 'vue';
import axios from 'axios';
import Projects from './projects.vue';
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
const baseUrl = window.location.href.split('/').slice(0, 3).join('/');
const vm = new Vue({ // eslint-disable-line no-unused-vars
new Vue({
el: '#projects_root',
delimiters: ['[[', ']]'],
data: {
items: [],
isActive: false,
isDelete: false,
project: null,
selected: 'All Project',
projectName: '',
description: '',
projectType: '',
descriptionError: '',
projectTypeError: '',
projectNameError: '',
},
methods: {
deleteProject() {
axios.delete(`${baseUrl}/v1/projects/${this.project.id}`).then(() => {
this.isDelete = false;
const index = this.items.indexOf(this.project);
this.items.splice(index, 1);
});
},
setProject(project) {
this.project = project;
this.isDelete = true;
},
matchType(projectType) {
if (projectType === 'DocumentClassification') {
return this.selected === 'Text Classification';
}
if (projectType === 'SequenceLabeling') {
return this.selected === 'Sequence Labeling';
}
if (projectType === 'Seq2seq') {
return this.selected === 'Seq2seq';
}
return false;
},
getDaysAgo(dateStr) {
const updatedAt = new Date(dateStr);
const currentTm = new Date();
components: { Projects },
// difference between days(ms)
const msDiff = currentTm.getTime() - updatedAt.getTime();
// convert daysDiff(ms) to daysDiff(day)
const daysDiff = Math.floor(msDiff / (1000 * 60 * 60 * 24));
return daysDiff;
},
create() {
const payload = {
name: this.projectName,
description: this.description,
project_type: this.projectType,
guideline: 'Please write annotation guideline.',
resourcetype: this.resourceType(),
};
axios.post(`${baseUrl}/v1/projects`, payload)
.then((response) => {
window.location = `${baseUrl}/projects/${response.data.id}/docs/create`;
})
.catch((error) => {
this.projectTypeError = '';
this.projectNameError = '';
this.descriptionError = '';
if ('resourcetype' in error.response.data) {
this.projectTypeError = error.response.data.resourcetype;
}
if ('name' in error.response.data) {
this.projectNameError = error.response.data.name[0];
}
if ('description' in error.response.data) {
this.descriptionError = error.response.data.description[0];
}
});
},
resourceType() {
if (this.projectType === 'DocumentClassification') {
return 'TextClassificationProject';
}
if (this.projectType === 'SequenceLabeling') {
return 'SequenceLabelingProject';
}
if (this.projectType === 'Seq2seq') {
return 'Seq2seqProject';
}
return '';
},
},
computed: {
selectedProjects() {
return this.items.filter(item => this.selected === 'All Project' || this.matchType(item.project_type));
data: {
djangoContext: {
username: JSON.parse(document.getElementById('django.username').textContent),
isSuperuser: JSON.parse(document.getElementById('django.is_superuser').textContent),
},
},
created() {
axios.get(`${baseUrl}/v1/projects`).then((response) => {
this.items = response.data;
});
},
template: '<Projects v-bind="djangoContext" />',
});

281
app/server/static/js/projects.vue

@ -0,0 +1,281 @@
<template>
<div v-cloak>
<section class="hero project-image">
<div class="container">
<div class="columns">
<div class="column is-10 is-offset-1">
<h1 class="title is-1 has-text-white">
Hello, {{ username | title }}.
</h1>
<h2 class="subtitle is-4 has-text-white">
I hope you are having a great day!
</h2>
<p v-if="isSuperuser">
<a class="button is-medium is-primary" @click="isActive=!isActive">
Create Project
</a>
</p>
</div>
</div>
</div>
</section>
<!-- Modal card for creating project. -->
<div class="modal" :class="{ 'is-active': isActive }">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Create Project</p>
<button class="delete" aria-label="close" @click="isActive=!isActive"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Project Name</label>
<div class="control">
<input class="input" type="text" required placeholder="Project name" v-model="projectName">
</div>
<p class="help is-danger">{{ projectNameError }}</p>
</div>
<div class="field">
<label class="label">Description</label>
<div class="control">
<textarea class="textarea" required placeholder="Project description" v-model="description"></textarea>
</div>
<p class="help is-danger">{{ descriptionError }}</p>
</div>
<div class="field">
<label class="label">Project Type</label>
<div class="control">
<select name="project_type" required v-model='projectType'>
<option value="" selected="selected">---------</option>
<option value="DocumentClassification">document classification</option>
<option value="SequenceLabeling">sequence labeling</option>
<option value="Seq2seq">sequence to sequence</option>
</select>
</div>
<p class="help is-danger">{{ projectTypeError }}</p>
</div>
</section>
<footer class="modal-card-foot pt20 pb20 pr20 pl20 has-background-white-ter">
<button class="button is-primary" @click="create()">Create</button>
<button class="button" @click="isActive=!isActive">Cancel</button>
</footer>
</div>
</div>
<div class="modal" :class="{ 'is-active': isDelete }">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Delete Project</p>
<button class="delete" aria-label="close" @click="isDelete=!isDelete"></button>
</header>
<section class="modal-card-body">
Are you sure you want to delete project?
</section>
<footer class="modal-card-foot pt20 pb20 pr20 pl20 has-background-white-ter">
<button class="button is-danger" @click="deleteProject()">Delete</button>
<button class="button" @click="isDelete=!isDelete">Cancel</button>
</footer>
</div>
</div>
<section class="hero">
<div class="container">
<div class="columns">
<div class="column is-10 is-offset-1">
<div class="card events-card">
<header class="card-header">
<p class="card-header-title">
{{ items.length }} Projects
</p>
<div class="field card-header-icon">
<div class="control">
<div class="select">
<select v-model="selected">
<option selected>All Project</option>
<option>Text Classification</option>
<option>Sequence Labeling</option>
<option>Seq2seq</option>
</select>
</div>
</div>
</div>
</header>
<div class="card-table">
<div class="content">
<table class="table is-fullwidth">
<tbody>
<tr v-for="project in selectedProjects" :key="project.id">
<td class="pl15r">
<div class="thumbnail-wrapper is-vertical">
<img class="project-thumbnail" :src="project.image">
</div>
<div class="dataset-item__main is-vertical">
<div class="dataset-item__main-title">
<div class="dataset-item__main-title-link dataset-item__link">
<a :href="'/projects/' + project.id" class="has-text-black">{{ project.name }}</a>
</div>
</div>
<div class="dataset-item__main-subtitle">
{{ project.description }}
</div>
<div class="dataset-item__main-info">
<span class="dataset-item__main-update">updated <span>{{ project.updated_at | daysAgo }}</span></span></div>
</div>
</td>
<td class="is-vertical"><span class="tag is-normal">{{ project.project_type }}</span></td>
<td v-if="isSuperuser" class="is-vertical"><a :href="'/projects/' + project.id + '/docs'">Edit</a></td>
<td v-if="isSuperuser" class="is-vertical"><a class="has-text-danger" @click="setProject(project)">Delete</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</template>
<script>
import axios from 'axios';
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
const baseUrl = window.location.href.split('/').slice(0, 3).join('/');
export default {
props: {
username: {
type: String,
required: true,
},
isSuperuser: {
type: Boolean,
default: false,
},
},
filters: {
title: (value) => {
const string = (value || '').toString();
return string.charAt(0).toUpperCase() + string.slice(1);
},
daysAgo: (dateStr) => {
const updatedAt = new Date(dateStr);
const currentTm = new Date();
// difference between days(ms)
const msDiff = currentTm.getTime() - updatedAt.getTime();
// convert daysDiff(ms) to daysDiff(day)
const daysDiff = Math.floor(msDiff / (1000 * 60 * 60 * 24));
return daysDiff === 1
? `${daysDiff} day ago`
: `${daysDiff} days ago`;
},
},
data: () => ({
items: [],
isActive: false,
isDelete: false,
project: null,
selected: 'All Project',
projectName: '',
description: '',
projectType: '',
descriptionError: '',
projectTypeError: '',
projectNameError: '',
}),
methods: {
deleteProject() {
axios.delete(`${baseUrl}/v1/projects/${this.project.id}`).then(() => {
this.isDelete = false;
const index = this.items.indexOf(this.project);
this.items.splice(index, 1);
});
},
setProject(project) {
this.project = project;
this.isDelete = true;
},
matchType(projectType) {
if (projectType === 'DocumentClassification') {
return this.selected === 'Text Classification';
}
if (projectType === 'SequenceLabeling') {
return this.selected === 'Sequence Labeling';
}
if (projectType === 'Seq2seq') {
return this.selected === 'Seq2seq';
}
return false;
},
create() {
const payload = {
name: this.projectName,
description: this.description,
project_type: this.projectType,
guideline: 'Please write annotation guideline.',
resourcetype: this.resourceType(),
};
axios.post(`${baseUrl}/v1/projects`, payload)
.then((response) => {
window.location = `${baseUrl}/projects/${response.data.id}/docs/create`;
})
.catch((error) => {
this.projectTypeError = '';
this.projectNameError = '';
this.descriptionError = '';
if ('resourcetype' in error.response.data) {
this.projectTypeError = error.response.data.resourcetype;
}
if ('name' in error.response.data) {
this.projectNameError = error.response.data.name[0];
}
if ('description' in error.response.data) {
this.descriptionError = error.response.data.description[0];
}
});
},
resourceType() {
if (this.projectType === 'DocumentClassification') {
return 'TextClassificationProject';
}
if (this.projectType === 'SequenceLabeling') {
return 'SequenceLabelingProject';
}
if (this.projectType === 'Seq2seq') {
return 'Seq2seqProject';
}
return '';
},
},
computed: {
selectedProjects() {
return this.items.filter(item => this.selected === 'All Project' || this.matchType(item.project_type));
},
},
created() {
axios.get(`${baseUrl}/v1/projects`).then((response) => {
this.items = response.data;
});
},
};
</script>

149
app/server/templates/projects.html

@ -3,151 +3,10 @@
{% load render_bundle from webpack_loader %}
{% load widget_tweaks %}
{% block content %}
<div id="projects_root" v-cloak>
<section class="hero project-image">
<div class="container">
<div class="columns">
<div class="column is-10 is-offset-1">
<h1 class="title is-1 has-text-white">
Hello, {{ user.get_username | title }}.
</h1>
<h2 class="subtitle is-4 has-text-white">
I hope you are having a great day!
</h2>
{% if user.is_superuser %}
<p>
<a class="button is-medium is-primary" @click="isActive=!isActive">
Create Project
</a>
</p>
{% endif %}
</div>
</div>
</div>
</section>
<!-- Modal card for creating project. -->
<div class="modal" :class="{ 'is-active': isActive }">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Create Project</p>
<button class="delete" aria-label="close" @click="isActive=!isActive"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Project Name</label>
<div class="control">
<input class="input" type="text" required placeholder="Project name" v-model="projectName">
</div>
<p class="help is-danger">[[ projectNameError ]]</p>
</div>
<div class="field">
<label class="label">Description</label>
<div class="control">
<textarea class="textarea" required placeholder="Project description" v-model="description"></textarea>
</div>
<p class="help is-danger">[[ descriptionError ]]</p>
</div>
<div class="field">
<label class="label">Project Type</label>
<div class="control">
<select name="project_type" required v-model='projectType'>
<option value="" selected="selected">---------</option>
<option value="DocumentClassification">document classification</option>
<option value="SequenceLabeling">sequence labeling</option>
<option value="Seq2seq">sequence to sequence</option>
</select>
</div>
<p class="help is-danger">[[ projectTypeError ]]</p>
</div>
</section>
<footer class="modal-card-foot pt20 pb20 pr20 pl20 has-background-white-ter">
<button class="button is-primary" @click="create()">Create</button>
<button class="button" @click="isActive=!isActive">Cancel</button>
</footer>
</div>
</div>
<div class="modal" :class="{ 'is-active': isDelete }">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Delete Project</p>
<button class="delete" aria-label="close" @click="isDelete=!isDelete"></button>
</header>
<section class="modal-card-body">
Are you sure you want to delete project?
</section>
<footer class="modal-card-foot pt20 pb20 pr20 pl20 has-background-white-ter">
<button class="button is-danger" @click="deleteProject()">Delete</button>
<button class="button" @click="isDelete=!isDelete">Cancel</button>
</footer>
</div>
</div>
<section class="hero">
<div class="container">
<div class="columns">
<div class="column is-10 is-offset-1">
<div class="card events-card">
<header class="card-header">
<p class="card-header-title">
[[ items.length ]] Projects
</p>
<div class="field card-header-icon">
<div class="control">
<div class="select">
<select v-model="selected">
<option selected>All Project</option>
<option>Text Classification</option>
<option>Sequence Labeling</option>
<option>Seq2seq</option>
</select>
</div>
</div>
</div>
</header>
<div class="card-table">
<div class="content">
<table class="table is-fullwidth">
<tbody>
<tr v-for="project in selectedProjects">
<td class="pl15r">
<div class="thumbnail-wrapper is-vertical">
<img class="project-thumbnail" v-bind:src="project.image">
</div>
<div class="dataset-item__main is-vertical">
<div class="dataset-item__main-title">
<div class="dataset-item__main-title-link dataset-item__link">
<a v-bind:href="'/projects/' + project.id" class="has-text-black">[[ project.name ]]</a>
</div>
</div>
<div class="dataset-item__main-subtitle">
[[ project.description ]]
</div>
<div class="dataset-item__main-info">
<span class="dataset-item__main-update">updated <span>[[ getDaysAgo(project.updated_at) ]] days ago</span></span></div>
</div>
</td>
<td class="is-vertical"><span class="tag is-normal">[[ project.project_type ]]</span></td>
{% if user.is_superuser %}
<td class="is-vertical"><a v-bind:href="'/projects/' + project.id + '/docs'">Edit</a></td>
<td class="is-vertical"><a class="has-text-danger" @click="setProject(project)">Delete</a></td>
{% endif %}
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
<div id="projects_root"></div>
{% endblock %}
{% block footer %}
{{ user.get_username | json_script:"django.username" }}
{{ user.is_superuser | json_script:"django.is_superuser" }}
{% render_bundle 'projects' 'js' %}
{% endblock %}
{% endblock %}
Loading…
Cancel
Save