Compare commits
7 Commits
78db967922
...
af2d8d636d
Author | SHA1 | Date |
---|---|---|
Derek | af2d8d636d | |
Derek | 07a13d42f9 | |
Derek | 8552738423 | |
Derek | fda6c10df9 | |
Derek | 9d571fd709 | |
Derek | 89745b15c5 | |
Derek | 3ffa12f3c4 |
|
@ -1,2 +1,3 @@
|
|||
files/
|
||||
__pycache__/
|
||||
static/components/
|
||||
|
|
1
Pipfile
1
Pipfile
|
@ -7,6 +7,7 @@ name = "pypi"
|
|||
flask = "*"
|
||||
flask-uploads = "*"
|
||||
wand = "*"
|
||||
gunicorn = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "2b757113898edc814da1a9d4b93adea38e861ac3590944a8cae1d77602fe06db"
|
||||
"sha256": "64858e875b29283189ce9e29e098ccb1b84744bdfba378646b95c4fa9f5770b7"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -38,6 +38,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==0.2.1"
|
||||
},
|
||||
"gunicorn": {
|
||||
"hashes": [
|
||||
"sha256:7ef2b828b335ed58e3b64ffa84caceb0a7dd7c5ca12f217241350dec36a1d5dc",
|
||||
"sha256:bc59005979efb6d2dd7d5ba72d99f8a8422862ad17ff3a16e900684630dd2a10"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==19.8.1"
|
||||
},
|
||||
"itsdangerous": {
|
||||
"hashes": [
|
||||
"sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519"
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "memepolice",
|
||||
"authors": [
|
||||
"Derek Schmidt <skehmatics@gmail.com>"
|
||||
],
|
||||
"description": "meme steal begone",
|
||||
"main": "memepolice.py",
|
||||
"license": "GPL",
|
||||
"homepage": "",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"webcomponentsjs": "^2.0.1",
|
||||
"vaadin-upload": "^4.1.0",
|
||||
"clipboard-copy": "advanced-rest-client/clipboard-copy#^2.0.1",
|
||||
"paper-tooltip": "PolymerElements/paper-tooltip#^2.1.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"webcomponentsjs": "^v1.1.0"
|
||||
}
|
||||
}
|
|
@ -56,3 +56,12 @@ def meirl_show_reddit(file):
|
|||
@app.route('/itmeirlbot-protect/files/<file>.jpg')
|
||||
def meirl_show_meirlbot(file):
|
||||
return app.send_static_file('bad.jpg')
|
||||
|
||||
|
||||
@app.route('/components/<path:path>')
|
||||
def components(path):
|
||||
return send_from_directory(os.path.join('static', 'components'), path)
|
||||
|
||||
@app.route('/custom-components/<path:path>')
|
||||
def custom_components(path):
|
||||
return send_from_directory(os.path.join('static', 'custom-components'), path)
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
<!--
|
||||
@license
|
||||
Copyright (c) 2017 Vaadin Ltd.
|
||||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
|
||||
-->
|
||||
|
||||
<link rel="import" href="../components/polymer/polymer-element.html">
|
||||
<link rel="import" href="../components/vaadin-themable-mixin/vaadin-themable-mixin.html">
|
||||
<link rel="import" href="../components/vaadin-progress-bar/src/vaadin-progress-bar.html">
|
||||
<link rel="import" href="../components/vaadin-upload/src/vaadin-upload-icons.html">
|
||||
<link rel="import" href="../components/clipboard-copy/clipboard-copy.html">
|
||||
<link rel="import" href="../components/paper-tooltip/paper-tooltip.html">
|
||||
|
||||
<dom-module id="vaadin-permalinked-upload-file">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<style include="lumo-field-button">
|
||||
:host {
|
||||
padding: var(--lumo-space-s) 0;
|
||||
}
|
||||
|
||||
:host(:not(:first-child)) {
|
||||
border-top: 1px solid var(--lumo-contrast-10pct);
|
||||
}
|
||||
|
||||
[part="row"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
[part="status"],
|
||||
[part="error"] {
|
||||
color: var(--lumo-secondary-text-color);
|
||||
font-size: var(--lumo-font-size-s);
|
||||
}
|
||||
|
||||
[part="info"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex: auto;
|
||||
}
|
||||
|
||||
[part="meta"] {
|
||||
width: 0.001px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
[part="name"] {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
[part="commands"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
[part="done-icon"],
|
||||
[part="warning-icon"] {
|
||||
margin-right: var(--lumo-space-xs);
|
||||
}
|
||||
|
||||
/* When both icons are hidden, let us keep space for one */
|
||||
[part="done-icon"][hidden] + [part="warning-icon"][hidden] {
|
||||
display: block !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
[part="done-icon"],
|
||||
[part="warning-icon"] {
|
||||
font-size: var(--lumo-icon-size-m);
|
||||
font-family: 'lumo-icons';
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
[part="start-button"],
|
||||
[part="retry-button"],
|
||||
[part="clear-button"],
|
||||
[part="copy-button"] {
|
||||
flex: none;
|
||||
margin-left: var(--lumo-space-xs);
|
||||
}
|
||||
|
||||
[part="done-icon"]::before,
|
||||
[part="warning-icon"]::before,
|
||||
[part="start-button"]::before,
|
||||
[part="retry-button"]::before,
|
||||
[part="clear-button"]::before,
|
||||
[part="copy-button"] i {
|
||||
vertical-align: -.25em;
|
||||
}
|
||||
|
||||
[part="done-icon"]::before {
|
||||
content: var(--lumo-icons-checkmark);
|
||||
color: var(--lumo-primary-text-color);
|
||||
}
|
||||
|
||||
[part="warning-icon"]::before {
|
||||
content: var(--lumo-icons-error);
|
||||
color: var(--lumo-error-text-color);
|
||||
}
|
||||
|
||||
[part="start-button"]::before {
|
||||
content: var(--lumo-icons-play);
|
||||
}
|
||||
|
||||
[part="retry-button"]::before {
|
||||
content: var(--lumo-icons-reload);
|
||||
}
|
||||
|
||||
[part="clear-button"]::before {
|
||||
content: var(--lumo-icons-cross);
|
||||
}
|
||||
|
||||
[part="error"] {
|
||||
color: var(--lumo-error-text-color);
|
||||
}
|
||||
|
||||
[part="progress"] {
|
||||
width: auto;
|
||||
margin-left: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||
margin-right: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
|
||||
}
|
||||
|
||||
[part="progress"][complete],
|
||||
[part="progress"][error] {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div part="row">
|
||||
<div part="info">
|
||||
<div part="done-icon" hidden$="[[!file.complete]]"></div>
|
||||
<div part="warning-icon" hidden$="[[!file.error]]"></div>
|
||||
<slot name="custom-icon"></slot>
|
||||
|
||||
<div part="meta">
|
||||
<div part="name" id="name">[[file.name]]</div>
|
||||
<div part="status" hidden$="[[!file.url]]" id="status">Avalible at <a href="[[file.url]]">[[file.url]]</a></div>
|
||||
<div part="error" id="error" hidden$="[[!file.error]]">[[file.error]]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div part="commands">
|
||||
<div part="start-button" file-event="file-start" on-click="_fireFileEvent" hidden$="[[!file.held]]"></div>
|
||||
<div part="retry-button" file-event="file-retry" on-click="_fireFileEvent" hidden$="[[!file.error]]"></div>
|
||||
<div part="clear-button" file-event="file-abort" on-click="_fireFileEvent" hidden$="[[file.complete]]"></div>
|
||||
<div part="copy-button" on-click="_copyUrl" hidden$="[[!file.complete]]">
|
||||
<clipboard-copy id="clipboardomatic" content="[[file.url]]"></clipboard-copy>
|
||||
<i class="material-icons">file_copy</i>
|
||||
</div>
|
||||
</div>
|
||||
<paper-tooltip id="copied_notif" for="copy-button" position="right">Copied to clipboard</paper-tooltip>
|
||||
</div>
|
||||
|
||||
<vaadin-progress-bar
|
||||
part="progress"
|
||||
id="progress"
|
||||
value$="[[_formatProgressValue(file.progress)]]"
|
||||
error$="[[file.error]]"
|
||||
indeterminate$="[[file.indeterminate]]"
|
||||
uploading$="[[file.uploading]]"
|
||||
complete$="[[file.complete]]"
|
||||
hidden$="[[file.complete]]">
|
||||
</vaadin-progress-bar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* `<vaadin-permalinked-upload-file>` element represents a file in the file list of `<vaadin-upload>`.
|
||||
*
|
||||
* ### Styling
|
||||
*
|
||||
* The following shadow DOM parts are available for styling:
|
||||
*
|
||||
* Part name | Description
|
||||
* ---|---
|
||||
* `row` | File container
|
||||
* `info` | Container for file status icon, file name, status and error messages
|
||||
* `done-icon` | File done status icon
|
||||
* `warning-icon` | File warning status icon
|
||||
* `meta` | Container for file name, status and error messages
|
||||
* `name` | File name
|
||||
* `error` | Error message, shown when error happens
|
||||
* `status` | Status message
|
||||
* `commands` | Container for file command icons
|
||||
* `start-button` | Start file upload button
|
||||
* `retry-button` | Retry file upload button
|
||||
* `clear-button` | Clear file button
|
||||
* `progress`| Progress bar
|
||||
*
|
||||
* The following state attributes are available for styling:
|
||||
*
|
||||
* Attribute | Description | Part name
|
||||
* ---|---|---
|
||||
* `error` | An error has happened during uploading | `:host`
|
||||
* `indeterminate` | Uploading is in progress, but the progress value is unknown | `:host`
|
||||
* `uploading` | Uploading is in progress | `:host`
|
||||
* `complete` | Uploading has finished successfully | `:host`
|
||||
*
|
||||
* See [ThemableMixin – how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki)
|
||||
*
|
||||
* @memberof Vaadin
|
||||
* @mixes Vaadin.ThemableMixin
|
||||
* @demo demo/index.html
|
||||
*/
|
||||
class PermalinkedUploadFileElement extends Vaadin.ThemableMixin(Polymer.Element) {
|
||||
static get is() {
|
||||
return 'vaadin-permalinked-upload-file';
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
file: Object
|
||||
};
|
||||
}
|
||||
|
||||
static get observers() {
|
||||
return [
|
||||
'_fileAborted(file.abort)',
|
||||
'_toggleHostAttribute(file.error, "error")',
|
||||
'_toggleHostAttribute(file.indeterminate, "indeterminate")',
|
||||
'_toggleHostAttribute(file.uploading, "uploading")',
|
||||
'_toggleHostAttribute(file.complete, "complete")',
|
||||
];
|
||||
}
|
||||
|
||||
_copyUrl(event) {
|
||||
var success = this.$.clipboardomatic.copy();
|
||||
if (success) {
|
||||
var notif = this.$.copied_notif;
|
||||
notif.show();
|
||||
window.setTimeout(function() {
|
||||
notif.hide();
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
_fileAborted(abort) {
|
||||
if (abort) {
|
||||
this._remove();
|
||||
}
|
||||
}
|
||||
|
||||
_remove() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('file-remove', {
|
||||
detail: {file: this.file},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_formatProgressValue(progress) {
|
||||
return progress / 100;
|
||||
}
|
||||
|
||||
_fireFileEvent(e) {
|
||||
e.preventDefault();
|
||||
return this.dispatchEvent(
|
||||
new CustomEvent(e.target.getAttribute('file-event'), {
|
||||
detail: {file: this.file},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
_toggleHostAttribute(value, attributeName) {
|
||||
const shouldHave = Boolean(value);
|
||||
const has = this.hasAttribute(attributeName);
|
||||
if (has !== shouldHave) {
|
||||
if (shouldHave) {
|
||||
this.setAttribute(attributeName, '');
|
||||
} else {
|
||||
this.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when the retry button is pressed. It is listened by `vaadin-upload`
|
||||
* which will start a new upload process of this file.
|
||||
*
|
||||
* @event file-retry
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to retry upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when the start button is pressed. It is listened by `vaadin-upload`
|
||||
* which will start a new upload process of this file.
|
||||
*
|
||||
* @event file-start
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to start upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired when abort button is pressed. It is listened by `vaadin-upload` which
|
||||
* will abort the upload in progress, but will not remove the file from the list
|
||||
* to allow the animation to hide the element to be run.
|
||||
*
|
||||
* @event file-abort
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to abort upload of
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fired after the animation to hide the element has finished. It is listened
|
||||
* by `vaadin-upload` which will actually remove the file from the upload
|
||||
* file list.
|
||||
*
|
||||
* @event file-remove
|
||||
* @param {Object} detail
|
||||
* @param {Object} detail.file file to remove from the upload of
|
||||
*/
|
||||
}
|
||||
|
||||
customElements.define(PermalinkedUploadFileElement.is, PermalinkedUploadFileElement);
|
||||
|
||||
/**
|
||||
* @namespace Vaadin
|
||||
*/
|
||||
window.Vaadin = window.Vaadin || {};
|
||||
Vaadin.PermalinkedUploadFileElement = PermalinkedUploadFileElement;
|
||||
})();
|
||||
</script>
|
||||
</dom-module>
|
|
@ -34,7 +34,10 @@ body {
|
|||
}
|
||||
|
||||
#upload_form {
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
}
|
||||
#upload_form form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
{% extends 'skel.html' %}
|
||||
{% block imports %}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<h1>
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{% extends '/skel.html' %}
|
||||
{% block imports %}
|
||||
<script src="{{ url_for('components', path='webcomponentsjs/webcomponents-loader.js') }}"></script>
|
||||
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-bind.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-repeat.html') }}">
|
||||
<link rel="import" href="{{ url_for('components', path='vaadin-upload/vaadin-upload.html') }}">
|
||||
<link rel="import" href="{{ url_for('custom_components', path='vaadin-permalinked-upload-file.html') }}">
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="mainwrapper">
|
||||
<h1>
|
||||
|
@ -7,9 +14,51 @@
|
|||
<p>
|
||||
Protect your memes from the evil clutches of @ItMeIRL.
|
||||
</p>
|
||||
<form id="upload_form" action="{{ url_for('meirl_upload')}}" method="POST" enctype="multipart/form-data">
|
||||
<input id="upload_input" name="meme" type="file" accept="image/*"></input>
|
||||
<button id="upload_button">Upload</button>
|
||||
</form>
|
||||
<div id="upload_form">
|
||||
<form class="noscript" action="{{ url_for('meirl_upload')}}" method="POST" enctype="multipart/form-data">
|
||||
<input id="upload_input" name="meme" type="file" accept="image/*"></input>
|
||||
<button id="upload_button">Upload</button>
|
||||
</form>
|
||||
<dom-bind>
|
||||
<template>
|
||||
<vaadin-upload id="file_upload" files="{%raw%}{{files}}{%endraw%}" target="{{ url_for('meirl_upload') }}?noredirect" method="POST" form-data-name="meme" accept="image/*" max-file-size="5000000">
|
||||
<div slot="drop-label-icon"></div>
|
||||
<span slot="drop-label" class="font-headline">or drag a file here (5MB maximum)</span>
|
||||
<div slot="file-list">
|
||||
<template is="dom-repeat" items="[[files]]" as="file">
|
||||
<vaadin-permalinked-upload-file file="[[file]]"></vaadin-permalinked-upload-file>
|
||||
</template>
|
||||
</div>
|
||||
</vaadin-upload>
|
||||
</template>
|
||||
</dom-bind>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script>
|
||||
function isES6()
|
||||
{
|
||||
try { Function("() => {};"); return true; }
|
||||
catch(exception) { return false; }
|
||||
}
|
||||
|
||||
window.addEventListener('WebComponentsReady', function() {
|
||||
// Remove noscript / dinosaur componenets if both webcomponents and es6 avalible
|
||||
if (isES6()){
|
||||
var noscripts = document.querySelectorAll('.noscript');
|
||||
for (var i = noscripts.length-1; i >= 0; i--){
|
||||
noscripts[i].parentNode.removeChild(noscripts[i]);
|
||||
}
|
||||
} else {
|
||||
// TODO: Abort polymer loading to fix IE become ing responsive for a bit
|
||||
}
|
||||
|
||||
var upload = document.querySelector('vaadin-upload#file_upload');
|
||||
|
||||
upload.addEventListener('upload-response', function(event) {
|
||||
event.detail.file.url = event.detail.xhr.response;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet" />
|
||||
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
|
||||
|
||||
{% block imports %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
Loading…
Reference in New Issue