mirror of
https://github.com/Bubka/2FAuth.git
synced 2025-08-10 14:17:36 +02:00
Set the uploader as a vue component with qrcode-reader as default
This commit is contained in:
@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||||||
use Zxing\QrReader;
|
use Zxing\QrReader;
|
||||||
use OTPHP\TOTP;
|
use OTPHP\TOTP;
|
||||||
use OTPHP\Factory;
|
use OTPHP\Factory;
|
||||||
|
use App\Classes\Options;
|
||||||
use Assert\AssertionFailedException;
|
use Assert\AssertionFailedException;
|
||||||
use Illuminate\Http\File;
|
use Illuminate\Http\File;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@ -21,6 +22,8 @@ class QrCodecontroller extends Controller
|
|||||||
public function decode(Request $request)
|
public function decode(Request $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
if(Options::get('useBasicQrcodeReader')) {
|
||||||
|
|
||||||
// input validation
|
// input validation
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'qrcode' => 'required|image',
|
'qrcode' => 'required|image',
|
||||||
@ -34,6 +37,15 @@ class QrCodecontroller extends Controller
|
|||||||
|
|
||||||
// delete uploaded file
|
// delete uploaded file
|
||||||
Storage::delete($path);
|
Storage::delete($path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
|
$this->validate($request, [
|
||||||
|
'uri' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$uri = $request->uri;
|
||||||
|
}
|
||||||
|
|
||||||
// return the OTP object
|
// return the OTP object
|
||||||
try {
|
try {
|
||||||
|
218
resources/js/components/QuickUploader.vue
Normal file
218
resources/js/components/QuickUploader.vue
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<template>
|
||||||
|
<div id="quick-uploader">
|
||||||
|
<!-- static landing UI -->
|
||||||
|
<div v-show="!(showStream && canStream)" class="container has-text-centered">
|
||||||
|
<div class="columns quick-uploader">
|
||||||
|
<!-- trailer phrase that invite to add an account -->
|
||||||
|
<div class="column is-full quick-uploader-header" :class="{ 'is-invisible' : !showTrailer }">
|
||||||
|
{{ $t('twofaccounts.no_account_here') }}<br>
|
||||||
|
{{ $t('twofaccounts.add_first_account') }}
|
||||||
|
</div>
|
||||||
|
<!-- add button -->
|
||||||
|
<div class="column is-full quick-uploader-button" >
|
||||||
|
<div class="quick-uploader-centerer">
|
||||||
|
<!-- scan button that launch camera stream -->
|
||||||
|
<label v-if="canStream" class="button is-link is-medium is-rounded is-focused" @click="enableStream()">
|
||||||
|
{{ $t('twofaccounts.forms.scan_qrcode') }}
|
||||||
|
</label>
|
||||||
|
<!-- or classic input field -->
|
||||||
|
<form v-else @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
|
||||||
|
<label :class="{'is-loading' : form.isBusy}" class="button is-link is-medium is-rounded is-focused">
|
||||||
|
<input v-if="$root.appSettings.useBasicQrcodeReader" class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput">
|
||||||
|
<qrcode-capture v-else @decode="uploadQrcode" class="file-input" ref="qrcodeInput" />
|
||||||
|
{{ $t('twofaccounts.forms.use_qrcode.val') }}
|
||||||
|
</label>
|
||||||
|
<field-error :form="form" field="qrcode" />
|
||||||
|
<field-error :form="form" field="uri" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Fallback link to classic form -->
|
||||||
|
<div class="column is-full quick-uploader-footer">
|
||||||
|
<router-link :to="{ name: 'create' }" class="is-link">{{ $t('twofaccounts.use_full_form') }}</router-link>
|
||||||
|
</div>
|
||||||
|
<div v-if="showError" class="column is-full quick-uploader-footer">
|
||||||
|
<notification :message="errorText" :isDeletable="false" type="is-danger" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- camera stream fullscreen scanner -->
|
||||||
|
<div v-show="showStream && canStream">
|
||||||
|
<div class="fullscreen-alert has-text-centered">
|
||||||
|
<span class="is-size-4 has-text-light">
|
||||||
|
<font-awesome-icon :icon="['fas', 'spinner']" size="2x" spin />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="fullscreen-streamer">
|
||||||
|
<qrcode-stream @decode="uploadQrcode" @init="onStreamerInit" :camera="camera" />
|
||||||
|
</div>
|
||||||
|
<div class="fullscreen-footer">
|
||||||
|
<!-- Cancel button -->
|
||||||
|
<label class="button is-large is-warning is-rounded" @click="disableStream()">
|
||||||
|
{{ $t('commons.cancel') }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import Form from './Form'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'QuickUploader',
|
||||||
|
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
form: new Form({
|
||||||
|
qrcode: null,
|
||||||
|
uri: '',
|
||||||
|
}),
|
||||||
|
errorName: '',
|
||||||
|
errorText: '',
|
||||||
|
showStream: false,
|
||||||
|
canStream: true,
|
||||||
|
camera: 'auto',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
debugMode: function() {
|
||||||
|
return process.env.NODE_ENV
|
||||||
|
},
|
||||||
|
|
||||||
|
showError: function() {
|
||||||
|
return this.debugMode == 'development' && this.errorName == 'NotAllowedError'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
showTrailer: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
directStreaming: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
if( this.$root.appSettings.useBasicQrcodeReader ) {
|
||||||
|
// User has set the basic QR code reader so we disable the modern one
|
||||||
|
this.canStream = this.showStream = false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if( this.directStreaming ) {
|
||||||
|
this.enableStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.form.clear()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
async enableStream() {
|
||||||
|
|
||||||
|
this.$parent.$emit('initStreaming')
|
||||||
|
|
||||||
|
this.camera = 'auto'
|
||||||
|
this.showStream = true
|
||||||
|
|
||||||
|
console.log('stream enabled')
|
||||||
|
},
|
||||||
|
|
||||||
|
async disableStream() {
|
||||||
|
|
||||||
|
this.camera = 'off'
|
||||||
|
this.showStream = false
|
||||||
|
|
||||||
|
this.$parent.$emit('stopStreaming')
|
||||||
|
},
|
||||||
|
|
||||||
|
async onStreamerInit (promise) {
|
||||||
|
|
||||||
|
this.errorText = ''
|
||||||
|
this.errorName = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
await promise
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
|
||||||
|
this.errorName = error.name
|
||||||
|
|
||||||
|
if (error.name === 'NotAllowedError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.need_grant_permission')
|
||||||
|
|
||||||
|
} else if (error.name === 'NotReadableError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.not_readable')
|
||||||
|
|
||||||
|
} else if (error.name === 'NotFoundError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.no_cam_on_device')
|
||||||
|
|
||||||
|
} else if (error.name === 'NotSupportedError' || error.name === 'InsecureContextError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.secured_context_required')
|
||||||
|
|
||||||
|
} else if (error.name === 'OverconstrainedError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.camera_not_suitable')
|
||||||
|
|
||||||
|
} else if (error.name === 'StreamApiNotSupportedError') {
|
||||||
|
this.errorText = this.$t('twofaccounts.stream.stream_api_not_supported')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setUploader()
|
||||||
|
},
|
||||||
|
|
||||||
|
setUploader() {
|
||||||
|
|
||||||
|
if( this.errorName ) {
|
||||||
|
this.canStream = false
|
||||||
|
console.log(this.errorText)
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !this.errorName && !this.showStream ) {
|
||||||
|
this.camera = 'off'
|
||||||
|
}
|
||||||
|
|
||||||
|
if( this.canStream && this.showStream) {
|
||||||
|
this.$parent.$emit('startStreaming')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async uploadQrcode(event) {
|
||||||
|
|
||||||
|
var response
|
||||||
|
|
||||||
|
if(this.$root.appSettings.useBasicQrcodeReader) {
|
||||||
|
let imgdata = new FormData();
|
||||||
|
imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]);
|
||||||
|
|
||||||
|
response = await this.form.upload('/api/qrcode/decode', imgdata)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We post the decoded URI instead of an image to decode
|
||||||
|
this.form.uri = event
|
||||||
|
|
||||||
|
if( !this.form.uri ) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await this.form.post('/api/qrcode/decode')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.push({ name: 'create', params: { qrAccount: response.data } });
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- show accounts list -->
|
||||||
<div class="container" v-if="this.showAccounts">
|
<div class="container" v-if="this.showAccounts">
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<div class="columns is-gapless is-mobile is-centered">
|
<div class="columns is-gapless is-mobile is-centered">
|
||||||
@ -32,8 +33,8 @@
|
|||||||
readyLabel: '',
|
readyLabel: '',
|
||||||
loadingLabel: 'refreshing'
|
loadingLabel: 'refreshing'
|
||||||
}" > -->
|
}" > -->
|
||||||
<draggable v-model="filteredAccounts" @start="drag = true" @end="saveOrder" ghost-class="ghost" handle=".tfa-dots" animation="200" class="accounts columns is-multiline is-centered">
|
<draggable v-model="filteredAccounts" @start="drag = true" @end="saveOrder" ghost-class="ghost" handle=".tfa-dots" animation="200" class="accounts">
|
||||||
<transition-group type="transition" :name="!drag ? 'flip-list' : null">
|
<transition-group class="columns is-multiline is-centered" type="transition" :name="!drag ? 'flip-list' : null">
|
||||||
<div class="tfa column is-narrow has-text-white" v-for="account in filteredAccounts" :key="account.id">
|
<div class="tfa column is-narrow has-text-white" v-for="account in filteredAccounts" :key="account.id">
|
||||||
<div class="tfa-container">
|
<div class="tfa-container">
|
||||||
<transition name="slideCheckbox">
|
<transition name="slideCheckbox">
|
||||||
@ -69,42 +70,17 @@
|
|||||||
</draggable>
|
</draggable>
|
||||||
<!-- </vue-pull-refresh> -->
|
<!-- </vue-pull-refresh> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- No account -->
|
<!-- Show uploader (because no account) -->
|
||||||
<div class="container has-text-centered" v-show="showQuickForm">
|
<quick-uploader v-if="showUploader" :directStreaming="accounts.length > 0" :showTrailer="accounts.length === 0" ref="QuickUploader"></quick-uploader>
|
||||||
<div class="columns is-mobile" :class="{ 'is-invisible' : this.accounts.length > 0}">
|
|
||||||
<div class="column quickform-header">
|
|
||||||
{{ $t('twofaccounts.no_account_here') }}<br>
|
|
||||||
{{ $t('twofaccounts.add_first_account') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
<form @submit.prevent="createAccount" @keydown="form.onKeydown($event)">
|
|
||||||
<div class="columns is-mobile no-account is-vcentered">
|
|
||||||
<div class="column has-text-centered">
|
|
||||||
<label :class="{'is-loading' : form.isBusy}" class="button is-link is-medium is-rounded is-focused">
|
|
||||||
<input class="file-input" type="file" accept="image/*" v-on:change="uploadQrcode" ref="qrcodeInput">
|
|
||||||
{{ $t('twofaccounts.forms.use_qrcode.val') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<field-error :form="form" field="qrcode" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="columns is-mobile">
|
|
||||||
<div class="column quickform-footer">
|
|
||||||
<router-link :to="{ name: 'create' }" class="is-link">{{ $t('twofaccounts.use_full_form') }}</router-link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- modal -->
|
<!-- modal -->
|
||||||
<modal v-model="ShowTwofaccountInModal">
|
<modal v-model="showTwofaccountInModal">
|
||||||
<twofaccount-show ref="TwofaccountShow" ></twofaccount-show>
|
<twofaccount-show ref="TwofaccountShow" ></twofaccount-show>
|
||||||
</modal>
|
</modal>
|
||||||
<!-- footer -->
|
<!-- footer -->
|
||||||
<vue-footer :showButtons="this.accounts.length > 0">
|
<vue-footer v-if="showFooter" :showButtons="accounts.length > 0">
|
||||||
<!-- New item buttons -->
|
<!-- New item buttons -->
|
||||||
<p class="control" v-if="!showQuickForm && !editMode">
|
<p class="control" v-if="!showUploader && !editMode">
|
||||||
<a class="button is-link is-rounded is-focus" @click="showQuickForm = true">
|
<a class="button is-link is-rounded is-focus" @click="showUploader = true">
|
||||||
<span>{{ $t('twofaccounts.new') }}</span>
|
<span>{{ $t('twofaccounts.new') }}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
<font-awesome-icon :icon="['fas', 'qrcode']" />
|
<font-awesome-icon :icon="['fas', 'qrcode']" />
|
||||||
@ -112,11 +88,11 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<!-- Manage button -->
|
<!-- Manage button -->
|
||||||
<p class="control" v-if="!showQuickForm && !editMode">
|
<p class="control" v-if="!showUploader && !editMode">
|
||||||
<a class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('twofaccounts.manage') }}</a>
|
<a class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('twofaccounts.manage') }}</a>
|
||||||
</p>
|
</p>
|
||||||
<!-- Done button -->
|
<!-- Done button -->
|
||||||
<p class="control" v-if="!showQuickForm && editMode">
|
<p class="control" v-if="!showUploader && editMode">
|
||||||
<a class="button is-success is-rounded" @click="setEditModeTo(false)">
|
<a class="button is-success is-rounded" @click="setEditModeTo(false)">
|
||||||
<span>{{ $t('twofaccounts.done') }}</span>
|
<span>{{ $t('twofaccounts.done') }}</span>
|
||||||
<span class="icon is-small">
|
<span class="icon is-small">
|
||||||
@ -125,8 +101,8 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<!-- Cancel QuickFormButton -->
|
<!-- Cancel QuickFormButton -->
|
||||||
<p class="control" v-if="showQuickForm">
|
<p class="control" v-if="showUploader && showFooter">
|
||||||
<a class="button is-dark is-rounded" @click="cancelQuickForm">
|
<a class="button is-dark is-rounded" @click="showUploader = false">
|
||||||
{{ $t('commons.cancel') }}
|
{{ $t('commons.cancel') }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -139,22 +115,21 @@
|
|||||||
|
|
||||||
import Modal from '../components/Modal'
|
import Modal from '../components/Modal'
|
||||||
import TwofaccountShow from '../components/TwofaccountShow'
|
import TwofaccountShow from '../components/TwofaccountShow'
|
||||||
import Form from './../components/Form'
|
import QuickUploader from './../components/QuickUploader'
|
||||||
import vuePullRefresh from 'vue-pull-refresh';
|
// import vuePullRefresh from 'vue-pull-refresh';
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
accounts : [],
|
accounts : [],
|
||||||
selectedAccounts: [],
|
selectedAccounts: [],
|
||||||
ShowTwofaccountInModal : false,
|
showTwofaccountInModal : false,
|
||||||
search: '',
|
search: '',
|
||||||
editMode: this.InitialEditMode,
|
editMode: this.InitialEditMode,
|
||||||
showQuickForm: false,
|
showUploader: false,
|
||||||
form: new Form({
|
showFooter: true,
|
||||||
qrcode: null
|
|
||||||
}),
|
|
||||||
drag: false,
|
drag: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -174,13 +149,14 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
showAccounts() {
|
showAccounts() {
|
||||||
return this.accounts.length > 0 && !this.showQuickForm ? true : false
|
return this.accounts.length > 0 && !this.showUploader ? true : false
|
||||||
},
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
props: ['InitialEditMode'],
|
props: ['InitialEditMode'],
|
||||||
|
|
||||||
created() {
|
mounted() {
|
||||||
|
|
||||||
this.fetchAccounts()
|
this.fetchAccounts()
|
||||||
|
|
||||||
@ -190,39 +166,30 @@
|
|||||||
this.$refs.TwofaccountShow.clearOTP()
|
this.$refs.TwofaccountShow.clearOTP()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// hide Footer when stream is on
|
||||||
|
this.$on('initStreaming', function() {
|
||||||
|
// this.showFooter = this.accounts.length > 0 ? false : true
|
||||||
|
this.showFooter = false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$on('stopStreaming', function() {
|
||||||
|
|
||||||
|
this.showUploader = this.accounts.length > 0 ? false : true
|
||||||
|
this.showFooter = true
|
||||||
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
TwofaccountShow,
|
TwofaccountShow,
|
||||||
'vue-pull-refresh': vuePullRefresh,
|
// 'vue-pull-refresh': vuePullRefresh,
|
||||||
|
QuickUploader,
|
||||||
draggable,
|
draggable,
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
onRefresh() {
|
|
||||||
var that = this
|
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(function () {
|
|
||||||
that.fetchAccounts()
|
|
||||||
resolve();
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async uploadQrcode(event) {
|
|
||||||
|
|
||||||
let imgdata = new FormData();
|
|
||||||
imgdata.append('qrcode', this.$refs.qrcodeInput.files[0]);
|
|
||||||
|
|
||||||
const { data } = await this.form.upload('/api/qrcode/decode', imgdata)
|
|
||||||
|
|
||||||
this.$router.push({ name: 'create', params: { qrAccount: data } });
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
fetchAccounts() {
|
fetchAccounts() {
|
||||||
this.accounts = []
|
this.accounts = []
|
||||||
this.selectedAccounts = []
|
this.selectedAccounts = []
|
||||||
@ -237,7 +204,7 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.showQuickForm = response.data.length === 0 ? true: false
|
this.showUploader = response.data.length === 0 ? true : false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -263,7 +230,7 @@
|
|||||||
this.axios.patch('/api/twofaccounts/reorder', {orderedIds: this.accounts.map(a => a.id)})
|
this.axios.patch('/api/twofaccounts/reorder', {orderedIds: this.accounts.map(a => a.id)})
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteAccount: function (id) {
|
deleteAccount(id) {
|
||||||
if(confirm(this.$t('twofaccounts.confirm.delete'))) {
|
if(confirm(this.$t('twofaccounts.confirm.delete'))) {
|
||||||
this.axios.delete('/api/twofaccounts/' + id)
|
this.axios.delete('/api/twofaccounts/' + id)
|
||||||
|
|
||||||
@ -299,10 +266,6 @@
|
|||||||
this.$parent.showToolbar = state
|
this.$parent.showToolbar = state
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelQuickForm() {
|
|
||||||
this.form.clear()
|
|
||||||
this.showQuickForm = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter (to, from, next) {
|
||||||
|
51
resources/sass/app.scss
vendored
51
resources/sass/app.scss
vendored
@ -167,6 +167,30 @@ a:hover {
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fullscreen-streamer {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-alert {
|
||||||
|
position: fixed;
|
||||||
|
top: 25vh;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-footer {
|
||||||
|
position: fixed;
|
||||||
|
top: calc(100vh - 8rem );
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.has-ellipsis {
|
.has-ellipsis {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -320,27 +344,38 @@ footer .field.is-grouped {
|
|||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quickform-header {
|
.quick-uploader {
|
||||||
height: 20vh;
|
flex-direction: column
|
||||||
padding-top: 2rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.quickform-footer {
|
.quick-uploader-header {
|
||||||
padding-top: 3rem;
|
padding-top: 7vh;
|
||||||
|
padding-bottom: 7vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview {
|
.preview {
|
||||||
margin-top: 20vh;
|
margin-top: 20vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-account {
|
.quick-uploader-button {
|
||||||
height: 256px;
|
height: 256px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-account::before {
|
.quick-uploader-centerer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
height: 256px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-uploader-button::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
opacity: 0.05;
|
opacity: 0.05;
|
||||||
|
Reference in New Issue
Block a user