diff --git a/app/.meteor/packages b/app/.meteor/packages
index 21c346df..ef2e1a5b 100644
--- a/app/.meteor/packages
+++ b/app/.meteor/packages
@@ -51,4 +51,3 @@ static-html
aldeed:collection2@3.0.0
aldeed:schema-index
akryum:vue-component
-autopublish@1.0.7
diff --git a/app/.meteor/versions b/app/.meteor/versions
index 0037252f..17878f35 100644
--- a/app/.meteor/versions
+++ b/app/.meteor/versions
@@ -12,7 +12,6 @@ akryum:vue-router2@0.2.2
aldeed:collection2@3.0.1
aldeed:schema-index@3.0.0
allow-deny@1.1.0
-autopublish@1.0.7
autoupdate@1.5.0
babel-compiler@7.2.4
babel-runtime@1.3.0
diff --git a/app/imports/api/icons/Icons.js b/app/imports/api/icons/Icons.js
new file mode 100644
index 00000000..5d21b44c
--- /dev/null
+++ b/app/imports/api/icons/Icons.js
@@ -0,0 +1,50 @@
+import SimpleSchema from 'simpl-schema';
+
+let Icons = new Mongo.Collection('icons');
+
+iconsSchema = new SimpleSchema({
+ name: {
+ type: String,
+ unique: true,
+ index: 1,
+ },
+ description: {
+ type: String,
+ optional: true,
+ },
+ tags: {
+ type: Array,
+ optional: true,
+ index: 1,
+ },
+ 'tags.$': {
+ type: String,
+ },
+ shape: {
+ type: String,
+ },
+});
+
+if (Meteor.isServer) {
+ Icons._ensureIndex({
+ 'name': 'text',
+ 'description': 'text',
+ 'tags': 'text',
+ });
+}
+
+Icons.attachSchema(iconsSchema);
+
+const writeIcons = new ValidatedMethod({
+ name: 'writeIcons',
+ validate: null,
+ run(icons){
+ if (Meteor.isServer){
+ this.unblock();
+ Icons.rawCollection().insert(icons, {ordered: false});
+ }
+ }
+});
+
+export { writeIcons };
+export default Icons;
diff --git a/app/imports/server/publications/icons.js b/app/imports/server/publications/icons.js
new file mode 100644
index 00000000..49f13418
--- /dev/null
+++ b/app/imports/server/publications/icons.js
@@ -0,0 +1,26 @@
+import Icons from '/imports/api/icons/Icons.js';
+
+Meteor.publish("sampleIcons", function(){
+ return Icons.find({}, {limit: 50});
+});
+
+
+Meteor.publish("searchIcons", function(searchValue) {
+ // Don't publish anything if there's no search value
+ if (!searchValue) {
+ return [];
+ }
+ return Icons.find(
+ { $text: {$search: searchValue} },
+ {
+ // relevant documents have a higher score.
+ fields: {
+ score: { $meta: "textScore" }
+ },
+ // `score` property specified in the projection fields above.
+ sort: {
+ score: { $meta: "textScore" }
+ }
+ }
+ );
+});
diff --git a/app/imports/server/publications/index.js b/app/imports/server/publications/index.js
index e0e0d145..4271f09f 100644
--- a/app/imports/server/publications/index.js
+++ b/app/imports/server/publications/index.js
@@ -1,5 +1,6 @@
-import "./characterList.js";
-import "./library.js";
-import "./singleCharacter.js";
-import "./user.js";
-import "./users.js";
+import './characterList.js';
+import './library.js';
+import './singleCharacter.js';
+import './user.js';
+import './users.js';
+import './icons.js';
diff --git a/app/imports/ui/StoryBook.vue b/app/imports/ui/StoryBook.vue
index a253731e..52763670 100644
--- a/app/imports/ui/StoryBook.vue
+++ b/app/imports/ui/StoryBook.vue
@@ -44,8 +44,10 @@
import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
import HealthBar from '/imports/ui/components/HealthBar.Story.vue';
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
+ import IconSearch from '/imports/ui/components/IconSearch.Story.vue';
import SkillListTile from '/imports/ui/components/SkillListTile.Story.vue';
import ToolbarLayout from '/imports/ui/layouts/ToolbarLayout.vue';
+
export default {
components: {
AbilityListTile,
@@ -54,6 +56,7 @@
DialogStack,
HealthBar,
HitDiceListTile,
+ IconSearch,
SkillListTile,
ToolbarLayout,
},
diff --git a/app/imports/ui/components/IconSearch.Story.vue b/app/imports/ui/components/IconSearch.Story.vue
new file mode 100644
index 00000000..f3393267
--- /dev/null
+++ b/app/imports/ui/components/IconSearch.Story.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/app/imports/ui/components/IconSearch.vue b/app/imports/ui/components/IconSearch.vue
new file mode 100644
index 00000000..dd0e517e
--- /dev/null
+++ b/app/imports/ui/components/IconSearch.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/imports/ui/icons/IconAdmin.vue b/app/imports/ui/icons/IconAdmin.vue
new file mode 100644
index 00000000..84d07d9d
--- /dev/null
+++ b/app/imports/ui/icons/IconAdmin.vue
@@ -0,0 +1,83 @@
+
+
+
+ DiceCloud
+
+
+
+
+
+
+
+
+
+
+
+ {{icon.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/imports/ui/icons/importIcons.js b/app/imports/ui/icons/importIcons.js
new file mode 100644
index 00000000..ae814f12
--- /dev/null
+++ b/app/imports/ui/icons/importIcons.js
@@ -0,0 +1,31 @@
+import { writeIcons } from '/imports/api/icons/Icons.js';
+
+/*
+ * Import a SVG sprite file. All the icons must contain one id and one path with a
+ * single 'd' attribute.
+ *
+ * A svg sprite file can be created by downloading the entire archive of
+ * https://game-icons.net/ then using the search function with *.svg to copy
+ * all the individual files into a single directory, and then using the npm
+ * sprite-generator to run `svg-sprite-generate -d icons -o sprite.svg` to save
+ * the sprite file.
+*/
+
+export default function importIcons(file){
+ let id, d, icons = [];
+ let reader = new FileReader();
+
+ reader.onload = function(){
+ reader.result.match(/i?d="([^"])+"/gi).forEach(s => {
+ if (s[0] === 'i'){
+ id = s.slice(4, -1);
+ } else if (s[0] === 'd'){
+ d = s.slice(3, -1);
+ icons.push ({_id: Random.id(), name: id, shape: d});
+ }
+ });
+ writeIcons.call(icons);
+ };
+
+ reader.readAsText(file);
+};
diff --git a/app/imports/ui/router.js b/app/imports/ui/router.js
index 82dd19a7..682978ce 100644
--- a/app/imports/ui/router.js
+++ b/app/imports/ui/router.js
@@ -49,6 +49,7 @@ RouterFactory.configure(factory => {
//Development routes
if (Meteor.isDevelopment){
let StoryBook = require('/imports/ui/StoryBook.vue').default;
+ let IconAdmin = require('/imports/ui/icons/IconAdmin.vue').default;
factory.addRoutes([
{
path: '/storybook/:component',
@@ -58,6 +59,10 @@ RouterFactory.configure(factory => {
path: '/storybook',
name: 'storybook',
component: StoryBook,
+ }, {
+ path: '/icon-admin',
+ name: "iconAdmin",
+ component: IconAdmin,
},
]);
}
diff --git a/app/package-lock.json b/app/package-lock.json
index 6c9fe581..c0c92620 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -2149,6 +2149,11 @@
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-1.4.2.tgz",
"integrity": "sha512-HnMBBtO5FqTPzr6FXVlxnYulGMrHVlcmBL7SSLE/AQnNbdUpT6Jw9D/Kdw9fbK5mdn7JSWMcfWpPJUrqYTtK1w=="
},
+ "vuetify-upload-button": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/vuetify-upload-button/-/vuetify-upload-button-1.2.2.tgz",
+ "integrity": "sha512-WQmAevQl4F2musSLpPzyT3fCBCksFiL7V6Jh8qD1fxGxHjAvjGywJKWjPLMzXDHkJfo6q2OPJtynPU0QXN5hGA=="
+ },
"vuex": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz",
diff --git a/app/package.json b/app/package.json
index a98ee859..9801f6ca 100644
--- a/app/package.json
+++ b/app/package.json
@@ -29,6 +29,7 @@
"vue-meteor-tracker": "^2.0.0-beta.5",
"vue-router": "^3.0.2",
"vuetify": "^1.4.2",
+ "vuetify-upload-button": "^1.2.2",
"vuex": "^3.0.1"
},
"devDependencies": {}