update playlist drag and drop
1249
dashboard-ui/bower_components/Sortable/Sortable.js
vendored
@ -1,34 +0,0 @@
|
||||
base64@1.0.3
|
||||
binary-heap@1.0.3
|
||||
blaze@2.1.2
|
||||
blaze-tools@1.0.3
|
||||
callback-hook@1.0.3
|
||||
check@1.0.5
|
||||
dburles:mongo-collection-instances@0.3.4
|
||||
ddp@1.1.0
|
||||
deps@1.0.7
|
||||
ejson@1.0.6
|
||||
geojson-utils@1.0.3
|
||||
html-tools@1.0.4
|
||||
htmljs@1.0.4
|
||||
id-map@1.0.3
|
||||
jquery@1.11.3_2
|
||||
json@1.0.3
|
||||
lai:collection-extensions@0.1.4
|
||||
local-test:rubaxa:sortable@1.2.1
|
||||
logging@1.0.7
|
||||
meteor@1.1.6
|
||||
minifiers@1.1.5
|
||||
minimongo@1.0.8
|
||||
mongo@1.1.0
|
||||
observe-sequence@1.0.6
|
||||
ordered-dict@1.0.3
|
||||
random@1.0.3
|
||||
reactive-var@1.0.5
|
||||
retry@1.0.3
|
||||
rubaxa:sortable@1.2.1
|
||||
spacebars-compiler@1.0.6
|
||||
templating@1.1.1
|
||||
tinytest@1.0.5
|
||||
tracker@1.0.7
|
||||
underscore@1.0.3
|
@ -1,8 +0,0 @@
|
||||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
notices-for-facebook-graph-api-2
|
@ -1 +0,0 @@
|
||||
local
|
@ -1,7 +0,0 @@
|
||||
# This file contains a token that is unique to your project.
|
||||
# Check it into your repository along with the rest of this directory.
|
||||
# It can be used for purposes such as:
|
||||
# - ensuring you don't accidentally deploy one app on top of another
|
||||
# - providing package authors with aggregated statistics
|
||||
|
||||
ir0jg2douy3yo5mehw
|
@ -1,10 +0,0 @@
|
||||
# Meteor packages used by this project, one per line.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-platform
|
||||
autopublish
|
||||
insecure
|
||||
rubaxa:sortable
|
||||
fezvrasta:bootstrap-material-design
|
@ -1,2 +0,0 @@
|
||||
browser
|
||||
server
|
@ -1 +0,0 @@
|
||||
METEOR@1.1.0.3
|
@ -1,53 +0,0 @@
|
||||
autopublish@1.0.3
|
||||
autoupdate@1.2.1
|
||||
base64@1.0.3
|
||||
binary-heap@1.0.3
|
||||
blaze@2.1.2
|
||||
blaze-tools@1.0.3
|
||||
boilerplate-generator@1.0.3
|
||||
callback-hook@1.0.3
|
||||
check@1.0.5
|
||||
dburles:mongo-collection-instances@0.3.4
|
||||
ddp@1.1.0
|
||||
deps@1.0.7
|
||||
ejson@1.0.6
|
||||
fastclick@1.0.3
|
||||
fezvrasta:bootstrap-material-design@0.3.0
|
||||
geojson-utils@1.0.3
|
||||
html-tools@1.0.4
|
||||
htmljs@1.0.4
|
||||
http@1.1.0
|
||||
id-map@1.0.3
|
||||
insecure@1.0.3
|
||||
jquery@1.11.3_2
|
||||
json@1.0.3
|
||||
lai:collection-extensions@0.1.4
|
||||
launch-screen@1.0.2
|
||||
livedata@1.0.13
|
||||
logging@1.0.7
|
||||
meteor@1.1.6
|
||||
meteor-platform@1.2.2
|
||||
minifiers@1.1.5
|
||||
minimongo@1.0.8
|
||||
mobile-status-bar@1.0.3
|
||||
mongo@1.1.0
|
||||
observe-sequence@1.0.6
|
||||
ordered-dict@1.0.3
|
||||
random@1.0.3
|
||||
reactive-dict@1.1.0
|
||||
reactive-var@1.0.5
|
||||
reload@1.1.3
|
||||
retry@1.0.3
|
||||
routepolicy@1.0.5
|
||||
rubaxa:sortable@1.2.1
|
||||
session@1.1.0
|
||||
spacebars@1.0.6
|
||||
spacebars-compiler@1.0.6
|
||||
templating@1.1.1
|
||||
tracker@1.0.7
|
||||
twbs:bootstrap@3.3.5
|
||||
ui@1.0.6
|
||||
underscore@1.0.3
|
||||
url@1.0.4
|
||||
webapp@1.2.0
|
||||
webapp-hashing@1.0.3
|
@ -1,57 +0,0 @@
|
||||
.glyphicon {
|
||||
vertical-align: baseline;
|
||||
font-size: 80%;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
[class^="mdi-"], [class*=" mdi-"] {
|
||||
vertical-align: baseline;
|
||||
font-size: 90%;
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
|
||||
.list-pair {
|
||||
display: flex; /* use the flexbox model */
|
||||
flex-direction: row;
|
||||
}
|
||||
.sortable {
|
||||
/* font-size: 2em;*/
|
||||
}
|
||||
|
||||
.sortable.source {
|
||||
/*background: #9FA8DA;*/
|
||||
flex: 0 0 auto;
|
||||
margin-right: 1em;
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.sortable.target {
|
||||
/*background: #3F51B5;*/
|
||||
flex: 1 1 auto;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.target .well {
|
||||
|
||||
}
|
||||
|
||||
.sortable-handle {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
.sortable-handle.pull-right {
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* show the remove button on hover */
|
||||
.removable .close {
|
||||
display: none;
|
||||
}
|
||||
.removable:hover .close {
|
||||
display: block;
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
<head>
|
||||
<title>Reactive RubaXa:Sortable for Meteor</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{> navbar}}
|
||||
|
||||
<div class="container">
|
||||
<div class="page-header">
|
||||
<h1>RubaXa:Sortable - reactive reorderable lists for Meteor</h1>
|
||||
<h2>Drag attribute types from the left to define an object type on the right</h2>
|
||||
<h3>Drag the <i class="sortable-handle mdi-action-view-headline"></i> handle to reorder elements</h3>
|
||||
</div>
|
||||
|
||||
{{> typeDefinition}}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<template name="typeDefinition">
|
||||
|
||||
<div class="row">
|
||||
<div class="list-pair col-sm-12">
|
||||
|
||||
<div class="sortable source list-group" id="types">
|
||||
{{#sortable items=types options=typesOptions}}
|
||||
<div class="list-group-item well well-sm">
|
||||
{{{icon}}} {{name}}
|
||||
</div>
|
||||
{{/sortable}}
|
||||
</div>
|
||||
|
||||
<div class="sortable target" id="object">
|
||||
{{#sortable items=attributes animation="100" handle=".sortable-handle" ghostClass="sortable-ghost" options=attributesOptions}}
|
||||
{{> sortableItemTarget}}
|
||||
{{/sortable}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template name="sortableItemTarget">
|
||||
<div data-id="{{order}}" class="sortable-item removable well well-sm">
|
||||
{{{icon}}}
|
||||
<i class="sortable-handle mdi-action-view-headline pull-right"></i>
|
||||
<span class="name">{{name}}</span>
|
||||
<span class="badge">{{order}}</span>
|
||||
<button type="button" class="close" data-dismiss="alert">
|
||||
<span aria-hidden="true">×</span><span class="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template name="navbar">
|
||||
<div class="navbar navbar-inverse">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-inverse-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="https://atmospherejs.com/rubaxa/sortable">RubaXa:Sortable</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse navbar-inverse-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="active"><a href="http://rubaxa-sortable.meteor.com">Meteor Demo</a></li>
|
||||
<li><a href="https://rubaxa.github.io/Sortable/" target="_blank">Sortable standalone demo</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">Source <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="dropdown-header">GitHub</li>
|
||||
<li><a href="https://github.com/RubaXa/Sortable/tree/dev/meteor/example" target="_blank">This demo</a></li>
|
||||
<li><a href="https://github.com/RubaXa/Sortable/tree/dev/meteor" target="_blank">Meteor integration</a></li>
|
||||
<li><a href="https://github.com/RubaXa/Sortable" target="_blank">rubaxa/sortable</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="https://atmospherejs.com/rubaxa/sortable">Star this package on Atmosphere!</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="javascript:void(0)" class="dropdown-toggle" data-toggle="dropdown">Resources <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="http://www.meteorpedia.com/read/Packaging_existing_Libraries">Packaging 3rd party libraries for Meteor</a></li>
|
||||
<li><a href="https://twitter.com/dandv">Author: @dandv</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,101 +0,0 @@
|
||||
// Define an object type by dragging together attributes
|
||||
|
||||
Template.typeDefinition.helpers({
|
||||
types: function () {
|
||||
return Types.find({}, { sort: { order: 1 } });
|
||||
},
|
||||
typesOptions: {
|
||||
sortField: 'order', // defaults to 'order' anyway
|
||||
group: {
|
||||
name: 'typeDefinition',
|
||||
pull: 'clone',
|
||||
put: false
|
||||
},
|
||||
sort: false // don't allow reordering the types, just the attributes below
|
||||
},
|
||||
|
||||
attributes: function () {
|
||||
return Attributes.find({}, {
|
||||
sort: { order: 1 },
|
||||
transform: function (doc) {
|
||||
doc.icon = Types.findOne({name: doc.type}).icon;
|
||||
return doc;
|
||||
}
|
||||
});
|
||||
},
|
||||
attributesOptions: {
|
||||
group: {
|
||||
name: 'typeDefinition',
|
||||
put: true
|
||||
},
|
||||
onAdd: function (event) {
|
||||
delete event.data._id; // Generate a new id when inserting in the Attributes collection. Otherwise, if we add the same type twice, we'll get an error that the ids are not unique.
|
||||
delete event.data.icon;
|
||||
event.data.type = event.data.name;
|
||||
event.data.name = 'Rename me (double click)'
|
||||
},
|
||||
// event handler for reordering attributes
|
||||
onSort: function (event) {
|
||||
console.log('Item %s went from #%d to #%d',
|
||||
event.data.name, event.oldIndex, event.newIndex
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Template.sortableItemTarget.events({
|
||||
'dblclick .name': function (event, template) {
|
||||
// Make the name editable. We should use an existing component, but it's
|
||||
// in a sorry state - https://github.com/arillo/meteor-x-editable/issues/1
|
||||
var name = template.$('.name');
|
||||
var input = template.$('input');
|
||||
if (input.length) { // jQuery never returns null - http://stackoverflow.com/questions/920236/how-can-i-detect-if-a-selector-returns-null
|
||||
input.show();
|
||||
} else {
|
||||
input = $('<input class="form-control" type="text" placeholder="' + this.name + '" style="display: inline">');
|
||||
name.after(input);
|
||||
}
|
||||
name.hide();
|
||||
input.focus();
|
||||
},
|
||||
'blur input[type=text]': function (event, template) {
|
||||
// commit the change to the name, if any
|
||||
var input = template.$('input');
|
||||
input.hide();
|
||||
template.$('.name').show();
|
||||
// TODO - what is the collection here? We'll hard-code for now.
|
||||
// https://github.com/meteor/meteor/issues/3303
|
||||
if (this.name !== input.val() && this.name !== '')
|
||||
Attributes.update(this._id, {$set: {name: input.val()}});
|
||||
},
|
||||
'keydown input[type=text]': function (event, template) {
|
||||
if (event.which === 27) {
|
||||
// ESC - discard edits and keep existing value
|
||||
template.$('input').val(this.name);
|
||||
event.preventDefault();
|
||||
event.target.blur();
|
||||
} else if (event.which === 13) {
|
||||
// ENTER
|
||||
event.preventDefault();
|
||||
event.target.blur();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// you can add events to all Sortable template instances
|
||||
Template.sortable.events({
|
||||
'click .close': function (event, template) {
|
||||
// `this` is the data context set by the enclosing block helper (#each, here)
|
||||
template.collection.remove(this._id);
|
||||
// custom code, working on a specific collection
|
||||
if (Attributes.find().count() === 0) {
|
||||
Meteor.setTimeout(function () {
|
||||
Attributes.insert({
|
||||
name: 'Not nice to delete the entire list! Add some attributes instead.',
|
||||
type: 'String',
|
||||
order: 0
|
||||
})
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
@ -1,2 +0,0 @@
|
||||
Types = new Mongo.Collection('types');
|
||||
Attributes = new Mongo.Collection('attributes');
|
@ -1,7 +0,0 @@
|
||||
@echo off
|
||||
REM Sanity check: make sure we're in the directory of the script
|
||||
set DIR=%~dp0
|
||||
cd %DIR%
|
||||
|
||||
set PACKAGE_DIRS=..\..\
|
||||
meteor run %*
|
@ -1,5 +0,0 @@
|
||||
# sanity check: make sure we're in the root directory of the example
|
||||
cd "$( dirname "$0" )"
|
||||
|
||||
# let Meteor find the local package
|
||||
PACKAGE_DIRS=../../ meteor run "$@"
|
@ -1,75 +0,0 @@
|
||||
Meteor.startup(function () {
|
||||
if (Types.find().count() === 0) {
|
||||
[
|
||||
{
|
||||
name: 'String',
|
||||
icon: '<span class="glyphicon glyphicon-tag" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Text, multi-line',
|
||||
icon: '<i class="mdi-communication-message" aria-hidden="true"></i>'
|
||||
},
|
||||
{
|
||||
name: 'Category',
|
||||
icon: '<span class="glyphicon glyphicon-list" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
icon: '<i class="mdi-image-looks-one" aria-hidden="true"></i>'
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
icon: '<span class="glyphicon glyphicon-calendar" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Hyperlink',
|
||||
icon: '<span class="glyphicon glyphicon-link" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Image',
|
||||
icon: '<span class="glyphicon glyphicon-picture" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Progress',
|
||||
icon: '<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Duration',
|
||||
icon: '<span class="glyphicon glyphicon-time" aria-hidden="true"></span>'
|
||||
},
|
||||
{
|
||||
name: 'Map address',
|
||||
icon: '<i class="mdi-maps-place" aria-hidden="true"></i>'
|
||||
},
|
||||
{
|
||||
name: 'Relationship',
|
||||
icon: '<span class="glyphicon glyphicon-flash" aria-hidden="true"></span>'
|
||||
}
|
||||
].forEach(function (type, i) {
|
||||
Types.insert({
|
||||
name: type.name,
|
||||
icon: type.icon,
|
||||
order: i
|
||||
});
|
||||
}
|
||||
);
|
||||
console.log('Initialized attribute types.');
|
||||
}
|
||||
|
||||
if (Attributes.find().count() === 0) {
|
||||
[
|
||||
{ name: 'Name', type: 'String' },
|
||||
{ name: 'Created at', type: 'Date' },
|
||||
{ name: 'Link', type: 'Hyperlink' },
|
||||
{ name: 'Owner', type: 'Relationship' }
|
||||
].forEach(function (attribute, i) {
|
||||
Attributes.insert({
|
||||
name: attribute.name,
|
||||
type: attribute.type,
|
||||
order: i
|
||||
});
|
||||
}
|
||||
);
|
||||
console.log('Created sample object type.');
|
||||
}
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
Sortable.collections = ['attributes'];
|
@ -1,16 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Update the sortField of documents with given ids in a collection, incrementing it by incDec
|
||||
* @param {String} collectionName - name of the collection to update
|
||||
* @param {String[]} ids - array of document ids
|
||||
* @param {String} orderField - the name of the order field, usually "order"
|
||||
* @param {Number} incDec - pass 1 or -1
|
||||
*/
|
||||
'rubaxa:sortable/collection-update': function (collectionName, ids, sortField, incDec) {
|
||||
var selector = {_id: {$in: ids}}, modifier = {$inc: {}};
|
||||
modifier.$inc[sortField] = incDec;
|
||||
Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true});
|
||||
}
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
Sortable = {};
|
||||
Sortable.collections = []; // array of collection names that the client is allowed to reorder
|
||||
|
||||
Meteor.methods({
|
||||
/**
|
||||
* Update the sortField of documents with given ids in a collection, incrementing it by incDec
|
||||
* @param {String} collectionName - name of the collection to update
|
||||
* @param {String[]} ids - array of document ids
|
||||
* @param {String} orderField - the name of the order field, usually "order"
|
||||
* @param {Number} incDec - pass 1 or -1
|
||||
*/
|
||||
'rubaxa:sortable/collection-update': function (collectionName, ids, sortField, incDec) {
|
||||
check(collectionName, String);
|
||||
// don't allow the client to modify just any collection
|
||||
if (!Sortable || !Array.isArray(Sortable.collections)) {
|
||||
throw new Meteor.Error(500, 'Please define Sortable.collections');
|
||||
}
|
||||
if (Sortable.collections.indexOf(collectionName) === -1) {
|
||||
throw new Meteor.Error(403, 'Collection <' + collectionName + '> is not Sortable. Please add it to Sortable.collections in server code.');
|
||||
}
|
||||
|
||||
check(ids, [String]);
|
||||
check(sortField, String);
|
||||
check(incDec, Number);
|
||||
var selector = {_id: {$in: ids}}, modifier = {$inc: {}};
|
||||
modifier.$inc[sortField] = incDec;
|
||||
Mongo.Collection.get(collectionName).update(selector, modifier, {multi: true});
|
||||
}
|
||||
});
|
@ -1,85 +0,0 @@
|
||||
// Package metadata file for Meteor.js
|
||||
'use strict';
|
||||
|
||||
var packageName = 'rubaxa:sortable'; // https://atmospherejs.com/rubaxa/sortable
|
||||
var gitHubPath = 'RubaXa/Sortable'; // https://github.com/RubaXa/Sortable
|
||||
var npmPackageName = 'sortablejs'; // https://www.npmjs.com/package/sortablejs - optional but recommended; used as fallback if GitHub fails
|
||||
|
||||
/* All of the below is just to get the version number of the 3rd party library.
|
||||
* First we'll try to read it from package.json. This works when publishing or testing the package
|
||||
* but not when running an example app that uses a local copy of the package because the current
|
||||
* directory will be that of the app, and it won't have package.json. Finding the path of a file is hard:
|
||||
* http://stackoverflow.com/questions/27435797/how-do-i-obtain-the-path-of-a-file-in-a-meteor-package
|
||||
* Therefore, we'll fall back to GitHub (which is more frequently updated), and then to NPMJS.
|
||||
* We also don't have the HTTP package at this stage, and if we use Package.* in the request() callback,
|
||||
* it will error that it must be run in a Fiber. So we'll use Node futures.
|
||||
*/
|
||||
var request = Npm.require('request');
|
||||
var Future = Npm.require('fibers/future');
|
||||
|
||||
var fut = new Future;
|
||||
var version;
|
||||
|
||||
if (!version) try {
|
||||
var packageJson = JSON.parse(Npm.require('fs').readFileSync('../package.json'));
|
||||
version = packageJson.version;
|
||||
} catch (e) {
|
||||
// if the file was not found, fall back to GitHub
|
||||
console.warn('Could not find ../package.json to read version number from; trying GitHub...');
|
||||
var url = 'https://api.github.com/repos/' + gitHubPath + '/tags';
|
||||
request.get({
|
||||
url: url,
|
||||
headers: {
|
||||
'User-Agent': 'request' // GitHub requires it
|
||||
}
|
||||
}, function (error, response, body) {
|
||||
if (!error && response.statusCode === 200) {
|
||||
var versions = JSON.parse(body).map(function (version) {
|
||||
return version['name'].replace(/^\D+/, '') // trim leading non-digits from e.g. "v4.3.0"
|
||||
}).sort();
|
||||
fut.return(versions[versions.length -1]);
|
||||
} else {
|
||||
// GitHub API rate limit reached? Fall back to npmjs.
|
||||
console.warn('GitHub request to', url, 'failed:\n ', response && response.statusCode, response && response.body, error || '', '\nTrying NPMJS...');
|
||||
url = 'http://registry.npmjs.org/' + npmPackageName + '/latest';
|
||||
request.get(url, function (error, response, body) {
|
||||
if (!error && response.statusCode === 200)
|
||||
fut.return(JSON.parse(body).version);
|
||||
else
|
||||
fut.throw('Could not get version information from ' + url + ' either (incorrect package name?):\n' + (response && response.statusCode || '') + (response && response.body || '') + (error || ''));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
version = fut.wait();
|
||||
}
|
||||
|
||||
// Now that we finally have an accurate version number...
|
||||
Package.describe({
|
||||
name: packageName,
|
||||
summary: 'Sortable: reactive minimalist reorderable drag-and-drop lists on modern browsers and touch devices',
|
||||
version: version,
|
||||
git: 'https://github.com/RubaXa/Sortable.git',
|
||||
documentation: 'README.md'
|
||||
});
|
||||
|
||||
Package.onUse(function (api) {
|
||||
api.versionsFrom(['METEOR@0.9.0', 'METEOR@1.0']);
|
||||
api.use('templating', 'client');
|
||||
api.use('dburles:mongo-collection-instances@0.3.4'); // to watch collections getting created
|
||||
api.export('Sortable'); // exported on the server too, as a global to hold the array of sortable collections (for security)
|
||||
api.addFiles([
|
||||
'../Sortable.js',
|
||||
'template.html', // the HTML comes first, so reactivize.js can refer to the template in it
|
||||
'reactivize.js'
|
||||
], 'client');
|
||||
api.addFiles('methods-client.js', 'client');
|
||||
api.addFiles('methods-server.js', 'server');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.use(packageName, 'client');
|
||||
api.use('tinytest', 'client');
|
||||
|
||||
api.addFiles('test.js', 'client');
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Publish package to Meteor's repository, Atmospherejs.com
|
||||
|
||||
# Make sure Meteor is installed, per https://www.meteor.com/install.
|
||||
# The curl'ed script is totally safe; takes 2 minutes to read its source and check.
|
||||
type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
|
||||
|
||||
# sanity check: make sure we're in the directory of the script
|
||||
cd "$( dirname "$0" )"
|
||||
|
||||
# publish package, creating it if it's the first time we're publishing
|
||||
PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
|
||||
|
||||
echo "Publishing $PACKAGE_NAME..."
|
||||
|
||||
# Attempt to re-publish the package - the most common operation once the initial release has
|
||||
# been made. If the package name was changed (rare), you'll have to pass the --create flag.
|
||||
meteor publish "$@"; EXIT_CODE=$?
|
||||
if (( $EXIT_CODE == 0 )); then
|
||||
echo "Thanks for releasing a new version. You can see it at"
|
||||
echo "https://atmospherejs.com/${PACKAGE_NAME/://}"
|
||||
else
|
||||
echo "We have an error. Please post it at https://github.com/RubaXa/Sortable/issues"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
@ -1,201 +0,0 @@
|
||||
/*
|
||||
Make a Sortable reactive by binding it to a Mongo.Collection.
|
||||
Calls `rubaxa:sortable/collection-update` on the server to update the sortField of affected records.
|
||||
|
||||
TODO:
|
||||
* supply consecutive values if the `order` field doesn't have any
|
||||
* .get(DOMElement) - return the Sortable object of a DOMElement
|
||||
* create a new _id automatically onAdd if the event.from list had pull: 'clone'
|
||||
* support arrays
|
||||
* sparse arrays
|
||||
* tests
|
||||
* drop onto existing empty lists
|
||||
* insert back into lists emptied by dropping
|
||||
* performance on dragging into long list at the beginning
|
||||
* handle failures on Collection operations, e.g. add callback to .insert
|
||||
* when adding elements, update ranks just for the half closer to the start/end of the list
|
||||
* revisit http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
|
||||
* reproduce the insidious bug where the list isn't always sorted (fiddle with dragging #1 over #2, then back, then #N before #1)
|
||||
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
Template.sortable.created = function () {
|
||||
var templateInstance = this;
|
||||
// `this` is a template instance that can store properties of our choice - http://docs.meteor.com/#/full/template_inst
|
||||
if (templateInstance.setupDone) return; // paranoid: only run setup once
|
||||
// this.data is the data context - http://docs.meteor.com/#/full/template_data
|
||||
// normalize all options into templateInstance.options, and remove them from .data
|
||||
templateInstance.options = templateInstance.data.options || {};
|
||||
Object.keys(templateInstance.data).forEach(function (key) {
|
||||
if (key === 'options' || key === 'items') return;
|
||||
templateInstance.options[key] = templateInstance.data[key];
|
||||
delete templateInstance.data[key];
|
||||
});
|
||||
templateInstance.options.sortField = templateInstance.options.sortField || 'order';
|
||||
// We can get the collection via the .collection property of the cursor, but changes made that way
|
||||
// will NOT be sent to the server - https://github.com/meteor/meteor/issues/3271#issuecomment-66656257
|
||||
// Thus we need to use dburles:mongo-collection-instances to get a *real* collection
|
||||
if (templateInstance.data.items && templateInstance.data.items.collection) {
|
||||
// cursor passed via items=; its .collection works client-only and has a .name property
|
||||
templateInstance.collectionName = templateInstance.data.items.collection.name;
|
||||
templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
|
||||
} else if (templateInstance.data.items) {
|
||||
// collection passed via items=; does NOT have a .name property, but _name
|
||||
templateInstance.collection = templateInstance.data.items;
|
||||
templateInstance.collectionName = templateInstance.collection._name;
|
||||
} else if (templateInstance.data.collection) {
|
||||
// cursor passed directly
|
||||
templateInstance.collectionName = templateInstance.data.collection.name;
|
||||
templateInstance.collection = Mongo.Collection.get(templateInstance.collectionName);
|
||||
} else {
|
||||
templateInstance.collection = templateInstance.data; // collection passed directly
|
||||
templateInstance.collectionName = templateInstance.collection._name;
|
||||
}
|
||||
|
||||
// TODO if (Array.isArray(templateInstance.collection))
|
||||
|
||||
// What if user filters some of the items in the cursor, instead of ordering the entire collection?
|
||||
// Use case: reorder by preference movies of a given genre, a filter within all movies.
|
||||
// A: Modify all intervening items **that are on the client**, to preserve the overall order
|
||||
// TODO: update *all* orders via a server method that takes not ids, but start & end elements - mild security risk
|
||||
delete templateInstance.data.options;
|
||||
|
||||
/**
|
||||
* When an element was moved, adjust its orders and possibly the order of
|
||||
* other elements, so as to maintain a consistent and correct order.
|
||||
*
|
||||
* There are three approaches to this:
|
||||
* 1) Using arbitrary precision arithmetic and setting only the order of the moved
|
||||
* element to the average of the orders of the elements around it -
|
||||
* http://programmers.stackexchange.com/questions/266451/maintain-ordered-collection-by-updating-as-few-order-fields-as-possible
|
||||
* The downside is that the order field in the DB will increase by one byte every
|
||||
* time an element is reordered.
|
||||
* 2) Adjust the orders of the intervening items. This keeps the orders sane (integers)
|
||||
* but is slower because we have to modify multiple documents.
|
||||
* TODO: we may be able to update fewer records by only altering the
|
||||
* order of the records between the newIndex/oldIndex and the start/end of the list.
|
||||
* 3) Use regular precision arithmetic, but when the difference between the orders of the
|
||||
* moved item and the one before/after it falls below a certain threshold, adjust
|
||||
* the order of that other item, and cascade doing so up or down the list.
|
||||
* This will keep the `order` field constant in size, and will only occasionally
|
||||
* require updating the `order` of other records.
|
||||
*
|
||||
* For now, we use approach #2.
|
||||
*
|
||||
* @param {String} itemId - the _id of the item that was moved
|
||||
* @param {Number} orderPrevItem - the order of the item before it, or null
|
||||
* @param {Number} orderNextItem - the order of the item after it, or null
|
||||
*/
|
||||
templateInstance.adjustOrders = function adjustOrders(itemId, orderPrevItem, orderNextItem) {
|
||||
var orderField = templateInstance.options.sortField;
|
||||
var selector = templateInstance.options.selector || {}, modifier = {$set: {}};
|
||||
var ids = [];
|
||||
var startOrder = templateInstance.collection.findOne(itemId)[orderField];
|
||||
if (orderPrevItem !== null) {
|
||||
// Element has a previous sibling, therefore it was moved down in the list.
|
||||
// Decrease the order of intervening elements.
|
||||
selector[orderField] = {$lte: orderPrevItem, $gt: startOrder};
|
||||
ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
|
||||
Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, -1);
|
||||
|
||||
// Set the order of the dropped element to the order of its predecessor, whose order was decreased
|
||||
modifier.$set[orderField] = orderPrevItem;
|
||||
} else {
|
||||
// element moved up the list, increase order of intervening elements
|
||||
selector[orderField] = {$gte: orderNextItem, $lt: startOrder};
|
||||
ids = _.pluck(templateInstance.collection.find(selector, {fields: {_id: 1}}).fetch(), '_id');
|
||||
Meteor.call('rubaxa:sortable/collection-update', templateInstance.collectionName, ids, orderField, 1);
|
||||
|
||||
// Set the order of the dropped element to the order of its successor, whose order was increased
|
||||
modifier.$set[orderField] = orderNextItem;
|
||||
}
|
||||
templateInstance.collection.update(itemId, modifier);
|
||||
};
|
||||
|
||||
templateInstance.setupDone = true;
|
||||
};
|
||||
|
||||
|
||||
Template.sortable.rendered = function () {
|
||||
var templateInstance = this;
|
||||
var orderField = templateInstance.options.sortField;
|
||||
|
||||
// sorting was changed within the list
|
||||
var optionsOnUpdate = templateInstance.options.onUpdate;
|
||||
templateInstance.options.onUpdate = function sortableUpdate(/**Event*/event) {
|
||||
var itemEl = event.item; // dragged HTMLElement
|
||||
event.data = Blaze.getData(itemEl);
|
||||
if (event.newIndex < event.oldIndex) {
|
||||
// Element moved up in the list. The dropped element has a next sibling for sure.
|
||||
var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
|
||||
templateInstance.adjustOrders(event.data._id, null, orderNextItem);
|
||||
} else if (event.newIndex > event.oldIndex) {
|
||||
// Element moved down in the list. The dropped element has a previous sibling for sure.
|
||||
var orderPrevItem = Blaze.getData(itemEl.previousElementSibling)[orderField];
|
||||
templateInstance.adjustOrders(event.data._id, orderPrevItem, null);
|
||||
} else {
|
||||
// do nothing - drag and drop in the same location
|
||||
}
|
||||
if (optionsOnUpdate) optionsOnUpdate(event);
|
||||
};
|
||||
|
||||
// element was added from another list
|
||||
var optionsOnAdd = templateInstance.options.onAdd;
|
||||
templateInstance.options.onAdd = function sortableAdd(/**Event*/event) {
|
||||
var itemEl = event.item; // dragged HTMLElement
|
||||
event.data = Blaze.getData(itemEl);
|
||||
// let the user decorate the object with additional properties before insertion
|
||||
if (optionsOnAdd) optionsOnAdd(event);
|
||||
|
||||
// Insert the new element at the end of the list and move it where it was dropped.
|
||||
// We could insert it at the beginning, but that would lead to negative orders.
|
||||
var sortSpecifier = {}; sortSpecifier[orderField] = -1;
|
||||
event.data.order = templateInstance.collection.findOne({}, { sort: sortSpecifier, limit: 1 }).order + 1;
|
||||
// TODO: this can obviously be optimized by setting the order directly as the arithmetic average, with the caveats described above
|
||||
var newElementId = templateInstance.collection.insert(event.data);
|
||||
event.data._id = newElementId;
|
||||
if (itemEl.nextElementSibling) {
|
||||
var orderNextItem = Blaze.getData(itemEl.nextElementSibling)[orderField];
|
||||
templateInstance.adjustOrders(newElementId, null, orderNextItem);
|
||||
} else {
|
||||
// do nothing - inserted after the last element
|
||||
}
|
||||
// remove the dropped HTMLElement from the list because we have inserted it in the collection, which will update the template
|
||||
itemEl.parentElement.removeChild(itemEl);
|
||||
};
|
||||
|
||||
// element was removed by dragging into another list
|
||||
var optionsOnRemove = templateInstance.options.onRemove;
|
||||
templateInstance.options.onRemove = function sortableRemove(/**Event*/event) {
|
||||
var itemEl = event.item; // dragged HTMLElement
|
||||
event.data = Blaze.getData(itemEl);
|
||||
// don't remove from the collection if group.pull is clone or false
|
||||
if (typeof templateInstance.options.group === 'undefined'
|
||||
|| typeof templateInstance.options.group.pull === 'undefined'
|
||||
|| templateInstance.options.group.pull === true
|
||||
) templateInstance.collection.remove(event.data._id);
|
||||
if (optionsOnRemove) optionsOnRemove(event);
|
||||
};
|
||||
|
||||
// just compute the `data` context
|
||||
['onStart', 'onEnd', 'onSort', 'onFilter'].forEach(function (eventHandler) {
|
||||
if (templateInstance.options[eventHandler]) {
|
||||
var userEventHandler = templateInstance.options[eventHandler];
|
||||
templateInstance.options[eventHandler] = function (/**Event*/event) {
|
||||
var itemEl = event.item; // dragged HTMLElement
|
||||
event.data = Blaze.getData(itemEl);
|
||||
userEventHandler(event);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
templateInstance.sortable = Sortable.create(templateInstance.firstNode.parentElement, templateInstance.options);
|
||||
// TODO make the object accessible, e.g. via Sortable.getSortableById() or some such
|
||||
};
|
||||
|
||||
|
||||
Template.sortable.destroyed = function () {
|
||||
if(this.sortable) this.sortable.destroy();
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
@echo off
|
||||
REM Test Meteor package before publishing to Atmospherejs.com
|
||||
|
||||
REM Sanity check: make sure we're in the directory of the script
|
||||
set DIR=%~dp0
|
||||
cd %DIR%
|
||||
|
||||
meteor test-packages ./ %*
|
@ -1,35 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Test Meteor package before publishing to Atmospherejs.com
|
||||
|
||||
# Make sure Meteor is installed, per https://www.meteor.com/install.
|
||||
# The curl'ed script is totally safe; takes 2 minutes to read its source and check.
|
||||
type meteor >/dev/null 2>&1 || { curl https://install.meteor.com/ | sh; }
|
||||
|
||||
# sanity check: make sure we're in the directory of the script
|
||||
cd "$( dirname "$0" )"
|
||||
|
||||
|
||||
# delete the temporary files even if Ctrl+C is pressed
|
||||
int_trap() {
|
||||
printf "\nTests interrupted. Cleaning up...\n\n"
|
||||
}
|
||||
trap int_trap INT
|
||||
|
||||
|
||||
EXIT_CODE=0
|
||||
|
||||
PACKAGE_NAME=$(grep -i name package.js | head -1 | cut -d "'" -f 2)
|
||||
|
||||
echo "### Testing $PACKAGE_NAME..."
|
||||
|
||||
# provide an invalid MONGO_URL so Meteor doesn't bog us down with an empty Mongo database
|
||||
if [ $# -gt 0 ]; then
|
||||
# interpret any parameter to mean we want an interactive test
|
||||
MONGO_URL=mongodb:// meteor test-packages ./
|
||||
else
|
||||
# automated/CI test with phantomjs
|
||||
./node_modules/.bin/spacejam --mongo-url mongodb:// test-packages ./
|
||||
EXIT_CODE=$(( $EXIT_CODE + $? ))
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
@ -1,5 +0,0 @@
|
||||
<template name="sortable">
|
||||
{{#each items}}
|
||||
{{> Template.contentBlock this}}
|
||||
{{/each}}
|
||||
</template>
|
@ -1,9 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
Tinytest.add('Sortable.is', function (test) {
|
||||
var items = document.createElement('ul');
|
||||
items.innerHTML = '<li data-id="one">item 1</li><li data-id="two">item 2</li><li data-id="three">item 3</li>';
|
||||
var sortable = new Sortable(items);
|
||||
test.instanceOf(sortable, Sortable, 'Instantiation OK');
|
||||
test.length(sortable.toArray(), 3, 'Three elements');
|
||||
});
|
239
dashboard-ui/bower_components/Sortable/st/app.css
vendored
@ -1,239 +0,0 @@
|
||||
html {
|
||||
background-image: -webkit-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
|
||||
background-image: -ms-linear-gradient(bottom, #F4E2C9 20%, #F4D7C9 100%);
|
||||
background-image: linear-gradient(to bottom, #F4E2C9 20%, #F4D7C9 100%);
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
color: #464637;
|
||||
min-height: 100%;
|
||||
font-size: 20px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
color: #FF3F00;
|
||||
font-size: 20px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
min-width: 1100px;
|
||||
max-width: 1300px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 750px) and (max-width: 970px){
|
||||
.container {
|
||||
width: 100%;
|
||||
min-width: 750px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
.logo {
|
||||
top: 55px;
|
||||
left: 30px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
padding: 3px 10px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background-color: #FF7373;
|
||||
z-index: 1000;
|
||||
}
|
||||
.title_xl {
|
||||
padding: 3px 15px;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.tile {
|
||||
width: 22%;
|
||||
min-width: 245px;
|
||||
color: #FF7270;
|
||||
padding: 10px 30px;
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
margin-left: 5px;
|
||||
margin-right: 30px;
|
||||
background-color: #fff;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.tile__name {
|
||||
cursor: move;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #FF7373;
|
||||
}
|
||||
|
||||
.tile__list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.tile__list:last-child {
|
||||
margin-right: 0;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.tile__list img {
|
||||
cursor: move;
|
||||
margin: 10px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.block {
|
||||
opacity: 1;
|
||||
position: absolute;
|
||||
}
|
||||
.block__list {
|
||||
padding: 20px 0;
|
||||
max-width: 360px;
|
||||
margin-top: -8px;
|
||||
margin-left: 5px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.block__list-title {
|
||||
margin: -20px 0 0;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
background: #5F9EDF;
|
||||
}
|
||||
.block__list li { cursor: move; }
|
||||
|
||||
.block__list_words li {
|
||||
background-color: #fff;
|
||||
padding: 10px 40px;
|
||||
}
|
||||
.block__list_words .sortable-ghost {
|
||||
opacity: 0.4;
|
||||
background-color: #F4E2C9;
|
||||
}
|
||||
|
||||
.block__list_words li:first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.block__list_tags {
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.block__list_tags:after {
|
||||
clear: both;
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
.block__list_tags li {
|
||||
color: #fff;
|
||||
float: left;
|
||||
margin: 8px 20px 10px 0;
|
||||
padding: 5px 10px;
|
||||
min-width: 10px;
|
||||
background-color: #5F9EDF;
|
||||
text-align: center;
|
||||
}
|
||||
.block__list_tags li:first-child:first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#editable {}
|
||||
#editable li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#editable i {
|
||||
-webkit-transition: opacity .2s;
|
||||
transition: opacity .2s;
|
||||
opacity: 0;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
color: #c00;
|
||||
top: 10px;
|
||||
right: 40px;
|
||||
position: absolute;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
#editable li:hover i {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
|
||||
#filter {}
|
||||
#filter button {
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: 0;
|
||||
opacity: .5;
|
||||
margin: 10px 0 0;
|
||||
transition: opacity .1s ease;
|
||||
cursor: pointer;
|
||||
background: #5F9EDF;
|
||||
padding: 10px 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
#filter button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#filter .block__list {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
margin-right: 10px;
|
||||
font: bold 20px Sans-Serif;
|
||||
color: #5F9EDF;
|
||||
display: inline-block;
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing; /* overrides 'move' */
|
||||
}
|
||||
|
||||
#todos input {
|
||||
padding: 5px;
|
||||
font-size: 14px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#nested ul li {
|
||||
background-color: rgba(0,0,0,.05);
|
||||
}
|
226
dashboard-ui/bower_components/Sortable/st/app.js
vendored
@ -1,226 +0,0 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var byId = function (id) { return document.getElementById(id); },
|
||||
|
||||
loadScripts = function (desc, callback) {
|
||||
var deps = [], key, idx = 0;
|
||||
|
||||
for (key in desc) {
|
||||
deps.push(key);
|
||||
}
|
||||
|
||||
(function _next() {
|
||||
var pid,
|
||||
name = deps[idx],
|
||||
script = document.createElement('script');
|
||||
|
||||
script.type = 'text/javascript';
|
||||
script.src = desc[deps[idx]];
|
||||
|
||||
pid = setInterval(function () {
|
||||
if (window[name]) {
|
||||
clearTimeout(pid);
|
||||
|
||||
deps[idx++] = window[name];
|
||||
|
||||
if (deps[idx]) {
|
||||
_next();
|
||||
} else {
|
||||
callback.apply(null, deps);
|
||||
}
|
||||
}
|
||||
}, 30);
|
||||
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
})()
|
||||
},
|
||||
|
||||
console = window.console;
|
||||
|
||||
|
||||
if (!console.log) {
|
||||
console.log = function () {
|
||||
alert([].join.apply(arguments, ' '));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Sortable.create(byId('foo'), {
|
||||
group: "words",
|
||||
animation: 150,
|
||||
store: {
|
||||
get: function (sortable) {
|
||||
var order = localStorage.getItem(sortable.options.group);
|
||||
return order ? order.split('|') : [];
|
||||
},
|
||||
set: function (sortable) {
|
||||
var order = sortable.toArray();
|
||||
localStorage.setItem(sortable.options.group, order.join('|'));
|
||||
}
|
||||
},
|
||||
onAdd: function (evt){ console.log('onAdd.foo:', [evt.item, evt.from]); },
|
||||
onUpdate: function (evt){ console.log('onUpdate.foo:', [evt.item, evt.from]); },
|
||||
onRemove: function (evt){ console.log('onRemove.foo:', [evt.item, evt.from]); },
|
||||
onStart:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
|
||||
onSort:function(evt){ console.log('onStart.foo:', [evt.item, evt.from]);},
|
||||
onEnd: function(evt){ console.log('onEnd.foo:', [evt.item, evt.from]);}
|
||||
});
|
||||
|
||||
|
||||
Sortable.create(byId('bar'), {
|
||||
group: "words",
|
||||
animation: 150,
|
||||
onAdd: function (evt){ console.log('onAdd.bar:', evt.item); },
|
||||
onUpdate: function (evt){ console.log('onUpdate.bar:', evt.item); },
|
||||
onRemove: function (evt){ console.log('onRemove.bar:', evt.item); },
|
||||
onStart:function(evt){ console.log('onStart.foo:', evt.item);},
|
||||
onEnd: function(evt){ console.log('onEnd.foo:', evt.item);}
|
||||
});
|
||||
|
||||
|
||||
// Multi groups
|
||||
Sortable.create(byId('multi'), {
|
||||
animation: 150,
|
||||
draggable: '.tile',
|
||||
handle: '.tile__name'
|
||||
});
|
||||
|
||||
[].forEach.call(byId('multi').getElementsByClassName('tile__list'), function (el){
|
||||
Sortable.create(el, {
|
||||
group: 'photo',
|
||||
animation: 150
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Editable list
|
||||
var editableList = Sortable.create(byId('editable'), {
|
||||
animation: 150,
|
||||
filter: '.js-remove',
|
||||
onFilter: function (evt) {
|
||||
evt.item.parentNode.removeChild(evt.item);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
byId('addUser').onclick = function () {
|
||||
Ply.dialog('prompt', {
|
||||
title: 'Add',
|
||||
form: { name: 'name' }
|
||||
}).done(function (ui) {
|
||||
var el = document.createElement('li');
|
||||
el.innerHTML = ui.data.name + '<i class="js-remove">✖</i>';
|
||||
editableList.el.appendChild(el);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Advanced groups
|
||||
[{
|
||||
name: 'advanced',
|
||||
pull: true,
|
||||
put: true
|
||||
},
|
||||
{
|
||||
name: 'advanced',
|
||||
pull: 'clone',
|
||||
put: false
|
||||
}, {
|
||||
name: 'advanced',
|
||||
pull: false,
|
||||
put: true
|
||||
}].forEach(function (groupOpts, i) {
|
||||
Sortable.create(byId('advanced-' + (i + 1)), {
|
||||
sort: (i != 1),
|
||||
group: groupOpts,
|
||||
animation: 150
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 'handle' option
|
||||
Sortable.create(byId('handle-1'), {
|
||||
handle: '.drag-handle',
|
||||
animation: 150
|
||||
});
|
||||
|
||||
|
||||
// Angular example
|
||||
angular.module('todoApp', ['ng-sortable'])
|
||||
.constant('ngSortableConfig', {onEnd: function() {
|
||||
console.log('default onEnd()');
|
||||
}})
|
||||
.controller('TodoController', ['$scope', function ($scope) {
|
||||
$scope.todos = [
|
||||
{text: 'learn angular', done: true},
|
||||
{text: 'build an angular app', done: false}
|
||||
];
|
||||
|
||||
$scope.addTodo = function () {
|
||||
$scope.todos.push({text: $scope.todoText, done: false});
|
||||
$scope.todoText = '';
|
||||
};
|
||||
|
||||
$scope.remaining = function () {
|
||||
var count = 0;
|
||||
angular.forEach($scope.todos, function (todo) {
|
||||
count += todo.done ? 0 : 1;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
$scope.archive = function () {
|
||||
var oldTodos = $scope.todos;
|
||||
$scope.todos = [];
|
||||
angular.forEach(oldTodos, function (todo) {
|
||||
if (!todo.done) $scope.todos.push(todo);
|
||||
});
|
||||
};
|
||||
}])
|
||||
.controller('TodoControllerNext', ['$scope', function ($scope) {
|
||||
$scope.todos = [
|
||||
{text: 'learn Sortable', done: true},
|
||||
{text: 'use ng-sortable', done: false},
|
||||
{text: 'Enjoy', done: false}
|
||||
];
|
||||
|
||||
$scope.remaining = function () {
|
||||
var count = 0;
|
||||
angular.forEach($scope.todos, function (todo) {
|
||||
count += todo.done ? 0 : 1;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
$scope.sortableConfig = { group: 'todo', animation: 150 };
|
||||
'Start End Add Update Remove Sort'.split(' ').forEach(function (name) {
|
||||
$scope.sortableConfig['on' + name] = console.log.bind(console, name);
|
||||
});
|
||||
}]);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
// Background
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function setNoiseBackground(el, width, height, opacity) {
|
||||
var canvas = document.createElement("canvas");
|
||||
var context = canvas.getContext("2d");
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
for (var i = 0; i < width; i++) {
|
||||
for (var j = 0; j < height; j++) {
|
||||
var val = Math.floor(Math.random() * 255);
|
||||
context.fillStyle = "rgba(" + val + "," + val + "," + val + "," + opacity + ")";
|
||||
context.fillRect(i, j, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
el.style.background = "url(" + canvas.toDataURL("image/png") + ")";
|
||||
}
|
||||
|
||||
setNoiseBackground(document.getElementsByTagName('body')[0], 50, 50, 0.02);
|
||||
}, false);
|
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 4.6 KiB |
@ -1,32 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
|
||||
|
||||
|
||||
<!-- List with handle -->
|
||||
<div id="listWithHandle" class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge">14</span>
|
||||
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
|
||||
Drag me by the handle
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge">2</span>
|
||||
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
|
||||
You can also select text
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge">1</span>
|
||||
<span class="glyphicon glyphicon-move" aria-hidden="true"></span>
|
||||
Best of both worlds!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -1,49 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>IFrame playground</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css"/>
|
||||
|
||||
|
||||
<!-- Latest Sortable -->
|
||||
<script src="../../Sortable.js"></script>
|
||||
|
||||
|
||||
<!-- Simple List -->
|
||||
<div id="simpleList" class="list-group">
|
||||
<div class="list-group-item">This is <a href="http://rubaxa.github.io/Sortable/">Sortable</a></div>
|
||||
<div class="list-group-item">It works with Bootstrap...</div>
|
||||
<div class="list-group-item">...out of the box.</div>
|
||||
<div class="list-group-item">It has support for touch devices.</div>
|
||||
<div class="list-group-item">Just drag some elements around.</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
Sortable.create(simpleList, {group: 'shared'});
|
||||
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
|
||||
iframe.src = 'frame.html';
|
||||
iframe.width = '100%';
|
||||
iframe.onload = function () {
|
||||
var doc = iframe.contentDocument,
|
||||
list = doc.getElementById('listWithHandle');
|
||||
|
||||
Sortable.create(list, {group: 'shared'});
|
||||
};
|
||||
|
||||
|
||||
document.body.appendChild(iframe);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
BIN
dashboard-ui/bower_components/Sortable/st/logo.png
vendored
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -1,4 +1,4 @@
|
||||
define(['itemShortcuts', 'connectionManager', 'layoutManager', 'browser', 'dom', 'registerElement'], function (itemShortcuts, connectionManager, layoutManager, browser, dom) {
|
||||
define(['itemShortcuts', 'connectionManager', 'layoutManager', 'browser', 'dom', 'loading', 'registerElement'], function (itemShortcuts, connectionManager, layoutManager, browser, dom, loading) {
|
||||
|
||||
var ItemsContainerProtoType = Object.create(HTMLDivElement.prototype);
|
||||
|
||||
@ -105,6 +105,76 @@
|
||||
});
|
||||
};
|
||||
|
||||
function onDrop(evt, itemsContainer) {
|
||||
|
||||
var playlistId = itemsContainer.getAttribute('data-playlistid');
|
||||
|
||||
loading.show();
|
||||
|
||||
var el = evt.item;
|
||||
|
||||
var newIndex = evt.newIndex;
|
||||
var itemId = el.getAttribute('data-playlistitemid');
|
||||
|
||||
var serverId = el.getAttribute('data-serverid');
|
||||
var apiClient = connectionManager.getApiClient(serverId);
|
||||
|
||||
apiClient.ajax({
|
||||
|
||||
url: apiClient.getUrl('Playlists/' + playlistId + '/Items/' + itemId + '/Move/' + newIndex),
|
||||
|
||||
type: 'POST'
|
||||
|
||||
}).then(function () {
|
||||
|
||||
el.setAttribute('data-index', newIndex);
|
||||
loading.hide();
|
||||
|
||||
}, function () {
|
||||
|
||||
loading.hide();
|
||||
|
||||
itemsContainer.dispatchEvent(new CustomEvent('needsrefresh', {
|
||||
detail: {},
|
||||
cancelable: false,
|
||||
bubbles: true
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
ItemsContainerProtoType.enableDragReordering = function (enabled) {
|
||||
|
||||
var current = this.sortable;
|
||||
|
||||
if (!enabled) {
|
||||
if (current) {
|
||||
current.destroy();
|
||||
this.sortable = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (current) {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
require(['sortable'], function (Sortable) {
|
||||
|
||||
self.sortable = new Sortable(self, {
|
||||
|
||||
draggable: ".listItem",
|
||||
handle: '.listViewDragHandle',
|
||||
|
||||
// dragging ended
|
||||
onEnd: function (/**Event*/evt) {
|
||||
|
||||
onDrop(evt, self);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ItemsContainerProtoType.attachedCallback = function () {
|
||||
|
||||
this.addEventListener('click', onClick);
|
||||
@ -130,6 +200,7 @@
|
||||
|
||||
this.enableHoverMenu(false);
|
||||
this.enableMultiSelect(false);
|
||||
this.enableDragReordering(false);
|
||||
this.removeEventListener('click', onClick);
|
||||
this.removeEventListener('contextmenu', onContextMenu);
|
||||
this.removeEventListener('contextmenu', disableEvent);
|
||||
|
@ -76,22 +76,6 @@
|
||||
elem.setAttribute('data-playlistid', item.Id);
|
||||
elem.innerHTML = html;
|
||||
|
||||
var listParent = elem;
|
||||
|
||||
require(['sortable'], function (Sortable) {
|
||||
|
||||
var sortable = new Sortable(listParent, {
|
||||
|
||||
draggable: ".listItem",
|
||||
handle: '.listViewDragHandle',
|
||||
|
||||
// dragging ended
|
||||
onEnd: function (/**Event*/evt) {
|
||||
|
||||
onDrop(evt, page, item);
|
||||
}
|
||||
});
|
||||
});
|
||||
ImageLoader.lazyChildren(elem);
|
||||
|
||||
$('.btnNextPage', elem).on('click', function () {
|
||||
@ -108,37 +92,12 @@
|
||||
});
|
||||
}
|
||||
|
||||
function onDrop(evt, page, item) {
|
||||
|
||||
Dashboard.showLoadingMsg();
|
||||
|
||||
var el = evt.item;
|
||||
|
||||
var newIndex = evt.newIndex;
|
||||
var itemId = el.getAttribute('data-playlistitemid');
|
||||
|
||||
ApiClient.ajax({
|
||||
|
||||
url: ApiClient.getUrl('Playlists/' + item.Id + '/Items/' + itemId + '/Move/' + newIndex),
|
||||
|
||||
type: 'POST'
|
||||
|
||||
}).then(function () {
|
||||
|
||||
el.setAttribute('data-index', newIndex);
|
||||
Dashboard.hideLoadingMsg();
|
||||
|
||||
}, function () {
|
||||
|
||||
Dashboard.hideLoadingMsg();
|
||||
reloadItems(page, item);
|
||||
});
|
||||
}
|
||||
|
||||
function init(page, item) {
|
||||
|
||||
var elem = page.querySelector('#childrenContent .itemsContainer');
|
||||
|
||||
elem.enableDragReordering(true);
|
||||
|
||||
elem.addEventListener('needsrefresh', function () {
|
||||
|
||||
reloadItems(page, item);
|
||||
|