Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
32
gaia/apps/music/components/sww-raw-cache/.bower.json
Executable file
32
gaia/apps/music/components/sww-raw-cache/.bower.json
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "sww-raw-cache",
|
||||
"main": "dist/sww-raw-cache.js",
|
||||
"version": "0.2.1",
|
||||
"authors": [
|
||||
"The Gaia Team"
|
||||
],
|
||||
"description": "Simple raw cache for sww using http verbs for content handling",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"devDependencies": {
|
||||
"serviceworkerware": "git@github.com:gaia-components/serviceworkerware.git"
|
||||
},
|
||||
"homepage": "https://github.com/gaia-components/sww-raw-cache",
|
||||
"_release": "0.2.1",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "0.2.1",
|
||||
"commit": "2d5d8df6894fd75d546d6a1c1f909ec1a704b4f7"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/sww-raw-cache.git",
|
||||
"_target": "~0.2.1",
|
||||
"_originalSource": "gaia-components/sww-raw-cache",
|
||||
"_direct": true
|
||||
}
|
||||
21
gaia/apps/music/components/sww-raw-cache/bower.json
Executable file
21
gaia/apps/music/components/sww-raw-cache/bower.json
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "sww-raw-cache",
|
||||
"main": "dist/sww-raw-cache.js",
|
||||
"version": "0.2.1",
|
||||
"authors": [
|
||||
"The Gaia Team"
|
||||
],
|
||||
"description": "Simple raw cache for sww using http verbs for content handling",
|
||||
"license": "MPL",
|
||||
"private": true,
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"devDependencies": {
|
||||
"serviceworkerware": "git@github.com:gaia-components/serviceworkerware.git"
|
||||
}
|
||||
}
|
||||
115
gaia/apps/music/components/sww-raw-cache/demo/index.html
Executable file
115
gaia/apps/music/components/sww-raw-cache/demo/index.html
Executable file
|
|
@ -0,0 +1,115 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Raw Cache Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
if (navigator.serviceWorker) {
|
||||
navigator.serviceWorker.getRegistration().then(function(reg) {
|
||||
if (!reg) {
|
||||
navigator.serviceWorker.register('./sw.js').then(function() {
|
||||
document.location.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert('No SW present :(');
|
||||
}
|
||||
|
||||
var myOrigin = document.location.toString().substr(0,
|
||||
document.location.toString().lastIndexOf('/') + 1);
|
||||
|
||||
function addContent(url, content, type) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', url, true);
|
||||
xhr.onload = function() {
|
||||
window.performance.mark('mark_post_save');
|
||||
var files = JSON.parse(localStorage['files'] || '[]');
|
||||
if (files.indexOf(url) === -1) {
|
||||
files.push(url);
|
||||
localStorage['files'] = JSON.stringify(files);
|
||||
}
|
||||
dumpGeneratedFiles();
|
||||
window.performance.measure('create_time', 'mark_pre_save',
|
||||
'mark_post_save');
|
||||
var time = window.performance.getEntriesByName('create_time');
|
||||
time = time.pop();
|
||||
alert(url + ' generated in ' +
|
||||
time.duration + ' ms');
|
||||
window.performance.clearMarks();
|
||||
window.performance.clearMeasures();
|
||||
};
|
||||
xhr.setRequestHeader('Content-Type', type || 'text/plain');
|
||||
window.performance.mark('mark_pre_save');
|
||||
xhr.send(content);
|
||||
}
|
||||
|
||||
function removeContent(url) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('DELETE', url, true);
|
||||
xhr.onload = function() {
|
||||
var files = JSON.parse(localStorage['files'] || '[]');
|
||||
file = url.substr(myOrigin.length);
|
||||
var index = files.indexOf(file);
|
||||
if (index > - 1) {
|
||||
files.splice(index, 1);
|
||||
}
|
||||
localStorage['files'] = JSON.stringify(files);
|
||||
dumpGeneratedFiles();
|
||||
alert(url + ' removed');
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function dumpGeneratedFiles() {
|
||||
var content = document.getElementById('content');
|
||||
content.innerHTML = '';
|
||||
|
||||
var files = JSON.parse(localStorage['files'] || '[]');
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
var ul = document.createElement('ul');
|
||||
|
||||
files.forEach(function(file) {
|
||||
var url = myOrigin + file;
|
||||
var li = document.createElement('li');
|
||||
li.innerHTML = '<a href="'+ url +'" target="_blank">' + url + '</a> <a href="javascript:removeContent(\'' + url + '\')">[x]</a>';
|
||||
ul.appendChild(li);
|
||||
});
|
||||
content.appendChild(ul);
|
||||
}
|
||||
|
||||
function process() {
|
||||
var newUrl = document.getElementById('newUrl');
|
||||
var url = newUrl.value.trim();
|
||||
if (!url) {
|
||||
alert('Invalid url');
|
||||
return;
|
||||
}
|
||||
var newContent = document.getElementById('newContent');
|
||||
var content = newContent.value.trim();
|
||||
if (!content) {
|
||||
alert('Invalid content');
|
||||
}
|
||||
|
||||
addContent(url, content);
|
||||
}
|
||||
window.addEventListener('DOMContentLoaded', dumpGeneratedFiles);
|
||||
</script>
|
||||
<body>
|
||||
<section id="content">
|
||||
</section>
|
||||
<section id="control">
|
||||
<h2>Content generation</h2>
|
||||
<script>document.write(myOrigin);</script>
|
||||
<input type="text" size=40 id="newUrl"><br/>
|
||||
<textarea cols=80 rows=10 id="newContent"></textarea></br/>
|
||||
<button onClick="process()">create content</button><br/>
|
||||
(be careful and don't overwrite the index.html ;))
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
11
gaia/apps/music/components/sww-raw-cache/demo/sw.js
Executable file
11
gaia/apps/music/components/sww-raw-cache/demo/sw.js
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
importScripts('../bower_components/serviceworkerware/dist/sww.js');
|
||||
importScripts('../dist/sww-raw-cache.js');
|
||||
|
||||
var worker = new ServiceWorkerWare();
|
||||
worker.use(new self.StaticCacher(['index.html', 'sw.js']));
|
||||
worker.use(new RawCache({
|
||||
cacheName: 'rawCache'
|
||||
}));
|
||||
worker.use(new self.SimpleOfflineCache());
|
||||
|
||||
worker.init();
|
||||
165
gaia/apps/music/components/sww-raw-cache/dist/sww-raw-cache.js
vendored
Executable file
165
gaia/apps/music/components/sww-raw-cache/dist/sww-raw-cache.js
vendored
Executable file
|
|
@ -0,0 +1,165 @@
|
|||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
var RawCache = require('./lib/sww-raw-cache.js');
|
||||
|
||||
self.RawCache = RawCache;
|
||||
|
||||
},{"./lib/sww-raw-cache.js":2}],2:[function(require,module,exports){
|
||||
/* global Response, Request, Promise */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Constructor for the RawCache object. It receives an object
|
||||
* with the configuration parameters.
|
||||
* @param {Object} options set of configuration parameters.
|
||||
*/
|
||||
function RawCache(options) {
|
||||
if (!options.cacheName) {
|
||||
throw Error('This middleware needs a cache name.');
|
||||
}
|
||||
|
||||
this.cacheName = options.cacheName;
|
||||
this.DEFAULT_CONTENT_TYPE = options.defaultContentType || 'text/html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to retrieve cache used by this middleware.
|
||||
* @returns {Promise} promise once resolved will contain the Cache object
|
||||
*/
|
||||
RawCache.prototype._getCache = function getCache() {
|
||||
this._cache = this._cache || self.caches.open(this.cacheName);
|
||||
return this._cache;
|
||||
};
|
||||
|
||||
// This middleware will support the following http methods
|
||||
RawCache.prototype.SUPPORTED_ACTIONS = ['get', 'post', 'put', 'delete'];
|
||||
|
||||
/**
|
||||
* Handles the request and perform actions over the cache content
|
||||
* based on the http verb used for the request.
|
||||
* @param {Request} the request object.
|
||||
* @param {Response} the response object.
|
||||
* @param {Function} the callback to finalize the fetching pipeline.
|
||||
*/
|
||||
RawCache.prototype.onFetch = function onFetch(request, response, endWith) {
|
||||
if (response) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
var method = request.method.toLowerCase();
|
||||
if (this.SUPPORTED_ACTIONS.indexOf(method) === -1) {
|
||||
// Method not supported, just bypass the request and do nothing
|
||||
// in this layer
|
||||
return null;
|
||||
}
|
||||
|
||||
return this[method].apply(this, [request, response, endWith]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the content from this cache.
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise with the response object cached or null
|
||||
*/
|
||||
RawCache.prototype.get = function get(request, response, endWith) {
|
||||
var _this = this;
|
||||
return _this._getCache().then(function(cache) {
|
||||
return cache.match(request).then(function (response) {
|
||||
if (!response) { return null; }
|
||||
return endWith(response);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the specified uri from the cache entry
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise, result of removing from cache.
|
||||
*/
|
||||
RawCache.prototype.delete = function del(request) {
|
||||
var _this = this;
|
||||
return this._getCache().then(function(cache) {
|
||||
return cache.delete(request).then(
|
||||
_this._getOKResponse,
|
||||
_this._getErrorResponse
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the body of the specified request as a cache entry for the uri
|
||||
* in the request.
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise, 200 response if cached correctly.
|
||||
*/
|
||||
RawCache.prototype.post = function post(request) {
|
||||
var _this = this;
|
||||
request = request.clone();
|
||||
return request.text().then(function(content) {
|
||||
var contentType =
|
||||
request.headers.get('Content-Type') || _this.DEFAULT_CONTENT_TYPE;
|
||||
var response = new Response(content, {
|
||||
'headers': {
|
||||
'x-sww-raw-cache': _this.cacheName + ';time=' + Date.now(),
|
||||
'Content-Type': contentType
|
||||
}
|
||||
});
|
||||
// We create a new request, with default GET method
|
||||
var customRequest = new Request(request.url);
|
||||
return _this._getCache().then(function(cache) {
|
||||
return cache.put(customRequest, response).then(
|
||||
_this._getOKResponse,
|
||||
_this._getErrorResponse
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// In this version put and post work in the same way, this could change
|
||||
// in future versions.
|
||||
RawCache.prototype.put = RawCache.prototype.post;
|
||||
|
||||
/**
|
||||
* Builds a 200 response to be returned when operations against the cache
|
||||
* are performed without problems.
|
||||
* @param (string) msg optional message
|
||||
* @returns (Response) response object
|
||||
*/
|
||||
RawCache.prototype._getOKResponse = function(msg) {
|
||||
return RawCache._getResponse(200, {
|
||||
'status': 'ok',
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a 500 response to be returned when operations against the cache
|
||||
* are performed with problems.
|
||||
* @param (string) msg optional message
|
||||
* @returns (Response) response object
|
||||
*/
|
||||
RawCache.prototype._getErrorResponse = function(msg) {
|
||||
return RawCache._getResponse(500, {
|
||||
'status': 'ko',
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a generic response.
|
||||
*/
|
||||
RawCache._getResponse = function(statusNumber, content) {
|
||||
return new Response(JSON.stringify(content), {
|
||||
'headers': { 'Content-Type': 'application/json' },
|
||||
'status': statusNumber
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = RawCache;
|
||||
|
||||
},{}]},{},[1])
|
||||
|
||||
|
||||
//# sourceMappingURL=sww-raw-cache.js.map
|
||||
1
gaia/apps/music/components/sww-raw-cache/dist/sww-raw-cache.js.map
vendored
Executable file
1
gaia/apps/music/components/sww-raw-cache/dist/sww-raw-cache.js.map
vendored
Executable file
File diff suppressed because one or more lines are too long
59
gaia/apps/music/components/sww-raw-cache/gulpfile.js
Executable file
59
gaia/apps/music/components/sww-raw-cache/gulpfile.js
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
var browserify = require('browserify');
|
||||
var gulp = require('gulp');
|
||||
var source = require('vinyl-source-stream');
|
||||
var buffer = require('vinyl-buffer');
|
||||
var sourcemaps = require('gulp-sourcemaps');
|
||||
var webserver = require('gulp-webserver');
|
||||
var jshint = require('gulp-jshint');
|
||||
var watch = require('gulp-watch');
|
||||
|
||||
var packageName = require('./package.json').name;
|
||||
|
||||
var getBundleName = function () {
|
||||
return packageName;
|
||||
};
|
||||
|
||||
gulp.task('javascript', function() {
|
||||
|
||||
var bundler = browserify({
|
||||
entries: ['./index.js'],
|
||||
debug: true,
|
||||
standAlone: packageName
|
||||
});
|
||||
|
||||
var bundle = function() {
|
||||
return bundler
|
||||
.bundle()
|
||||
.pipe(source(getBundleName() + '.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(sourcemaps.init({loadMaps: true}))
|
||||
.pipe(sourcemaps.write('./'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
};
|
||||
|
||||
return bundle();
|
||||
});
|
||||
|
||||
gulp.task('webserver', function() {
|
||||
gulp.src('.')
|
||||
.pipe(webserver({
|
||||
livereload: true,
|
||||
directoryListing: true,
|
||||
open: true,
|
||||
port: 8081
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('./lib/*', ['lint', 'javascript']);
|
||||
});
|
||||
|
||||
gulp.task('lint', function() {
|
||||
return gulp.src(['./lib/*.js', './index.js'])
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter('default'));
|
||||
});
|
||||
|
||||
gulp.task('default', ['lint','javascript']);
|
||||
3
gaia/apps/music/components/sww-raw-cache/index.js
Executable file
3
gaia/apps/music/components/sww-raw-cache/index.js
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
var RawCache = require('./lib/sww-raw-cache.js');
|
||||
|
||||
self.RawCache = RawCache;
|
||||
154
gaia/apps/music/components/sww-raw-cache/lib/sww-raw-cache.js
Executable file
154
gaia/apps/music/components/sww-raw-cache/lib/sww-raw-cache.js
Executable file
|
|
@ -0,0 +1,154 @@
|
|||
/* global Response, Request, Promise */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Constructor for the RawCache object. It receives an object
|
||||
* with the configuration parameters.
|
||||
* @param {Object} options set of configuration parameters.
|
||||
*/
|
||||
function RawCache(options) {
|
||||
if (!options.cacheName) {
|
||||
throw Error('This middleware needs a cache name.');
|
||||
}
|
||||
|
||||
this.cacheName = options.cacheName;
|
||||
this.DEFAULT_CONTENT_TYPE = options.defaultContentType || 'text/html';
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to retrieve cache used by this middleware.
|
||||
* @returns {Promise} promise once resolved will contain the Cache object
|
||||
*/
|
||||
RawCache.prototype._getCache = function getCache() {
|
||||
this._cache = this._cache || self.caches.open(this.cacheName);
|
||||
return this._cache;
|
||||
};
|
||||
|
||||
// This middleware will support the following http methods
|
||||
RawCache.prototype.SUPPORTED_ACTIONS = ['get', 'post', 'put', 'delete'];
|
||||
|
||||
/**
|
||||
* Handles the request and perform actions over the cache content
|
||||
* based on the http verb used for the request.
|
||||
* @param {Request} the request object.
|
||||
* @param {Response} the response object.
|
||||
* @param {Function} the callback to finalize the fetching pipeline.
|
||||
*/
|
||||
RawCache.prototype.onFetch = function onFetch(request, response, endWith) {
|
||||
if (response) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
var method = request.method.toLowerCase();
|
||||
if (this.SUPPORTED_ACTIONS.indexOf(method) === -1) {
|
||||
// Method not supported, just bypass the request and do nothing
|
||||
// in this layer
|
||||
return null;
|
||||
}
|
||||
|
||||
return this[method].apply(this, [request, response, endWith]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the content from this cache.
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise with the response object cached or null
|
||||
*/
|
||||
RawCache.prototype.get = function get(request, response, endWith) {
|
||||
var _this = this;
|
||||
return _this._getCache().then(function(cache) {
|
||||
return cache.match(request).then(function (response) {
|
||||
if (!response) { return null; }
|
||||
return endWith(response);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the specified uri from the cache entry
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise, result of removing from cache.
|
||||
*/
|
||||
RawCache.prototype.delete = function del(request) {
|
||||
var _this = this;
|
||||
return this._getCache().then(function(cache) {
|
||||
return cache.delete(request).then(
|
||||
_this._getOKResponse,
|
||||
_this._getErrorResponse
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the body of the specified request as a cache entry for the uri
|
||||
* in the request.
|
||||
* @param {Request} request the request object.
|
||||
* @param {Response} response the response object.
|
||||
* @returns {Promise} Promise, 200 response if cached correctly.
|
||||
*/
|
||||
RawCache.prototype.post = function post(request) {
|
||||
var _this = this;
|
||||
request = request.clone();
|
||||
return request.text().then(function(content) {
|
||||
var contentType =
|
||||
request.headers.get('Content-Type') || _this.DEFAULT_CONTENT_TYPE;
|
||||
var response = new Response(content, {
|
||||
'headers': {
|
||||
'x-sww-raw-cache': _this.cacheName + ';time=' + Date.now(),
|
||||
'Content-Type': contentType
|
||||
}
|
||||
});
|
||||
// We create a new request, with default GET method
|
||||
var customRequest = new Request(request.url);
|
||||
return _this._getCache().then(function(cache) {
|
||||
return cache.put(customRequest, response).then(
|
||||
_this._getOKResponse,
|
||||
_this._getErrorResponse
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// In this version put and post work in the same way, this could change
|
||||
// in future versions.
|
||||
RawCache.prototype.put = RawCache.prototype.post;
|
||||
|
||||
/**
|
||||
* Builds a 200 response to be returned when operations against the cache
|
||||
* are performed without problems.
|
||||
* @param (string) msg optional message
|
||||
* @returns (Response) response object
|
||||
*/
|
||||
RawCache.prototype._getOKResponse = function(msg) {
|
||||
return RawCache._getResponse(200, {
|
||||
'status': 'ok',
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a 500 response to be returned when operations against the cache
|
||||
* are performed with problems.
|
||||
* @param (string) msg optional message
|
||||
* @returns (Response) response object
|
||||
*/
|
||||
RawCache.prototype._getErrorResponse = function(msg) {
|
||||
return RawCache._getResponse(500, {
|
||||
'status': 'ko',
|
||||
'msg': msg
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a generic response.
|
||||
*/
|
||||
RawCache._getResponse = function(statusNumber, content) {
|
||||
return new Response(JSON.stringify(content), {
|
||||
'headers': { 'Content-Type': 'application/json' },
|
||||
'status': statusNumber
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = RawCache;
|
||||
18
gaia/apps/music/components/sww-raw-cache/package.json
Executable file
18
gaia/apps/music/components/sww-raw-cache/package.json
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "sww-raw-cache",
|
||||
"version": "0.2.1",
|
||||
"description": "Middleware used in ServiceWorkerWare that allows developers to control the cache content via http verbs",
|
||||
"main": "dist/sww-raw-cache.js",
|
||||
"author": "The Gaia Team",
|
||||
"license": "MPL",
|
||||
"devDependencies": {
|
||||
"browserify": "^9.0.3",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-jshint": "^1.9.2",
|
||||
"gulp-sourcemaps": "^1.5.0",
|
||||
"gulp-watch": "^4.1.1",
|
||||
"gulp-webserver": "^0.9.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.0.0"
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue