aboutsummaryrefslogblamecommitdiffhomepage
path: root/src/App.vue
blob: f7fd34adc30729e6b40790cb6141aea2fe427d52 (plain) (tree)





















































































































































































































                                                                         
<template>
  <div
    id="app"
    v-if="config"
    :class="[
      `theme-${config.theme}`,
      isDark ? 'is-dark' : 'is-light',
      !config.footer ? 'no-footer' : ''
    ]"
  >
    <DynamicTheme :themes="config.colors" />
    <div id="bighead">
      <section v-if="config.header" class="first-line">
        <div v-cloak class="container">
          <div class="logo">
            <img v-if="config.logo" :src="config.logo" />
            <i v-if="config.icon" :class="config.icon"></i>
          </div>
          <div class="dashboard-title">
            <span class="headline">{{ config.subtitle }}</span>
            <h1>{{ config.title }}</h1>
          </div>
        </div>
      </section>

      <Navbar
        :open="showMenu"
        :links="config.links"
        @navbar:toggle="showMenu = !showMenu"
      >
        <DarkMode @updated="isDark = $event" />

        <SettingToggle
          @updated="vlayout = $event"
          name="vlayout"
          icon="fa-list"
          iconAlt="fa-columns"
        />

        <SearchInput
          class="navbar-item is-inline-block-mobile"
          @input="filterServices"
          @search:focus="showMenu = true"
          @search:open="navigateToFirstService"
          @search:cancel="filterServices"
        />
      </Navbar>
    </div>

    <section id="main-section" class="section">
      <div v-cloak class="container">
        <ConnectivityChecker @network:status-update="offline = $event" />
        <div v-if="!offline">
          <!-- Optional messages -->
          <Message :item="config.message" />

          <!-- Horizontal layout -->
          <div v-if="!vlayout || filter" class="columns is-multiline">
            <template v-for="group in services">
              <h2 v-if="group.name" class="column is-full group-title">
                <i v-if="group.icon" :class="group.icon"></i>
                {{ group.name }}
              </h2>
              <Service
                v-for="item in group.items"
                :key="item.url"
                v-bind:item="item"
                class="column is-one-third-widescreen"
              />
            </template>
          </div>

          <!-- Vertical layout -->
          <div
            v-if="!filter && vlayout"
            class="columns is-multiline layout-vertical"
          >
            <div
              class="column is-one-third-widescreen"
              v-for="group in services"
              :key="group.name"
            >
              <h2 v-if="group.name" class="group-title">
                <i v-if="group.icon" :class="group.icon"></i>
                {{ group.name }}
              </h2>
              <Service
                v-for="item in group.items"
                v-bind:item="item"
                :key="item.url"
              />
            </div>
          </div>
        </div>
      </div>
    </section>

    <footer class="footer">
      <div class="container">
        <div
          class="content has-text-centered"
          v-if="config.footer"
          v-html="config.footer"
        ></div>
      </div>
    </footer>
  </div>
</template>

<script>
const jsyaml = require("js-yaml");
const merge = require("lodash.merge");

import Navbar from "./components/Navbar.vue";
import ConnectivityChecker from "./components/ConnectivityChecker.vue";
import Service from "./components/Service.vue";
import Message from "./components/Message.vue";
import SearchInput from "./components/SearchInput.vue";
import SettingToggle from "./components/SettingToggle.vue";
import DarkMode from "./components/DarkMode.vue";
import DynamicTheme from "./components/DynamicTheme.vue";

import defaultConfig from "./assets/defaults.yml";

export default {
  name: "App",
  components: {
    Navbar,
    ConnectivityChecker,
    Service,
    Message,
    SearchInput,
    SettingToggle,
    DarkMode,
    DynamicTheme
  },
  data: function () {
    return {
      config: null,
      services: null,
      offline: false,
      filter: "",
      vlayout: true,
      isDark: null,
      showMenu: false
    };
  },
  created: async function () {
    try {
      const defaults = jsyaml.load(defaultConfig);
      let config = await this.getConfig();

      this.config = merge(defaults, config);

      console.log(this.config);
      this.services = this.config.services;
      document.title = `${this.config.title} | ${this.config.subtitle}`;
    } catch (error) {
      this.offline = true;
    }
  },
  methods: {
    getConfig: function () {
      return fetch("config.yml").then(function (response) {
        if (response.status != 200) {
          return;
        }
        return response.text().then(function (body) {
          return jsyaml.load(body);
        });
      });
    },
    matchesFilter: function (item) {
      return (
        item.name.toLowerCase().includes(this.filter) ||
        (item.tag && item.tag.toLowerCase().includes(this.filter))
      );
    },
    navigateToFirstService: function (target) {
      try {
        const service = this.services[0].items[0];
        window.open(service.url, target || service.target || "_self");
      } catch (error) {
        console.warning("fail to open service");
      }
    },
    filterServices: function (filter) {
      this.filter = filter;

      if (!filter) {
        this.services = this.config.services;
        return;
      }

      const searchResultItems = [];
      for (const group of this.config.services) {
        for (const item of group.items) {
          if (this.matchesFilter(item)) {
            searchResultItems.push(item);
          }
        }
      }

      this.services = [
        {
          name: filter,
          icon: "fas fa-search",
          items: searchResultItems
        }
      ];
    }
  }
};
</script>