瀏覽代碼

feat: implement new layout

pull/60/head
Tyler Chen 4 年之前
父節點
當前提交
a0aa1ae620
共有 15 個文件被更改,包括 323 次插入105 次删除
  1. +2
    -2
      src/_components/layout/Header.vue
  2. +56
    -14
      src/_components/layout/Sidebar.vue
  3. +1
    -1
      src/_pages/main/account/Devices.vue
  4. +1
    -1
      src/_pages/main/bubble/LaunchBubble.vue
  5. +2
    -2
      src/_pages/main/bubble/LaunchingBubble.vue
  6. +72
    -0
      src/_pages/main/bubble/MyBubble.vue
  7. +53
    -10
      src/_pages/main/bubble/Network.vue
  8. +117
    -0
      src/_pages/main/bubble/Networks.vue
  9. +6
    -21
      src/_router/index.js
  10. +7
    -7
      src/_store/account.module.js
  11. +2
    -2
      src/_store/system.module.js
  12. +2
    -2
      src/account/NetworkPage.vue
  13. +2
    -2
      src/account/profile/ActionPage.vue
  14. +0
    -22
      src/app/App.vue
  15. +0
    -19
      webpack.config.dev-example.js

+ 2
- 2
src/_components/layout/Header.vue 查看文件

@@ -33,7 +33,7 @@
</router-link>
</div>
<div v-else class="d-flex justify-content-center align-items-center">
<router-link to="/bubbles" class="d-flex align-items-center">
<router-link to="/bubble" class="d-flex align-items-center">
<Button headerLink>
{{ messages.label_menu_network }}
</Button>
@@ -104,7 +104,7 @@
</router-link>
</div>
<div v-else>
<router-link class="dropdown-item" to="/bubbles">
<router-link class="dropdown-item" to="/bubble">
{{ messages.label_menu_network }}
</router-link>
<router-link


+ 56
- 14
src/_components/layout/Sidebar.vue 查看文件

@@ -14,7 +14,7 @@
:class="{ 'menu-invisible': !menuVisible }"
>
<!-- Profile -->
<router-link to="/me">
<router-link to="/me" @click.native="closeMenu()">
<div class="profile">
<img
:src="`https://www.gravatar.com/avatar/${userHash}?r=pg`"
@@ -31,30 +31,61 @@

<!-- Navigation -->
<div class="navigation">
<div class="navigation-item active">
<router-link to="/bubble" class="navigation-item" @click.native="closeMenu()">
<i class="fa fa-home icon icon-home"></i>
<span>{{ messages.label_menu_network }}</span>
</div>
<div class="navigation-item">
</router-link>

<router-link
to="/devices"
v-if="!configs.sageLauncher"
class="navigation-item"
>
<i class="fa fa-tablet icon icon-devices"></i>
<span>{{ messages.label_menu_devices }}</span>
</div>
<div class="navigation-item">
</router-link>

<router-link
to="/apps"
v-if="!configs.sageLauncher"
class="navigation-item"
>
<i class="fa fa-smile icon icon-apps"></i>
<span>{{ messages.label_menu_apps }}</span>
</router-link>

<router-link
to="/admin/accounts"
v-if="!configs.sageLauncher"
class="navigation-item"
>
<i class="fa fa-users icon icon-users"></i>

<span>
{{ messages.label_menu_admin_users }}
</span>
</router-link>

<!-- settings -->
<!-- <router-link class="navigation-item">
<i class="fa fa-cog icon icon-settings"></i>
<span>{{ messages.label_menu_settings }}</span>
</div>
<div class="navigation-item">
</router-link> -->

<router-link to="/support" class="navigation-item" @click.native="closeMenu()">
<i class="fa fa-question-circle icon icon-help"></i>
<span>{{ messages.label_menu_help }}</span>
</div>
</router-link>
</div>
<!-- Upgrade Plan -->

<div class="flex-grow-1"></div>
<!-- Logout -->
<Button color="outline" class="logout-button">
{{ messages.log_out }}
</Button>
<router-link to="/logout" class="logout-button" @click.native="closeMenu()">
<Button color="outline" block>
{{ messages.log_out }}
</Button>
</router-link>
</div>
</aside>
</template>
@@ -171,7 +202,7 @@
margin-left: 20px;
}

