Basic frontend uploader component
This commit is contained in:
parent
e327a0bada
commit
fb430e76e9
@ -1,6 +1,7 @@
|
||||
from wtforms.fields import BooleanField, DecimalField, StringField
|
||||
from wtforms.fields import BooleanField, DecimalField, FileField, StringField
|
||||
from wtforms.fields.html5 import DateField
|
||||
from wtforms.validators import Required, Optional
|
||||
from flask_wtf.file import FileAllowed
|
||||
|
||||
from .forms import BaseForm
|
||||
from atst.utils.localization import translate
|
||||
@ -12,6 +13,13 @@ class TaskOrderForm(BaseForm):
|
||||
description=translate("forms.task_order.number_description"),
|
||||
validators=[Required()],
|
||||
)
|
||||
pdf = FileField(
|
||||
None,
|
||||
validators=[
|
||||
FileAllowed(["pdf"], translate("forms.task_order.file_format_not_allowed"))
|
||||
],
|
||||
render_kw={"accept": ".pdf,application/pdf"},
|
||||
)
|
||||
|
||||
|
||||
class FundingForm(BaseForm):
|
||||
|
@ -9,6 +9,7 @@ import levelofwarrant from '../levelofwarrant'
|
||||
import multicheckboxinput from '../multi_checkbox_input'
|
||||
import optionsinput from '../options_input'
|
||||
import textinput from '../text_input'
|
||||
import uploadinput from '../upload_input'
|
||||
import toggler from '../toggler'
|
||||
|
||||
export default {
|
||||
@ -23,6 +24,7 @@ export default {
|
||||
optionsinput,
|
||||
textinput,
|
||||
toggler,
|
||||
uploadinput,
|
||||
},
|
||||
mixins: [FormMixin],
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export default {
|
||||
const pdf = this.initialData
|
||||
|
||||
return {
|
||||
showUpload: !pdf || this.uploadErrors.length > 0,
|
||||
attachment: pdf || null,
|
||||
}
|
||||
},
|
||||
|
||||
@ -37,5 +37,24 @@ export default {
|
||||
showUploadInput: function() {
|
||||
this.showUpload = true
|
||||
},
|
||||
addAttachment: function(e) {
|
||||
this.attachment = e.target.value
|
||||
},
|
||||
removeAttachment: function(e) {
|
||||
e.preventDefault()
|
||||
this.attachment = null
|
||||
this.$refs.attachmentInput.value = null
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
baseName: function() {
|
||||
if (this.attachment) {
|
||||
return this.attachment.split(/[\\/]/).pop()
|
||||
}
|
||||
},
|
||||
hasAttachment: function() {
|
||||
return !!this.attachment
|
||||
},
|
||||
},
|
||||
}
|
||||
|
1
static/icons/check-circle-solid.svg
Normal file
1
static/icons/check-circle-solid.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>
|
After Width: | Height: | Size: 600 B |
@ -23,6 +23,7 @@
|
||||
@import "elements/graphs";
|
||||
@import "elements/menu";
|
||||
@import "elements/card";
|
||||
@import "elements/uploader";
|
||||
|
||||
@import "components/accordion_table";
|
||||
@import "components/topbar";
|
||||
|
48
styles/elements/_uploader.scss
Normal file
48
styles/elements/_uploader.scss
Normal file
@ -0,0 +1,48 @@
|
||||
.upload-widget {
|
||||
label.upload-label {
|
||||
text-align: right;
|
||||
border: 1px solid black;
|
||||
padding: 0;
|
||||
display: block;
|
||||
|
||||
.upload-button {
|
||||
padding: 1rem 1.5rem;
|
||||
display: inline-block;
|
||||
background-color: $color-blue;
|
||||
border: 1px solid $color-blue;
|
||||
margin: -1px;
|
||||
color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-blue-darker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.uploaded-file {
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
|
||||
svg * {
|
||||
fill: $color-green;
|
||||
}
|
||||
}
|
||||
|
||||
.uploaded-file__name {
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
font-weight: $font-bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.uploaded-file__remove {
|
||||
vertical-align: middle;
|
||||
margin-left: 2rem;
|
||||
font-size: $small-font-size;
|
||||
}
|
||||
}
|
@ -1,24 +1,37 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
|
||||
{% macro UploadInput(field, show_label=False) -%}
|
||||
<uploadinput inline-template v-bind:initial-data='{{ field.data | tojson }}' v-bind:upload-errors='{{ field.errors | list }}'>
|
||||
<div>
|
||||
<template v-if="showUpload">
|
||||
<div class="usa-input {% if field.errors %} usa-input--error {% endif %}">
|
||||
{% if show_label %}
|
||||
{{ field.label }}
|
||||
{% endif %}
|
||||
{{ field.description }}
|
||||
{{ field }}
|
||||
{% for error in field.errors %}
|
||||
<span class="usa-input__message">{{error}}</span>
|
||||
{% endfor %}
|
||||
<div v-show="hasAttachment" class="uploaded-file">
|
||||
{{ Icon("check-circle-solid") }}
|
||||
<span class="uploaded-file__name" v-html="baseName"></span>
|
||||
<a href="#" class="uploaded-file__remove" v-on:click="removeAttachment">Remove</a>
|
||||
</div>
|
||||
<div v-show="hasAttachment === false" class="usa-input {% if field.errors %} usa-input--error {% endif %}">
|
||||
{% if show_label %}
|
||||
{{ field.label }}
|
||||
{% endif %}
|
||||
{{ field.description }}
|
||||
<div class="upload-widget">
|
||||
<label class="upload-label" for="{{ field.name }}">
|
||||
<span class="upload-button">
|
||||
Browse
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
v-on:change="addAttachment"
|
||||
ref="attachmentInput"
|
||||
accept="{{ field.accept }}"
|
||||
id="{{ field.name }}"
|
||||
name="{{ field.name }}"
|
||||
aria-label="Task Order Upload"
|
||||
type="file">
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>Uploaded {{ field.data.filename }}</p>
|
||||
<div>
|
||||
<button type="button" v-on:click="showUploadInput">Change</button>
|
||||
</div>
|
||||
</template>
|
||||
{% for error in field.errors %}
|
||||
<span class="usa-input__message">{{error}}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</uploadinput>
|
||||
{%- endmacro %}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
{% from 'components/save_button.html' import SaveButton %}
|
||||
{% from 'components/text_input.html' import TextInput %}
|
||||
{% from 'components/upload_input.html' import UploadInput %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col task-order-form">
|
||||
@ -11,7 +12,7 @@
|
||||
{% include "portfolios/header.html" %}
|
||||
{% endblock %}
|
||||
<base-form inline-template>
|
||||
<form id="new-task-order" action='{{ url_for("task_orders.update", portfolio_id=portfolio.id) }}' method="POST" autocomplete="off">
|
||||
<form id="new-task-order" action='{{ url_for("task_orders.update", portfolio_id=portfolio.id) }}' method="POST" autocomplete="off" enctype="multipart/form-data">
|
||||
{{ form.csrf_token }}
|
||||
<div class="panel__content">
|
||||
<!-- TODO: implement save bar with component -->
|
||||
@ -28,6 +29,7 @@
|
||||
{{ "task_orders.new.form_help_text" | translate }}
|
||||
<hr>
|
||||
{{ TextInput(form.number, validation='taskOrderNumber') }}
|
||||
{{ UploadInput(form.pdf) }}
|
||||
</div>
|
||||
</form>
|
||||
</base-form>
|
||||
|
Loading…
x
Reference in New Issue
Block a user