瀏覽代碼

Add a Light theme and a theme detection/selection feature

Bubka 2 年之前
父節點
當前提交
eadebb41ac

+ 1 - 0
app/Http/Controllers/SinglePageController.php

@@ -20,6 +20,7 @@ class SinglePageController extends Controller
         $subdir = config('2fauth.config.appSubdirectory') ? '/' . config('2fauth.config.appSubdirectory') : '';
 
         return view('landing')->with([
+            'theme'       => Settings::get('theme'),
             'appSettings' => Settings::all()->toJson(),
             'appConfig'   => collect([
                 'proxyAuth'      => config('auth.defaults.guard') === 'reverse-proxy-guard' ? true : false,

+ 1 - 0
config/2fauth.php

@@ -71,6 +71,7 @@ return [
         'checkForUpdate' => true,
         'lastRadarScan' => 0,
         'latestRelease' => false,
+        'theme' => 'dark',
     ],
 
 ];

File diff suppressed because it is too large
+ 0 - 0
public/css/app.css


File diff suppressed because it is too large
+ 0 - 0
public/js/app.js


+ 0 - 9
public/js/app.js.LICENSE.txt

@@ -1,12 +1,3 @@
-/*
- * [hi-base32]{@link https://github.com/emn178/hi-base32}
- *
- * @version 0.5.0
- * @author Chen, Yi-Cyuan [emn178@gmail.com]
- * @copyright Chen, Yi-Cyuan 2015-2018
- * @license MIT
- */
-
 /*!
  * Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com
  * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)

+ 2 - 2
public/mix-manifest.json

@@ -1,6 +1,6 @@
 {
-    "/js/app.js": "/js/app.js?id=240f28920699c7791d10dc3255e51edd",
+    "/js/app.js": "/js/app.js?id=31a3cc6392bca913e0ffd620b73f373d",
     "/js/manifest.js": "/js/manifest.js?id=af5ab3286fe62cebba2085465b83b8b5",
-    "/css/app.css": "/css/app.css?id=7a62b621b3fdb6e0e88a7230fdfbd075",
+    "/css/app.css": "/css/app.css?id=d169eec83285d15944c82fa6f28c8bb2",
     "/js/vendor.js": "/js/vendor.js?id=5b9def2003ec2c6749359957e3a52c0e"
 }

+ 26 - 1
resources/js/app.js

@@ -18,7 +18,32 @@ const app = new Vue({
         appSettings: window.appSettings,
         appConfig: window.appConfig,
         isDemoApp: window.isDemoApp,
-        isTestingApp: window.isTestingApp
+        isTestingApp: window.isTestingApp,
+        prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
+    },
+
+    computed: {
+        showDarkMode: function() {
+            return this.appSettings.theme == 'dark' ||
+                (this.appSettings.theme == 'system' && this.prefersDarkScheme)
+        }
+    },
+
+    mounted () {
+        this.mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
+        this.$nextTick(() => {
+            this.mediaQueryList.addEventListener('change', this.setDarkScheme)
+        })
+    },
+
+    beforeDestroy () {
+        this.mediaQueryList.removeEventListener('change', this.setDarkScheme)
+    },
+
+    methods: {
+        setDarkScheme ({ matches }) {
+            this.prefersDarkScheme = matches
+        }
     },
     i18n,
     router,

+ 1 - 1
resources/js/components/Footer.vue

@@ -1,5 +1,5 @@
 <template>
-    <footer class="has-background-black-ter">
+    <footer>
         <div class="columns is-gapless" v-if="showButtons">
             <div class="column has-text-centered">
                 <div class="field is-grouped">

+ 5 - 5
resources/js/components/FormPasswordField.vue

@@ -26,14 +26,14 @@
         <div v-if="showRules" class="columns is-mobile is-size-7 mt-0">
             <div class="column is-one-third">
                 <span class="has-text-weight-semibold">{{ $t("auth.forms.mandatory_rules") }}</span><br />
-                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
+                <span class="is-underscored" :class="{'is-dot' : IsLongEnough}"></span>{{ $t('auth.forms.is_long_enough') }}<br/>
             </div>
             <div class="column">
                 <span class="has-text-weight-semibold">{{ $t("auth.forms.optional_rules_you_should_follow") }}</span><br />
-                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
-                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
-                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
-                <span class="is-underscored" :class="{'has-background-success-dark is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
+                <span class="is-underscored" :class="{'is-dot' : hasLowerCase}"></span>{{ $t('auth.forms.has_lower_case') }}<br/>
+                <span class="is-underscored" :class="{'is-dot' : hasUpperCase}"></span>{{ $t('auth.forms.has_upper_case') }}<br/>
+                <span class="is-underscored" :class="{'is-dot' : hasSpecialChar}"></span>{{ $t('auth.forms.has_special_char') }}<br/>
+                <span class="is-underscored" :class="{'is-dot' : hasNumber}"></span>{{ $t('auth.forms.has_number') }}
             </div>
         </div>
     </div> 

+ 5 - 2
resources/js/components/FormToggle.vue

@@ -5,12 +5,15 @@
             <button 
                 role="radio" 
                 type="button"
-                class="button is-dark" 
+                class="button" 
                 :aria-checked="form[fieldName] === choice.value"
                 :disabled="isDisabled" 
                 v-for="(choice, index) in choices" 
                 :key="index" 
-                :class="{ 'is-link' : form[fieldName] === choice.value }" 
+                :class="{
+                    'is-link' : form[fieldName] === choice.value,
+                    'is-dark' : $root.showDarkMode
+                }" 
                 v-on:click.stop="setRadio(choice.value)"
             >
                 <input 

+ 2 - 2
resources/js/components/Modal.vue

@@ -5,7 +5,7 @@
             <section class="section">
                 <div class="columns is-centered">
                     <div class="column is-three-quarters">
-                        <div class="box has-text-centered has-background-black-ter is-shadowless">
+                        <div class="modal-slot box has-text-centered is-shadowless">
                             <slot></slot>
                         </div>
                     </div>
@@ -14,7 +14,7 @@
         </div>
         <div v-if="this.showcloseButton" class="fullscreen-footer">
             <!-- Close button -->
-            <button ref="closeModalButton" class="button is-dark is-rounded" @click.stop="closeModal">
+            <button ref="closeModalButton" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="closeModal">
                 {{ $t('commons.close') }}
             </button>
         </div>

+ 3 - 3
resources/js/components/OtpDisplayer.vue

@@ -3,10 +3,10 @@
         <figure class="image is-64x64" :class="{ 'no-icon': !internal_icon }" style="display: inline-block">
             <img :src="$root.appConfig.subdirectory + '/storage/icons/' + internal_icon" v-if="internal_icon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
         </figure>
-        <p class="is-size-4 has-text-grey-light has-ellipsis">{{ internal_service }}</p>
-        <p class="is-size-6 has-text-grey has-ellipsis">{{ internal_account }}</p>
+        <p class="is-size-4 has-ellipsis" :class="$root.showDarkMode ? 'has-text-grey-light' : 'has-text-grey'">{{ internal_service }}</p>
+        <p class="is-size-6 has-ellipsis" :class="$root.showDarkMode ? 'has-text-grey' : 'has-text-grey-light'">{{ internal_account }}</p>
         <p>
-            <span role="log" ref="otp" tabindex="0" class="otp is-size-1 has-text-white is-clickable px-3" @click="copyOTP(internal_password)" @keyup.enter="copyOTP(internal_password)" :title="$t('commons.copy_to_clipboard')">
+            <span role="log" ref="otp" tabindex="0" class="otp is-size-1 is-clickable px-3" :class="$root.showDarkMode ? 'has-text-white' : 'has-text-grey-dark'" @click="copyOTP(internal_password)" @keyup.enter="copyOTP(internal_password)" :title="$t('commons.copy_to_clipboard')">
                 {{ displayedOtp }}
             </span>
         </p>

+ 1 - 1
resources/js/components/SettingTabs.vue

@@ -1,5 +1,5 @@
 <template>
-    <div class="options-header has-background-black-ter">
+    <div class="options-header">
         <responsive-width-wrapper>
             <div class="tabs is-centered is-fullwidth">
                 <ul>

+ 4 - 0
resources/js/mixins.js

@@ -97,6 +97,10 @@ Vue.mixin({
             // url
             // week
         },
+
+        setTheme(theme) {
+            document.documentElement.dataset.theme = theme;
+        }
     }
 
 })

+ 8 - 2
resources/js/packages/fontawesome.js

@@ -38,7 +38,10 @@ import {
     faEyeSlash,
     faExternalLinkAlt,
     faCamera,
-    faFileDownload
+    faFileDownload,
+    faSun,
+    faMoon,
+    faDesktop
 } from '@fortawesome/free-solid-svg-icons'
 
 import {
@@ -81,7 +84,10 @@ library.add(
     faEyeSlash,
     faExternalLinkAlt,
     faCamera,
-    faFileDownload
+    faFileDownload,
+    faSun,
+    faMoon,
+    faDesktop
 );
 
 Vue.component('font-awesome-icon', FontAwesomeIcon)

+ 9 - 9
resources/js/views/About.vue

@@ -2,10 +2,10 @@
     <responsive-width-wrapper>
         <h1 class="title has-text-grey-dark">{{ pagetitle }}</h1>
         <p class="block">
-            <span class="has-text-white"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br />
+            <span :class="$root.showDarkMode ? 'has-text-white':'has-text-black'"><span class="is-size-5">2FAuth</span> v{{ appVersion }}</span><br />
             {{ $t('commons.2fauth_teaser')}}
         </p>
-        <img src="logo.svg" style="height: 32px" alt="2FAuth logo" />
+        <img class="about-logo" src="logo.svg" alt="2FAuth logo" />
         <p class="block" :class="showUserOptions ? 'mb-5' : '' ">
             ©Bubka <a class="is-size-7" href="https://github.com/Bubka/2FAuth/blob/master/LICENSE">AGPL-3.0 license</a>
         </p>
@@ -13,25 +13,25 @@
             {{ $t('commons.resources') }}
         </h2>
         <div class="buttons">
-            <a class="button is-dark" href="https://github.com/Bubka/2FAuth" target="_blank">
+            <a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://github.com/Bubka/2FAuth" target="_blank">
                 <span class="icon is-small">
                     <font-awesome-icon :icon="['fab', 'github-alt']" />
                 </span>
                 <span>Github</span>
             </a>
-            <a class="button is-dark" href="https://docs.2fauth.app/" target="_blank">
+            <a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://docs.2fauth.app/" target="_blank">
                 <span class="icon is-small">
                     <font-awesome-icon :icon="['fas', 'book']" />
                 </span>
                 <span>Docs</span>
             </a>
-            <a class="button is-dark" href="https://demo.2fauth.app/" target="_blank">
+            <a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://demo.2fauth.app/" target="_blank">
                 <span class="icon is-small">
                     <font-awesome-icon :icon="['fas', 'flask']" />
                 </span>
                 <span>Demo</span>
             </a>
-            <a class="button is-dark" href="https://docs.2fauth.app/resources/rapidoc.html" target="_blank">
+            <a class="button" :class="{'is-dark' : $root.showDarkMode}" href="https://docs.2fauth.app/resources/rapidoc.html" target="_blank">
                 <span class="icon is-small">
                     <font-awesome-icon :icon="['fas', 'code']" />
                 </span>
@@ -51,7 +51,7 @@
         <h2 class="title is-5 has-text-grey-light">
             {{ $t('commons.environment') }}
         </h2>
-        <div class="box has-background-black-bis is-family-monospace is-size-7">
+        <div class="about-debug box is-family-monospace is-size-7">
             <button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listInfos.innerText" v-clipboard:success="clipboardSuccessHandler">
                 <font-awesome-icon :icon="['fas', 'copy']" />
             </button>
@@ -63,7 +63,7 @@
             <h2 class="title is-5 has-text-grey-light">
                 {{ $t('settings.user_options') }}
             </h2>
-            <div class="box has-background-black-bis is-family-monospace is-size-7">
+            <div class="about-debug box is-family-monospace is-size-7">
                 <button :aria-label="$t('commons.copy_to_clipboard')" class="button is-like-text is-pulled-right is-small is-text" v-clipboard="() => this.$refs.listUserOptions.innerText" v-clipboard:success="clipboardSuccessHandler">
                     <font-awesome-icon :icon="['fas', 'copy']" />
                 </button>
@@ -76,7 +76,7 @@
         <vue-footer :showButtons="true">
             <!-- close button -->
             <p class="control">
-                <router-link :to="{ name: 'accounts', params: { toRefresh: true } }" role="button" :aria-label="$t('commons.close_the_x_page', {pagetitle: pagetitle})" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
+                <router-link :to="{ name: 'accounts', params: { toRefresh: true } }" role="button" :aria-label="$t('commons.close_the_x_page', {pagetitle: pagetitle})" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
             </p>
         </vue-footer>
     </responsive-width-wrapper>

+ 22 - 22
resources/js/views/Accounts.vue

@@ -6,7 +6,7 @@
                 <div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
                     <div class="columns is-multiline">
                         <div class="column is-full" v-for="group in groups" v-if="group.twofaccounts_count > 0" :key="group.id">
-                            <button class="button is-fullwidth is-dark has-text-light is-outlined" @click="setActiveGroup(group.id)">{{ group.name }}</button>
+                            <button class="button is-fullwidth" :class="{'is-dark has-text-light is-outlined':$root.showDarkMode}" @click="setActiveGroup(group.id)">{{ group.name }}</button>
                         </div>
                     </div>
                     <div class="columns is-centered">
@@ -19,7 +19,7 @@
             <vue-footer :showButtons="true">
                 <!-- Close Group switch button -->
                 <p class="control">
-                    <button class="button is-dark is-rounded" @click="closeGroupSwitch()">{{ $t('commons.close') }}</button>
+                    <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="closeGroupSwitch()">{{ $t('commons.close') }}</button>
                 </p>
             </vue-footer>
         </div>
@@ -32,7 +32,7 @@
                 <div class="column is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
                     <div class="columns is-multiline">
                         <div class="column is-full" v-for="group in groups" :key="group.id">
-                            <button class="button is-fullwidth is-dark has-text-light is-outlined" :class="{ 'is-link' : moveAccountsTo === group.id}" @click="moveAccountsTo = group.id">
+                            <button class="button is-fullwidth" :class="{'is-link' : moveAccountsTo === group.id, 'is-dark has-text-light is-outlined':$root.showDarkMode}" @click="moveAccountsTo = group.id">
                                 <span v-if="group.id === 0" class="is-italic">
                                     {{ $t('groups.no_group') }}
                                 </span>
@@ -56,12 +56,12 @@
                 </p>
                 <!-- Cancel button -->
                 <p class="control">
-                    <button class="button is-dark is-rounded" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</button>
+                    <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="showGroupSelector = false">{{ $t('commons.cancel') }}</button>
                 </p>
             </vue-footer>
         </div>
         <!-- header -->
-        <div class="header has-background-black-ter" v-if="this.showAccounts || this.showGroupSwitch">
+        <div class="header" v-if="this.showAccounts || this.showGroupSwitch">
             <div class="columns is-gapless is-mobile is-centered">
                 <div class="column is-three-quarters-mobile is-one-third-tablet is-one-quarter-desktop is-one-quarter-widescreen is-one-quarter-fullhd">
                     <!-- search -->
@@ -79,19 +79,19 @@
                         <div class="columns">
                             <div class="column">
                                 <!-- selected label -->
-                                <span class="mr-1">{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}</span>
+                                <span class="has-text-grey mr-1">{{ selectedAccounts.length }}&nbsp;{{ $t('commons.selected') }}</span>
                                 <!-- deselect all -->
                                 <button @click="clearSelected" class="clear-selection delete mr-4" :style="{visibility: selectedAccounts.length > 0 ? 'visible' : 'hidden'}" :title="$t('commons.clear_selection')"></button>
                                 <!-- select all button -->
-                                <button @click="selectAll" class="button mr-5 has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.select_all')">
+                                <button @click="selectAll" class="button mr-5 has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.select_all')">
                                     <span>{{ $t('commons.all') }}</span>
                                     <font-awesome-icon class="ml-1" :icon="['fas', 'check-square']" />
                                 </button>
                                 <!-- sort asc/desc buttons -->
-                                <button @click="sortAsc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_ascending')">
+                                <button @click="sortAsc" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_ascending')">
                                     <font-awesome-icon :icon="['fas', 'sort-alpha-down']" />
                                 </button>
-                                <button @click="sortDesc" class="button has-line-height p-1 is-ghost has-background-black-ter has-text-grey" :title="$t('commons.sort_descending')">
+                                <button @click="sortDesc" class="button has-line-height p-1 is-ghost has-text-grey" :title="$t('commons.sort_descending')">
                                     <font-awesome-icon :icon="['fas', 'sort-alpha-up']" />
                                 </button>
                             </div>
@@ -101,13 +101,13 @@
                     <div v-else class="has-text-centered">
                         <div class="columns">
                             <div class="column" v-if="!showGroupSwitch">
-                                <button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
+                                <button :title="$t('groups.show_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : !$root.showDarkMode}" @click.stop="toggleGroupSwitch">
                                     {{ activeGroupName }} ({{ filteredAccounts.length }})&nbsp;
                                     <font-awesome-icon  :icon="['fas', 'caret-down']" />
                                 </button>
                             </div>
                             <div class="column" v-else>
-                                <button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" @click.stop="toggleGroupSwitch">
+                                <button :title="$t('groups.hide_group_selector')" tabindex="1" class="button is-text is-like-text" :class="{'has-text-grey' : !$root.showDarkMode}" @click.stop="toggleGroupSwitch">
                                     {{ $t('groups.select_accounts_to_show') }}
                                 </button>
                             </div>
@@ -131,12 +131,12 @@
                 }" > -->
                 <draggable v-model="filteredAccounts" @start="drag = true" @end="saveOrder" ghost-class="ghost" handle=".tfa-dots" animation="200" class="accounts">
                     <transition-group class="columns is-multiline" :class="{ 'is-centered': $root.appSettings.displayMode === 'grid' }" type="transition" :name="!drag ? 'flip-list' : null">
-                        <div :class="[$root.appSettings.displayMode === 'grid' ? 'tfa-grid' : 'tfa-list']" class="column is-narrow has-text-white" v-for="account in filteredAccounts" :key="account.id">
+                        <div :class="[$root.appSettings.displayMode === 'grid' ? 'tfa-grid' : 'tfa-list']" class="column is-narrow" v-for="account in filteredAccounts" :key="account.id">
                             <div class="tfa-container">
         	                    <transition name="slideCheckbox">
         	                        <div class="tfa-cell tfa-checkbox" v-if="editMode">
         	                            <div class="field">
-        	                                <input class="is-checkradio is-small is-white" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts">
+        	                                <input class="is-checkradio is-small" :class="$root.showDarkMode ? 'is-white':'is-info'" :id="'ckb_' + account.id" :value="account.id" type="checkbox" :name="'ckb_' + account.id" v-model="selectedAccounts">
         	                                <label tabindex="0" :for="'ckb_' + account.id" v-on:keypress.space.prevent="selectAccount(account.id)"></label>
         	                            </div>
         	                        </div>
@@ -151,10 +151,10 @@
         	                    <transition name="fadeInOut">
         	                        <div class="tfa-cell tfa-edit has-text-grey" v-if="editMode">
                                         <!-- <div class="tags has-addons"> -->
-                                            <router-link :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-dark is-rounded mr-1">
+                                            <router-link :to="{ name: 'editAccount', params: { twofaccountId: account.id }}" class="tag is-rounded mr-1" :class="$root.showDarkMode ? 'is-dark' : 'is-white'">
                                             {{ $t('commons.edit') }}
                                             </router-link>
-                                            <router-link :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-dark is-rounded" :title="$t('twofaccounts.show_qrcode')">
+                                            <router-link :to="{ name: 'showQRcode', params: { twofaccountId: account.id }}" class="tag is-rounded" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" :title="$t('twofaccounts.show_qrcode')">
                                                 <font-awesome-icon :icon="['fas', 'qrcode']" />
                                             </router-link>
                                        <!-- </div> -->
@@ -182,13 +182,13 @@
                 </p>
                 <!-- Manage button -->
                 <p class="control" v-if="!editMode">
-                    <button class="button is-dark is-rounded" @click="setEditModeTo(true)">{{ $t('commons.manage') }}</button>
+                    <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click="setEditModeTo(true)">{{ $t('commons.manage') }}</button>
                 </p>
                 <!-- move button -->
                 <p class="control" v-if="editMode">
                     <button 
-                        :disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded" 
-                        :class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'" 
+                        :disabled='selectedAccounts.length == 0' class="button is-rounded" 
+                        :class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']" 
                         @click="showGroupSelector = true"
                         :title="$t('groups.move_selected_to_group')" >
                             {{ $t('commons.move') }}
@@ -197,8 +197,8 @@
                 <!-- delete button -->
                 <p class="control" v-if="editMode">
                     <button 
-                        :disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded" 
-                        :class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'" 
+                        :disabled='selectedAccounts.length == 0' class="button is-rounded" 
+                        :class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']" 
                         @click="destroyAccounts" >
                             {{ $t('commons.delete') }}
                     </button>
@@ -206,8 +206,8 @@
                 <!-- export button -->
                 <p class="control" v-if="editMode">
                     <button 
-                        :disabled='selectedAccounts.length == 0' class="button is-outlined is-rounded" 
-                        :class="selectedAccounts.length > 0 ? 'is-link' : 'is-dark'" 
+                        :disabled='selectedAccounts.length == 0' class="button is-rounded" 
+                        :class="[{'is-outlined': $root.showDarkMode||selectedAccounts.length == 0}, selectedAccounts.length == 0 ? 'is-dark': 'is-link']" 
                         @click="exportAccounts" 
                         :title="$t('twofaccounts.export_selected_to_json')" >
                             {{ $t('commons.export') }}

+ 3 - 3
resources/js/views/Capture.vue

@@ -5,13 +5,13 @@
             <section class="section">
                 <div class="columns is-centered">
                     <div class="column is-three-quarters">
-                        <div class="box has-text-centered has-background-black-ter is-shadowless">
+                        <div class="modal-slot box has-text-centered is-shadowless">
                             <div v-if="errorText">
                                 <p class="block is-size-5">{{ $t('twofaccounts.stream.live_scan_cant_start') }}</p>
-                                <p class="has-text-light block">{{ $t('twofaccounts.stream.' + errorText + '.reason') }}</p>
+                                <p class="block" :class="{'has-text-light': $root.showDarkMode}">{{ $t('twofaccounts.stream.' + errorText + '.reason') }}</p>
                                 <p class="is-size-7">{{ $t('twofaccounts.stream.' + errorText + '.solution') }}</p>
                             </div>
-                            <span v-else class="is-size-4 has-text-light">
+                            <span v-else class="is-size-4" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'">
                                 <font-awesome-icon :icon="['fas', 'spinner']" size="2x" spin />
                             </span>
                         </div>

+ 3 - 3
resources/js/views/Groups.vue

@@ -12,10 +12,10 @@
             </router-link>
         </div>
         <div v-if="groups.length > 0">
-            <div v-for="group in groups" :key="group.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
+            <div v-for="group in groups" :key="group.id" class="group-item is-size-5 is-size-6-mobile">
                 {{ group.name }}
                 <!-- delete icon -->
-                <button class="button tag is-dark is-pulled-right" @click="deleteGroup(group.id)"  :title="$t('commons.delete')">
+                <button class="button tag is-pulled-right" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @click="deleteGroup(group.id)"  :title="$t('commons.delete')">
                     {{ $t('commons.delete') }}
                 </button>
                 <!-- edit link -->
@@ -37,7 +37,7 @@
         <vue-footer :showButtons="true">
             <!-- close button -->
             <p class="control">
-                <router-link  :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
+                <router-link  :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
             </p>
         </vue-footer>
     </responsive-width-wrapper>

+ 2 - 2
resources/js/views/Start.vue

@@ -23,7 +23,7 @@
             </div>
             <!-- alternative methods -->
             <div class="column is-full">
-                <div class="block has-text-light">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
+                <div class="block" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
                 <!-- upload a qr code -->
                 <div class="block has-text-link" v-if="!$root.appSettings.useBasicQrcodeReader">
                     <label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="$refs.qrcodeInputLabel.click()">
@@ -49,7 +49,7 @@
         <vue-footer :showButtons="true" >
             <!-- back button -->
             <p class="control" v-if="accountCount > 0">
-                <router-link class="button is-dark is-rounded" :to="{ name: returnToView }" >
+                <router-link class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" :to="{ name: returnToView }" >
                     {{ $t('commons.back') }}
                 </router-link>
             </p>

+ 1 - 1
resources/js/views/settings/Account.vue

@@ -37,7 +37,7 @@
         <vue-footer :showButtons="true">
             <!-- Cancel button -->
             <p class="control">
-                <button class="button is-dark is-rounded" @click.stop="exitSettings">
+                <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="exitSettings">
                     {{ $t('commons.close') }}
                 </button>
             </p>

+ 4 - 4
resources/js/views/settings/OAuth.vue

@@ -14,12 +14,12 @@
                     </a>
                 </div>
                 <div v-if="tokens.length > 0">
-                    <div v-for="token in tokens" :key="token.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
+                    <div v-for="token in tokens" :key="token.id" class="group-item is-size-5 is-size-6-mobile">
                         <font-awesome-icon v-if="token.value" class="has-text-success" :icon="['fas', 'check']" /> {{ token.name }}
                         <!-- revoke link -->
                         <div class="tags is-pulled-right">
-                            <button v-if="token.value" class="button tag" v-clipboard="() => token.value" v-clipboard:success="clipboardSuccessHandler">{{ $t('commons.copy') }}</button>
-                            <button class="button tag is-dark " @click="revokeToken(token.id)" :title="$t('settings.revoke')">{{ $t('settings.revoke') }}</button>
+                            <button v-if="token.value" class="button tag" :class="{'is-link': !$root.showDarkMode}" v-clipboard="() => token.value" v-clipboard:success="clipboardSuccessHandler">{{ $t('commons.copy') }}</button>
+                            <button class="button tag" :class="$root.showDarkMode ? 'is-dark':'is-white'" @click="revokeToken(token.id)" :title="$t('settings.revoke')">{{ $t('settings.revoke') }}</button>
                         </div>
                         <!-- edit link -->
                         <!-- <router-link :to="{ name: 'settings.oauth.editPAT' }" class="has-text-grey pl-1" :title="$t('commons.edit')">
@@ -47,7 +47,7 @@
                 <vue-footer :showButtons="true">
                     <!-- close button -->
                     <p class="control">
-                        <router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded" tabindex="0">{{ $t('commons.close') }}</router-link>
+                        <router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" tabindex="0">{{ $t('commons.close') }}</router-link>
                     </p>
                 </vue-footer>
             </form-wrapper>

+ 13 - 1
resources/js/views/settings/Options.vue

@@ -20,6 +20,8 @@
                     </div>
                     <!-- display mode -->
                     <form-toggle v-on:displayMode="saveSetting('displayMode', $event)" :choices="layouts" :form="form" fieldName="displayMode" :label="$t('settings.forms.display_mode.label')" :help="$t('settings.forms.display_mode.help')" />
+                    <!-- theme -->
+                    <form-toggle v-on:theme="saveSetting('theme', $event)" :choices="themes" :form="form" fieldName="theme" :label="$t('settings.forms.theme.label')" :help="$t('settings.forms.theme.help')" />
                     <!-- show icon -->
                     <form-checkbox v-on:showAccountsIcons="saveSetting('showAccountsIcons', $event)" :form="form" fieldName="showAccountsIcons" :label="$t('settings.forms.show_accounts_icons.label')" :help="$t('settings.forms.show_accounts_icons.help')" />
                     <!-- Official icons -->
@@ -56,7 +58,7 @@
         <vue-footer :showButtons="true">
             <!-- Cancel button -->
             <p class="control">
-                <button class="button is-dark is-rounded" @click.stop="exitSettings">
+                <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="exitSettings">
                     {{ $t('commons.close') }}
                 </button>
             </p>
@@ -104,11 +106,17 @@
                     rememberActiveGroup: true,
                     getOfficialIcons: null,
                     checkForUpdate: null,
+                    theme: 'dark',
                 }),
                 layouts: [
                     { text: this.$t('settings.forms.grid'), value: 'grid', icon: 'th' },
                     { text: this.$t('settings.forms.list'), value: 'list', icon: 'list' },
                 ],
+                themes: [
+                    { text: this.$t('settings.forms.light'), value: 'light', icon: 'sun' },
+                    { text: this.$t('settings.forms.dark'), value: 'dark', icon: 'moon' },
+                    { text: this.$t('settings.forms.automatic'), value: 'system', icon: 'desktop' },
+                ],
                 kickUserAfters: [
                     { text: this.$t('settings.forms.never'), value: '0' },
                     { text: this.$t('settings.forms.on_otp_copy'), value: '-1' },
@@ -199,6 +207,10 @@
                     }
                     else {
                         this.$root.appSettings[response.data.key] = response.data.value
+
+                        if(settingName === 'theme') {
+                            this.setTheme(response.data.value)
+                        }
                     }
                 })
             },

+ 3 - 3
resources/js/views/settings/WebAuthn.vue

@@ -15,10 +15,10 @@
                 </div>
                 <!-- credentials list -->
                 <div v-if="credentials.length > 0" class="field">
-                    <div v-for="credential in credentials" :key="credential.id" class="group-item has-text-light is-size-5 is-size-6-mobile">
+                    <div v-for="credential in credentials" :key="credential.id" class="group-item is-size-5 is-size-6-mobile">
                         {{ displayName(credential) }}
                         <!-- revoke link -->
-                        <button class="button tag is-dark is-pulled-right" @click="revokeCredential(credential.id)" :title="$t('settings.revoke')">
+                        <button class="button tag is-pulled-right" :class="$root.showDarkMode ? 'is-dark':'is-white'" @click="revokeCredential(credential.id)" :title="$t('settings.revoke')">
                             {{ $t('settings.revoke') }}
                         </button>
                         <!-- edit link -->
@@ -49,7 +49,7 @@
                 <vue-footer :showButtons="true">
                     <!-- close button -->
                     <p class="control">
-                        <router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-dark is-rounded">{{ $t('commons.close') }}</router-link>
+                        <router-link :to="{ name: 'accounts', params: { toRefresh: false } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}">{{ $t('commons.close') }}</router-link>
                     </p>
                 </vue-footer>
             </form-wrapper>

+ 4 - 4
resources/js/views/twofaccounts/Create.vue

@@ -66,7 +66,7 @@
                 <div class="field is-grouped">
                     <!-- i'm lucky button -->
                     <div class="control" v-if="$root.appSettings.getOfficialIcons">
-                        <v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 1">
+                        <v-button @click="fetchLogo" :color="$root.showDarkMode ? 'is-dark' : ''" :nativeType="'button'" :isDisabled="form.service.length < 1">
                             <span class="icon is-small">
                                 <font-awesome-icon :icon="['fas', 'globe']" />
                             </span>
@@ -75,7 +75,7 @@
                     </div>
                     <!-- upload button -->
                     <div class="control">
-                        <div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
+                        <div role="button" tabindex="0" class="file" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @keyup.enter="$refs.iconInputLabel.click()">
                             <label class="file-label" ref="iconInputLabel">
                                 <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
                                 <span class="file-cta">
@@ -85,7 +85,7 @@
                                     <span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
                                 </span>
                             </label>
-                            <span class="tag is-black is-large" v-if="tempIcon">
+                            <span class="tag is-large" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" v-if="tempIcon">
                                 <img class="icon-preview" :src="$root.appConfig.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
                                 <button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
                             </span>
@@ -149,7 +149,7 @@
             <div class="block">
                 {{ $t('errors.data_of_qrcode_is_not_valid_URI') }}
             </div>
-            <div class="block has-text-light mb-6" v-html="uri"></div>
+            <div class="block mb-6" :class="$root.showDarkMode ? 'has-text-light':'has-text-grey-dark'" v-html="uri"></div>
             <!-- Copy to clipboard -->
             <div class="block has-text-link">
                 <button class="button is-link is-outlined is-rounded" v-clipboard="() => uri" v-clipboard:success="clipboardSuccessHandler">

+ 7 - 7
resources/js/views/twofaccounts/Edit.vue

@@ -10,7 +10,7 @@
             <div class="field is-grouped">
                 <!-- i'm lucky button -->
                 <div class="control" v-if="$root.appSettings.getOfficialIcons">
-                    <v-button @click="fetchLogo" :color="'is-dark'" :nativeType="'button'" :isDisabled="form.service.length < 3">
+                    <v-button @click="fetchLogo" :color="$root.showDarkMode ? 'is-dark' : ''" :nativeType="'button'" :isDisabled="form.service.length < 3">
                         <span class="icon is-small">
                             <font-awesome-icon :icon="['fas', 'globe']" />
                         </span>
@@ -19,7 +19,7 @@
                 </div>
                 <!-- upload button -->
                 <div class="control">
-                    <div role="button" tabindex="0" class="file is-dark" @keyup.enter="$refs.iconInputLabel.click()">
+                    <div role="button" tabindex="0" class="file" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" @keyup.enter="$refs.iconInputLabel.click()">
                         <label class="file-label" ref="iconInputLabel">
                             <input aria-hidden="true" tabindex="-1" class="file-input" type="file" accept="image/*" v-on:change="uploadIcon" ref="iconInput">
                             <span class="file-cta">
@@ -29,7 +29,7 @@
                                 <span class="file-label">{{ $t('twofaccounts.forms.choose_image') }}</span>
                             </span>
                         </label>
-                        <span class="tag is-black is-large" v-if="tempIcon">
+                        <span class="tag is-large" :class="$root.showDarkMode ? 'is-dark' : 'is-white'" v-if="tempIcon">
                             <img class="icon-preview" :src="$root.appConfig.subdirectory + '/storage/icons/' + tempIcon" :alt="$t('twofaccounts.icon_to_illustrate_the_account')">
                             <button class="clear-selection delete is-small" @click.prevent="deleteIcon" :aria-label="$t('twofaccounts.remove_icon')"></button>
                         </span>
@@ -50,14 +50,14 @@
                         <input :id="this.inputId('text','secret')" class="input" type="text" v-model="form.secret" :disabled="secretIsLocked">
                     </p>
                     <p class="control" v-if="secretIsLocked">
-                        <button type="button" class="button is-dark field-lock" @click.stop="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
+                        <button type="button" class="button field-lock" :class="{'is-dark' : $root.showDarkMode}" @click.stop="secretIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
                             <span class="icon">
                                 <font-awesome-icon :icon="['fas', 'lock']" />
                             </span>
                         </button>
                     </p>
                     <p class="control" v-else>
-                        <button type="button" class="button is-dark field-unlock"  @click.stop="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
+                        <button type="button" class="button field-unlock" :class="{'is-dark' : $root.showDarkMode}" @click.stop="secretIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
                             <span class="icon has-text-danger">
                                 <font-awesome-icon :icon="['fas', 'lock-open']" />
                             </span>
@@ -89,14 +89,14 @@
                                 <input class="input" type="text" placeholder="" v-model="form.counter" :disabled="counterIsLocked" />
                             </div>
                             <div class="control" v-if="counterIsLocked">
-                                <button type="button" class="button is-dark field-lock" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
+                                <button type="button" class="button field-lock" :class="{'is-dark' : $root.showDarkMode}" @click="counterIsLocked = false" :title="$t('twofaccounts.forms.unlock.title')">
                                     <span class="icon">
                                         <font-awesome-icon :icon="['fas', 'lock']" />
                                     </span>
                                 </button>
                             </div>
                             <div class="control" v-else>
-                                <button type="button" class="button is-dark field-unlock"  @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
+                                <button type="button" class="button field-unlock" :class="{'is-dark' : $root.showDarkMode}" @click="counterIsLocked = true" :title="$t('twofaccounts.forms.lock.title')">
                                     <span class="icon has-text-danger">
                                         <font-awesome-icon :icon="['fas', 'lock-open']" />
                                     </span>

+ 14 - 31
resources/js/views/twofaccounts/Import.vue

@@ -39,43 +39,19 @@
                     <p class="help">{{ $t('twofaccounts.import.supported_formats_for_file_upload') }}</p>
                 </div>
                 <!-- Supported migration resources -->
-                <h5 class="title is-5 mb-3 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h5>
+                <h5 class="title is-6 mb-3 has-text-grey-dark">{{ $t('twofaccounts.import.supported_migration_formats') }}</h5>
                 <div class="field is-grouped is-grouped-multiline pt-0">
-                    <div class="control">
+                    <div v-for="(source, index) in supportedSources" :key="index" class="control">
                         <div class="tags has-addons">
-                        <span class="tag is-dark">2FAuth</span>
-                        <span class="tag is-black">JSON</span>
-                        </div>
-                    </div>
-                    <div class="control">
-                        <div class="tags has-addons">
-                        <span class="tag is-dark">Google Auth</span>
-                        <span class="tag is-black">{{ $t('twofaccounts.import.qr_code') }}</span>
-                        </div>
-                    </div>
-                    <div class="control">
-                        <div class="tags has-addons">
-                        <span class="tag is-dark">Aegis Auth</span>
-                        <span class="tag is-black">JSON</span>
-                        </div>
-                    </div>
-                    <div class="control">
-                        <div class="tags has-addons">
-                        <span class="tag is-dark">Aegis Auth</span>
-                        <span class="tag is-black">{{ $t('twofaccounts.import.plain_text') }}</span>
-                        </div>
-                    </div>
-                    <div class="control">
-                        <div class="tags has-addons">
-                        <span class="tag is-dark">2FAS Auth</span>
-                        <span class="tag is-black">JSON</span>
+                        <span class="tag" :class="$root.showDarkMode ? 'is-dark' : 'is-white'">{{ source.app }}</span>
+                        <span class="tag" :class="$root.showDarkMode ? 'is-black' : 'has-background-grey-lighter has-text-black'">{{ source.format }}</span>
                         </div>
                     </div>
                 </div>
                 <span class="is-size-7" v-html="$t('twofaccounts.import.do_not_set_password_or_encryption')"></span>
             </div>
             <div v-else>
-                <div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item has-text-light is-size-5 is-size-6-mobile">
+                <div v-for="(account, index) in exportedAccounts" :key="account.name" class="group-item is-size-5 is-size-6-mobile">
                     <div class="is-flex is-justify-content-space-between">
                         <!-- Account name -->
                         <div v-if="account.id > -2 && account.imported !== 0" class="is-flex-grow-1 has-ellipsis is-clickable" @click="previewAccount(index)" :title="$t('twofaccounts.import.generate_a_test_password')">
@@ -86,7 +62,7 @@
                         <!-- buttons -->
                         <div v-if="account.imported === -1" class="tags is-flex-wrap-nowrap">
                             <!-- discard button -->
-                            <button class="button tag is-dark has-text-grey-light" @click="discardAccount(index)"  :title="$t('twofaccounts.import.discard_this_account')">
+                            <button class="button tag" :class="{'is-dark has-text-grey-light' : $root.showDarkMode}" @click="discardAccount(index)"  :title="$t('twofaccounts.import.discard_this_account')">
                                 <font-awesome-icon :icon="['fas', 'trash']" />
                             </button>
                             <!-- import button -->
@@ -148,7 +124,7 @@
                 </p>
                 <!-- close button -->
                 <p class="control">
-                    <router-link  :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-dark is-rounded" v-html="importableCount > 0 ? $t('commons.cancel') : $t('commons.close')"></router-link>
+                    <router-link  :to="{ name: 'accounts', params: { toRefresh: true } }" class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" v-html="importableCount > 0 ? $t('commons.cancel') : $t('commons.close')"></router-link>
                 </p>
             </vue-footer>
         </responsive-width-wrapper>
@@ -184,6 +160,13 @@
                 }),
                 uploadForm: new Form(),
                 ShowTwofaccountInModal : false,
+                supportedSources: [
+                    {app: '2FAuth', format: 'JSON'},
+                    {app: 'Google Auth', format: this.$t('twofaccounts.import.qr_code')},
+                    {app: 'Aegis Auth', format: 'JSON'},
+                    {app: 'Aegis Auth', format: this.$t('twofaccounts.import.plain_text')},
+                    {app: '2FAS auth', format: 'JSON'},
+                ]
             }
         },
 

+ 1 - 1
resources/js/views/twofaccounts/QRcode.vue

@@ -8,7 +8,7 @@
         </div>
         <div class="fullscreen-footer">
             <!-- Close button -->
-            <button class="button is-dark is-rounded" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});">
+            <button class="button is-rounded" :class="{'is-dark' : $root.showDarkMode}" @click.stop="$router.push({name: 'accounts', params: {initialEditMode: true}});">
                 {{ $t('commons.close') }}
             </button>
         </div>

+ 7 - 0
resources/lang/en/settings.php

@@ -70,6 +70,13 @@ return [
         ],
         'grid' => 'Grid',
         'list' => 'List',
+        'theme' => [
+            'label' => 'Theme',
+            'help' => 'Force a specific theme or apply the theme defined in your system/browser preferences'
+        ],
+        'light' => 'Light',
+        'dark' => 'Dark',
+        'automatic' => 'Auto',
         'show_accounts_icons' => [
             'label' => 'Show icons',
             'help' => 'Show icons accounts in the main view'

+ 1 - 1
resources/lang/en/twofaccounts.php

@@ -156,7 +156,7 @@ return [
         'possible_duplicate' => 'An account with the exact same data already exists',
         'invalid_account' => '- invalid account -',
         'invalid_service' => '- invalid service -',
-        'do_not_set_password_or_encryption' => 'Do NOT set a password or encryption On when you export data from a 2FA app.',
+        'do_not_set_password_or_encryption' => 'Do NOT enable Password protection or Encryption when you export data (from a 2FA app) you want to import into 2FAuth.',
     ],
 
 ];

File diff suppressed because it is too large
+ 261 - 44
resources/sass/app.scss


+ 2 - 2
resources/views/landing.blade.php

@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html class="has-background-black-ter" lang="{!! $lang !!}">
+<html data-theme='{{ $theme }}' lang="{!! $lang !!}">
 <head>
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -19,7 +19,7 @@
 
     <link href="{!! $subdirectory !!}{{ mix('css/app.css') }}" rel="stylesheet">
 </head>
-<body class="has-text-lighter">
+<body>
     <div id="app">
         <app></app>
     </div>

Some files were not shown because too many files changed in this diff