&.active {
&.router-link-active {
background-color: #66cda4;
color: white;
border-radius: 20px;
@@ -195,6 +226,7 @@

.icon {
font-size: 20px;
width: 20px;
}

.icon-home {
@@ -203,6 +235,12 @@
.icon-devices {
color: #4b53df;
}
.icon-apps {
color: #9916df;
}
.icon-users {
color: #2ed1a1;
}
.icon-settings {
color: #e6458a;
}
@@ -229,7 +267,7 @@ export default {
}),

computed: {
...mapState('system', ['messages']),
...mapState('system', ['messages', 'configs']),

userHash() {
return md5(this.currentUser.email);
@@ -240,6 +278,10 @@ export default {
toggleMenu() {
this.menuVisible = !this.menuVisible;
},
closeMenu() {
console.log('click.nativeed');
this.menuVisible = false;
}
},
};
</script>

+ 1
- 1
src/_pages/main/account/Devices.vue 查看文件

@@ -1,6 +1,6 @@
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ -->
<template>
<div class="bubble-form">
<div>
<em v-if="loading && loading.devices">{{ messages.loading_devices }}</em>
<div v-if="devices && devices.length > 0">
<h2>{{ messages.table_title_devices }}</h2>


+ 1
- 1
src/_pages/main/bubble/LaunchBubble.vue 查看文件

@@ -260,7 +260,7 @@ export default {
newNodeNotification(nn) {
if (nn && nn.uuid) {
this.$router.push({
path: '/launching-bubble/' + nn.networkName,
path: '/bubble/' + nn.networkName,
});
this.submitted = false;
}


+ 2
- 2
src/_pages/main/bubble/LaunchingBubble.vue 查看文件

@@ -281,7 +281,7 @@ export default {
network(net) {
if (net) {
if (net.uuid === 'Not Found')
this.$router.replace({ path: '/bubbles' });
this.$router.replace({ path: '/bubble' });
this.networkUuid = net.uuid;
if (net.state !== 'stopping')
this.clearRefresherInterval(this.stopRefresher);
@@ -327,7 +327,7 @@ export default {

deletedNetworkUuid(uuid) {
if (uuid && this.networkUuid && uuid === this.networkUuid) {
this.$router.replace({ path: '/bubbles' });
this.$router.replace({ path: '/bubble' });
}
},



+ 72
- 0
src/_pages/main/bubble/MyBubble.vue 查看文件

@@ -0,0 +1,72 @@
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ -->
<template>
<div>
<em v-if="loading && loading.networks">{{ messages.loading_networks }}</em>

<div v-if="networks && networks.length > 0">
<!-- Show network list -->
<NetworkList />
</div>
<div v-else>
<!-- Show Launch Bubble Page -->
<LaunchBubble />
</div>
</div>
</template>

<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { util } from '~/_helpers';

import NetworkList from './Networks';
import LaunchBubble from './LaunchBubble';

export default {
components: {
NetworkList,
LaunchBubble,
},
computed: {
...mapState('networks', ['networks']),
...mapState('system', ['messages', 'configs']),
...mapState('users', ['policy']),
},

created() {
if (!this.configs.sageLauncher) {
this.$router.replace({ path: '/bubble/' + this.configs.networkUuid });
}

const user = util.currentUser();
const selectedLocale =
user !== null &&
typeof user.locale !== 'undefined' &&
user.locale !== null
? user.locale
: 'detect';
this.getAllNetworks({
userId: user.uuid,
messages: this.messages,
errors: this.errors,
});
this.loadMessages('post_auth', selectedLocale);
this.loadMessages('apps', selectedLocale);
},
methods: {
...mapActions('networks', [
'getAllNetworks',
'stopNetwork',
'deleteNetwork',
]),
...mapGetters('networks', ['loading']),
...mapActions('system', ['loadMessages']),
},
watch: {
networks(nets) {
if (nets && nets.length === 1 && util.currentUser().admin !== true) {
this.$router.replace({ path: '/bubble/' + nets[0].name });
}
},
},
};
</script>

+ 53
- 10
src/_pages/main/bubble/Network.vue 查看文件

@@ -1,6 +1,6 @@
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ -->
<template>
<div v-if="network" class="bubble-form">
<div v-if="network">
<h4>
{{ network.nickname }} -
<i>{{ messages['msg_network_state_' + network.state] }}</i>
@@ -12,8 +12,7 @@

<h6
v-if="
!isSelfNet &&
(isInReadyToRestoreState || network.state === 'running')
!isSelfNet && (isInReadyToRestoreState || network.state === 'running')
"
>
<Button
@@ -42,6 +41,8 @@
network.state === 'restoring')
"
>
<div ref="lottie" class="lottie"></div>

<!-- adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ -->
<div v-if="stats.percent" class="progress-wrap">
<div
@@ -80,7 +81,8 @@
<table border="0" width="100%">
<tr>
<td
v-for="deviceType in addableDeviceTypes"
v-for="(deviceType, key) in addableDeviceTypes"
:key="key"
align="center"
:width="addableDeviceWidth + '%'"
>
@@ -394,12 +396,14 @@
@import '../../../_scss/components/form';
@import '../../../_scss/breakpoints';

.bubble-form {
width: 800px;
.lottie {
width: 100%;
max-width: 500px;
}
</style>

<script>
import Lottie from 'lottie-web';
import { mapState, mapActions, mapGetters } from 'vuex';
import { util } from '~/_helpers';
import { loadingImgSrc } from '~/_store';
@@ -427,8 +431,16 @@ export default {
upgradeRefresher: null,
logsExpirationDays: null,
backupDownloadRefresher: null,

lottie: null,
timerID: null,
frameNumber: 0,
targetFrameNumber: 0,
};
},

mounted() {},

computed: {
...mapState('networks', [
'network',
@@ -577,6 +589,23 @@ export default {
'upgrade',
]),
refreshStatus(userId) {
if (!this.lottie && this.$refs.lottie) {
this.lottie = Lottie.loadAnimation({
container: this.$refs.lottie,
renderer: '',
loop: false,
autoplay: false,
path: '/launching-bubble.json',
});
this.frameNumber = 0;
this.targetFrameNumber = 20;

this.timerID = setInterval(() => {
if (this.frameNumber < this.targetFrameNumber) {
this.frameNumber++;
}
}, 100);
}
this.getNetworkById({
userId: userId,
networkId: this.networkId,
@@ -757,6 +786,7 @@ export default {
this.upgrade();
},
},

created() {
const user = util.currentUser();
this.refreshStatus(user.uuid);
@@ -765,17 +795,18 @@ export default {
this.getAppLinks(user.locale);
this.loadSystemConfigs();
},

beforeDestroy() {
this.clearRefresherInterval(this.refresher);
this.clearRefresherInterval(this.stopRefresher);
this.clearRefresherInterval(this.backupDownloadRefresher);
this.resetRestoreKey();
},

watch: {
network(net) {
network(net, oldNet) {
if (net) {
if (net.uuid === 'Not Found')
this.$router.replace({ path: '/bubbles' });
if (net.uuid === 'Not Found') this.$router.replace({ path: '/bubble' });
this.networkUuid = net.uuid;
if (net.state !== 'stopping')
this.clearRefresherInterval(this.stopRefresher);
@@ -814,10 +845,22 @@ export default {
// status not found for our network
this.clearRefresherInterval(this.refresher);
}

if (this.stats) {
this.targetFrameNumber = 20 + (30 / 100) * this.stats.percent;
}
},

frameNumber() {
this.lottie.goToAndStop(this.frameNumber, true);
if (this.frameNumber >= 50) {
clearInterval(this.timerID);
}
},

deletedNetworkUuid(uuid) {
if (uuid && this.networkUuid && uuid === this.networkUuid) {
this.$router.replace({ path: '/bubbles' });
this.$router.replace({ path: '/bubble' });
}
},
configs(c) {


+ 117
- 0
src/_pages/main/bubble/Networks.vue 查看文件

@@ -0,0 +1,117 @@
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ -->
<template>
<div>
<div v-if="networks && networks.length > 0">
<h2>{{ messages.table_title_networks }}</h2>
<table border="1">
<thead>
<tr>
<th nowrap="nowrap">{{ messages.label_field_networks_name }}</th>
<th nowrap="nowrap">{{ messages.label_field_networks_locale }}</th>
<th nowrap="nowrap">
{{ messages.label_field_networks_timezone }}
</th>
<th nowrap="nowrap">
{{ messages.label_field_networks_object_state }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="network in networks" :key="network.uuid">
<td>
<router-link :to="{ path: '/bubble/' + network.name }"
>{{ network.name }}.{{ network.domainName }}</router-link
>
</td>
<td nowrap="nowrap">
{{ messages['locale_' + network.locale] || network.locale }}
</td>
<td nowrap="nowrap">
{{ messages['tz_name_' + network.timezone] || network.timezone }}
</td>
<td>{{ messages['msg_network_state_' + network.state] }}</td>
</tr>
</tbody>
</table>
<hr />
</div>
</div>
</template>

<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import { util } from '~/_helpers';

export default {
data() {
return {
verifiedContacts: null,
};
},
computed: {
...mapState('networks', ['networks']),
...mapState('system', ['messages', 'configs']),
...mapState('users', ['policy']),
},
created() {
if (!this.configs.sageLauncher) {
this.$router.replace({ path: '/bubble/' + this.configs.networkUuid });
}

const user = util.currentUser();
const selectedLocale =
user !== null &&
typeof user.locale !== 'undefined' &&
user.locale !== null
? user.locale
: 'detect';
this.getAllNetworks({
userId: user.uuid,
messages: this.messages,
errors: this.errors,
});
this.loadMessages('post_auth', selectedLocale);
this.loadMessages('apps', selectedLocale);
this.getPolicyByUserId({
userId: user.uuid,
messages: this.messages,
errors: this.errors,
});
},
methods: {
...mapActions('users', ['getPolicyByUserId']),
...mapActions('networks', [
'getAllNetworks',
'stopNetwork',
'deleteNetwork',
]),
...mapGetters('networks', ['loading']),
...mapActions('system', ['loadMessages']),
hasVerifiedContact(policy) {
if (policy && policy.accountContacts) {
const contacts = policy.accountContacts;
for (let i = 0; i < contacts.length; i++) {
if (contacts[i].verified && isNotAuthenticator(contacts[i]))
return true;
}
return false;
}
return false;
},
},
watch: {
networks(nets) {
if (nets && nets.length) {
if (nets.length === 0) {
this.$router.replace({ path: '/new_bubble' });
} else if (nets.length === 1 && util.currentUser().admin !== true) {
this.$router.replace({ path: '/bubble/' + nets[0].name });
}
}
},
policy(p) {
this.verifiedContacts = this.hasVerifiedContact(p);
},
},
};
</script>

+ 6
- 21
src/_router/index.js 查看文件

@@ -90,6 +90,8 @@ export const router = new Router({
// { path: '/restore', component: RestorePage },

// new route
{ path: '', redirect: '/bubble', exact: true },

{
path: '',
component: () => import('~/_pages/Layout'),
@@ -122,22 +124,13 @@ export const router = new Router({
},
{ path: 'notifications', component: NotificationsPage },
{
path: 'bubbles',
component: NetworksPage,
children: [
{
path: '',
component: NewNetworkPage,
children: newNetworkChildren,
},
],
path: 'bubble',
component: () => import('~/_pages/main/bubble/MyBubble'),
},
{
path: 'new_bubble',
component: NewNetworkPage,
children: newNetworkChildren,
path: 'bubble/:id',
component: () => import('~/_pages/main/bubble/Network'),
},
{ path: 'bubble/:id', component: NetworkPage },
{ path: 'action', component: ActionPage },
{ path: 'resetPassword/:code', component: SetPasswordPage },

@@ -214,14 +207,6 @@ export const router = new Router({
path: 'support',
component: () => import('~/_pages/main/account/Support'),
},
{
path: 'launch-bubble',
component: () => import('~/_pages/main/bubble/LaunchBubble'),
},
{
path: 'launching-bubble/:id',
component: () => import('~/_pages/main/bubble/LaunchingBubble'),
},
],
},
{


+ 7
- 7
src/_store/account.module.js 查看文件

@@ -99,19 +99,19 @@ const actions = {
systemConfigs.bubbleNode === false ||
systemConfigs.sageLauncher === true
) {
router.replace('/bubbles');
router.replace('/bubble');
} else if (systemConfigs.bubbleNode === true) {
router.replace('/devices');
} else {
router.replace('/');
}
} else {
console.log(
'account.login: landing page==' +
JSON.stringify(landing) +
', systemConfigs.bubbleNode==' +
systemConfigs.bubbleNode
);
// console.log(
// 'account.login: landing page==' +
// JSON.stringify(landing) +
// ', systemConfigs.bubbleNode==' +
// systemConfigs.bubbleNode
// );
util.resetLandingPage();
router.replace(landing.fullPath);
}


+ 2
- 2
src/_store/system.module.js 查看文件

@@ -246,7 +246,7 @@ const getters = {
});
if (configs.sageLauncher) {
dashApps.push({
href: '/bubbles',
href: '/bubble',
title: messages.label_menu_networks,
icon: messages.label_menu_networks_icon,
index: 1,
@@ -295,7 +295,7 @@ const getters = {
index: 2,
});
adminApps.push({
href: '/admin/bubbles',
href: '/admin/bubble',
title: messages.label_menu_admin_networks,
icon: messages.label_menu_admin_networks_icon,
index: 3,


+ 2
- 2
src/account/NetworkPage.vue 查看文件

@@ -447,7 +447,7 @@
watch: {
network (net) {
if (net) {
if (net.uuid === 'Not Found') this.$router.replace({path: '/bubbles'});
if (net.uuid === 'Not Found') this.$router.replace({path: '/bubble'});
this.networkUuid = net.uuid;
if (net.state !== 'stopping') this.clearRefresherInterval(this.stopRefresher);
}
@@ -483,7 +483,7 @@
},
deletedNetworkUuid (uuid) {
if (uuid && this.networkUuid && (uuid === this.networkUuid)) {
this.$router.replace({path: '/bubbles'});
this.$router.replace({path: '/bubble'});
}
},
configs (c) {


+ 2
- 2
src/account/profile/ActionPage.vue 查看文件

@@ -72,8 +72,8 @@
console.log('ActionPage.watch.actionStatus: still requesting, doing nothing');
} else {
if (status.success) {
console.log('ActionPage.watch.actionStatus: sending to /bubbles with success');
this.$router.push({path: '/bubbles', query: {action: this.actionType, ok: 'true'}});
console.log('ActionPage.watch.actionStatus: sending to /bubble with success');
this.$router.push({path: '/bubble', query: {action: this.actionType, ok: 'true'}});

} else if (status.error !== null && status.type === 'approve'
&& this.errors && this.errors.items && this.errors.items.length === 1


+ 0
- 22
src/app/App.vue 查看文件

@@ -174,28 +174,6 @@ export default {
isInRestoringMode() {
return this.configs ? this.configs.awaitingRestore : undefined;
},
isNewPage() {
const newPages = [
'/login',
'/forgotPassword',
'/register',
'/appLogin',
'/verifyEmail',
'/payment',
'/me',
'/devices',
'/launch-bubble',
'/launching-bubble',
'/restore',
'/support',
'/legal',
];

return (
newPages.includes(this.$route.path) ||
newPages.filter((p) => this.$route.path.startsWith(p)).length
);
},
},
methods: {
...mapActions({ clearAlert: 'alert/clear' }),


+ 0
- 19
webpack.config.dev-example.js 查看文件

@@ -1,19 +0,0 @@
/**
* Copyright (c) 2020 Bubble, Inc. All rights reserved.
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/
*/

// -------------------------------------------------------------
// Copy this file to webpack.config.dev.js to use in development
// -------------------------------------------------------------

const webpackMerge = require('webpack-merge');

module.exports = webpackMerge.merge(require('./webpack.config.js'), {
mode: 'development',
devServer: {
proxy: {
'/api': 'http://example.com:8888' // change this to wherever your bubble API is running
}
},
});

Loading…
取消
儲存