Browse Source

Resolve "rearrange main window"

Eric Tröbs 4 years ago
parent
commit
cbfca79409

+ 89 - 26
webui/src/App.vue

@@ -1,36 +1,52 @@
 <template>
   <div id="app">
-    <!-- <img alt="Ape" src="/logo.png"> -->
+    <!-- top navigation bar -->
+    <top-navigation-bar :window="window"
+                        :current-project="currentProject"
+                        v-on:side="window.menu = !window.menu"></top-navigation-bar>
 
-    <project-open-dialog v-if="showProjectOpenDialog"
-                         :status="status" :socket="socket"
-                         @create="showProjectCreationDialog=true"/>
-    <project-creation-dialog v-if="showProjectCreationDialog"
-                             :status="status" :socket="socket"
-                             @cancel="showProjectCreationDialog=false"/>
-    <project-main-window v-if="showProjectMainWindow"
-                         :status="status" :socket="socket"/>
+    <!-- bottom content -->
+    <div class="bottom">
+      <!-- side navigation bar -->
+      <side-navigation-bar :window="window"
+                           :socket="socket"
+                           :project-path="currentProjectPath"></side-navigation-bar>
 
-    <pre style="text-align: left; font-size: 80%">{{ this.status }}</pre>
+      <!-- actual content -->
+      <div class="content">
+        <project-open-window v-if="!currentProject"
+                             :projects="projects"
+                             :status="status"
+                             :socket="socket"
+                             v-on:open="show('settings')"/>
+        <template v-else>
+          <project-settings-window v-if="window.content === 'settings'"
+                                   :current-project="currentProject"
+                                   :current-project-path="currentProjectPath"
+                                   :socket="socket"/>
+        </template>
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
 import io from "socket.io-client";
-import ProjectOpenDialog from "@/components/projects/project-open-dialog";
-import ProjectMainWindow from "@/components/projects/project-main-window";
-import ProjectCreationDialog from "@/components/projects/project-creation-dialog";
+import ProjectOpenWindow from "@/components/projects/project-open-window";
+import TopNavigationBar from "@/components/window/top-navigation-bar";
+import SideNavigationBar from "@/components/window/side-navigation-bar";
+import ProjectSettingsWindow from "@/components/projects/project-settings-window";
 
 export default {
   name: 'App',
   components: {
-    ProjectCreationDialog,
-    ProjectMainWindow,
-    ProjectOpenDialog
+    ProjectSettingsWindow,
+    SideNavigationBar,
+    TopNavigationBar,
+    ProjectOpenWindow
   },
   data: function() {
     return {
-      status: null,
       // initialize socket.io connection
       socket: {
         io: io('http://' + window.location.hostname + ':5000'),
@@ -41,22 +57,49 @@ export default {
           this.io.emit('add', {path: path, value: value});
         }
       },
-      showProjectCreationDialog: false
+      status: null,
+      window: {
+        wide: true,
+        menu: false,
+        content: 'settings',
+      }
     }
   },
   computed: {
-    showProjectOpenDialog: function() {
-      return this.status != null
-          && this.status.projects.filter(x => ['open', 'load'].includes(x.status)).length === 0
-          && !this.showProjectCreationDialog;
+    projects: function() {
+      return this.status == null ? [] : this.status.projects;
+    },
+    currentProject: function() {
+      for (let i = 0; i < this.projects.length; i++)
+        if (this.projects[i].status === 'open')
+          return this.projects[i];
+
+      return false;
+    },
+    currentProjectPath: function() {
+      for (let i = 0; i < this.projects.length; i++)
+        if (this.projects[i].status === 'open')
+          return 'projects/' + i;
+
+      return false;
+    }
+  },
+  methods: {
+    resize: function() {
+      this.window.wide = (document.body.offsetWidth > 1024);
     },
-    showProjectMainWindow: function() {
-      return this.status != null && this.status.projects.filter(x => ['open'].includes(x.status)).length > 0
-          && !this.showProjectCreationDialog;
+    show: function(value) {
+      this.window.content = value;
     }
   },
-  created() {
+  created: function() {
     this.socket.io.on('app_status', status => this.status = status);
+
+    window.addEventListener('resize', this.resize);
+    this.resize();
+  },
+  destroyed: function() {
+    window.removeEventListener('resize', this.resize);
   }
 }
 </script>
@@ -65,5 +108,25 @@ export default {
 #app {
   width: 100%;
   height: 100%;
+
+  display: flex;
+  flex-direction: column;
+}
+
+.bottom {
+  display: flex;
+  flex-grow: 1;
+  flex-direction: row;
+  width: 100%;
+}
+
+.content {
+  flex-grow: 1;
+  overflow: hidden;
+}
+
+.content > * {
+  width: 100%;
+  height: 100%;
 }
 </style>

+ 3 - 15
webui/src/components/projects/project-creation-dialog.vue → webui/src/components/projects/project-creation-window.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="project-creation-dialog">
+  <div class="project-creation-window">
     <h1 class="title">Create Project</h1>
 
     <div class="content">
@@ -42,7 +42,7 @@ import SelectInput from "@/components/base/select-input";
 import ButtonInput from "@/components/base/button-input";
 
 export default {
-  name: "project-creation-dialog",
+  name: "project-creation-window",
   components: {ButtonInput, SelectInput, TextareaInput, TextInput},
   props: ['status', 'socket'],
   data: function() {
@@ -95,19 +95,7 @@ export default {
 </script>
 
 <style scoped>
-.project-creation-dialog {
-  position: fixed;
-  top: 5vh;
-  left: 5vw;
-
-  width: 90vw;
-  height: 90vh;
-
-  background-color: var(--background);
-  border-radius: 1rem;
-  border: 1px solid rgba(0, 0, 0, 0.2);
-  box-shadow: 0.3rem 0.3rem 0.4rem rgba(0, 0, 0, 0.2);
-
+.project-creation-window {
   display: flex;
   flex-direction: column;
 }

+ 0 - 167
webui/src/components/projects/project-main-window.vue

@@ -1,167 +0,0 @@
-<template>
-  <div class="project-main-window">
-    <div class="top-navigation">
-      <img class="burger"
-           src="@/assets/icons/three-bars.svg"
-           alt="Menü"
-           v-if="!wide"
-           :class="{ rotation: menu }"
-           @click="invertMenu">
-
-      <div class="name">
-        {{ project.name }}
-      </div>
-    </div>
-
-    <div class="bottom">
-      <div class="side-navigation"
-           :class="{ wide: wide, narrow: !wide, active: menu }">
-        <div class="item"
-             :class="{active: activeContent === 'settings'}"
-             @click="activeContent = 'settings'">
-          Project Settings
-        </div>
-
-        <div class="item" @click="socket.set(projectPath, 'save')">
-          Close
-        </div>
-      </div>
-
-      <div class="content">
-        <project-main-window-settings v-if="activeContent === 'settings'"
-                                      :status="status" :socket="socket"/>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import ProjectMainWindowSettings from "@/components/projects/project-main-window-settings";
-
-export default {
-  name: "project-main-window",
-  components: {ProjectMainWindowSettings},
-  props: ['status', 'socket'],
-  data: function() {
-    return {
-      wide: true,
-      menu: false,
-      activeContent: 'settings'
-    }
-  },
-  computed: {
-    project: function() {
-      return this.status.projects.filter(x => (x.status === 'open'))[0];
-    },
-    projectPath: function() {
-      for (let i = 0; i < this.status.projects.length; i++)
-        if (this.status.projects[i].status === 'open')
-          return 'projects/' + i + '/status';
-
-      return null;
-    }
-  },
-  methods: {
-    resize: function() {
-      this.wide = (document.body.offsetWidth > 1024);
-    },
-    invertMenu: function() {
-      this.menu = !this.menu;
-    }
-  },
-  created: function() {
-    window.addEventListener('resize', this.resize);
-    this.resize();
-  },
-  destroyed: function() {
-    window.removeEventListener('resize', this.resize);
-  }
-}
-</script>
-
-<style scoped>
-.project-main-window {
-  width: 100%;
-  height: 100%;
-
-  display: flex;
-  flex-direction: column;
-}
-
-.top-navigation {
-  background-color: #3f3f3f;
-  color: whitesmoke;
-  display: flex;
-  padding: 1rem;
-
-  flex-direction: row;
-  align-items: center;
-}
-
-.top-navigation .burger {
-  transition: transform 0.5s;
-  filter: invert(1);
-  width: 2rem;
-  margin-right: 0.75rem;
-}
-
-.top-navigation .burger.rotation {
-  transform: rotate(90deg);
-}
-
-.top-navigation .name {
-  font-weight: bold;
-  font-family: "Roboto Condensed";
-}
-
-.bottom {
-  display: flex;
-  flex-grow: 1;
-  flex-direction: row;
-  position: relative;
-}
-
-.side-navigation {
-  background-color: #2f2f2f;
-  color: whitesmoke;
-}
-
-.side-navigation.wide {
-  flex-grow: 0;
-  height: 100%;
-}
-
-.side-navigation.narrow {
-  width: 85vw;
-  left: -85vw;
-  position: absolute;
-  top: 0;
-  bottom: 0;
-  transition: 0.3s ease-out;
-}
-
-.side-navigation.narrow.active {
-  left: 0;
-}
-
-.side-navigation .item {
-  padding: 1rem 3rem 1rem 1rem;
-  cursor: pointer;
-}
-
-.side-navigation .item:hover {
-  background-color: #292929;
-}
-
-.side-navigation .item.active {
-  background-color: #1f1f1f;
-}
-
-.side-navigation .item:not(:last-child) {
-  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
-}
-
-.content {
-  flex-grow: 1;
-}
-</style>

+ 0 - 131
webui/src/components/projects/project-open-dialog.vue

@@ -1,131 +0,0 @@
-<template>
-  <div class="project-open-dialog">
-    <div class="title">
-      <h1>Projekt öffnen</h1>
-      Klicken Sie ein Projekt an, um es zu öffnen oder neu anzulegen.
-    </div>
-
-    <div class="projects">
-      <div class="project"
-           v-for="(project, i) in status.projects"
-           :key="project.id"
-           @click="load(i)">
-        <h2>{{ project.name }}</h2>
-
-        <div class="description">{{ project.description }}</div>
-
-        <div v-if="project.access === 0">
-          created on {{ datetime(project.created) }}
-        </div>
-        <div v-else>
-          {{ datetime(project.access) }}
-        </div>
-      </div>
-    </div>
-
-    <div class="footer">
-      <button-input @click="$emit('create', null)" type="primary">
-        new project
-      </button-input>
-    </div>
-  </div>
-</template>
-
-<script>
-import ButtonInput from "@/components/base/button-input";
-
-export default {
-  name: "project-open-dialog",
-  components: {ButtonInput},
-  props: ['status', 'socket'],
-  methods: {
-    load: function(index) {
-      this.socket.set('projects/' + index + '/status', 'load');
-    },
-    datetime: function(timestamp) {
-      const date = new Date(timestamp * 1000);
-
-      const da = date.getDate();
-      const mo = date.getMonth() + 1;
-      const ye = date.getFullYear();
-      const ho = date.getHours();
-      const mi = date.getMinutes();
-
-      return [
-        [
-          da < 10 ? '0' + da : da,
-          mo < 10 ? '0' + mo : mo,
-          ye
-        ].join('.'),
-        [
-          ho < 10 ? '0' + ho : ho,
-          mi < 10 ? '0' + mi : mi
-        ].join(':')
-      ].join(' ');
-    }
-  }
-}
-</script>
-
-<style scoped>
-.project-open-dialog {
-  position: fixed;
-  top: 5vh;
-  left: 5vw;
-
-  width: 90vw;
-  height: 90vh;
-
-  background-color: var(--background);
-  border-radius: 1rem;
-  border: 1px solid rgba(0, 0, 0, 0.2);
-  box-shadow: 0.3rem 0.3rem 0.4rem rgba(0, 0, 0, 0.2);
-
-  display: flex;
-  flex-direction: column;
-}
-
-h1 {
-  margin: 0 0 0.4rem 0;
-}
-
-h2 {
-  margin: 0;
-  font-size: 120%;
-  font-family: "Roboto Condensed";
-}
-
-.title {
-  margin: 0;
-  padding: 2rem;
-  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
-  flex-grow: 0;
-}
-
-.projects {
-  overflow: auto;
-  flex-grow: 1;
-}
-
-.project:not(:last-child) {
-  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
-}
-
-.project {
-  padding: 1rem 2rem;
-  cursor: pointer;
-}
-
-.description {
-  text-overflow: ellipsis;
-  width: 100%;
-  white-space: nowrap;
-  overflow: hidden;
-}
-
-.footer {
-  padding: 2rem;
-  display: flex;
-  justify-content: flex-end;
-}
-</style>

+ 146 - 0
webui/src/components/projects/project-open-window.vue

@@ -0,0 +1,146 @@
+<template>
+  <div class="project-open-window">
+    <project-creation-window v-if="create"
+                             :status="status"
+                             :socket="socket"
+                             @cancel="create = false"></project-creation-window>
+    <template v-else>
+      <div class="title">
+        <h1>Projekt öffnen</h1>
+        Klicken Sie ein Projekt an, um es zu öffnen oder neu anzulegen.
+      </div>
+
+      <div class="projects">
+        <div class="project"
+             v-for="project in sortedProjects"
+             :key="project.id"
+             @click="load(project.index)">
+          <h2>{{ project.name }}</h2>
+
+          <div class="description">{{ project.description }}</div>
+
+          <div v-if="project.access === 0">
+            created on {{ datetime(project.created) }}
+          </div>
+          <div v-else>
+            {{ datetime(project.access) }}
+          </div>
+        </div>
+      </div>
+
+      <div class="footer">
+        <button-input @click="create = true" type="primary">
+          new project
+        </button-input>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script>
+import ButtonInput from "@/components/base/button-input";
+import ProjectCreationWindow from "@/components/projects/project-creation-window";
+
+export default {
+  name: "project-open-window",
+  components: {ProjectCreationWindow, ButtonInput},
+  props: ['projects', 'status', 'socket'],
+  data: function() {
+    return {
+      create: false
+    }
+  },
+  computed: {
+    sortedProjects: function() {
+      return [...this.projects].map((e, i) => {
+        e.index = i;
+        return e;
+      }).sort((a, b) => {
+        const x = a.access === 0 ? a.created : a.access;
+        const y = b.access === 0 ? b.created : b.access;
+
+        if (x < y)
+          return 1;
+        else
+          return -1;
+      });
+    }
+  },
+  methods: {
+    load: function(index) {
+      this.socket.set('projects/' + index + '/status', 'load');
+    },
+    datetime: function(timestamp) {
+      const date = new Date(timestamp * 1000);
+
+      const da = date.getDate();
+      const mo = date.getMonth() + 1;
+      const ye = date.getFullYear();
+      const ho = date.getHours();
+      const mi = date.getMinutes();
+
+      return [
+        [
+          da < 10 ? '0' + da : da,
+          mo < 10 ? '0' + mo : mo,
+          ye
+        ].join('.'),
+        [
+          ho < 10 ? '0' + ho : ho,
+          mi < 10 ? '0' + mi : mi
+        ].join(':')
+      ].join(' ');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.project-open-window {
+  display: flex;
+  flex-direction: column;
+}
+
+h1 {
+  margin: 0 0 0.4rem 0;
+}
+
+h2 {
+  margin: 0;
+  font-size: 120%;
+  font-family: "Roboto Condensed";
+}
+
+.title {
+  margin: 0;
+  padding: 2rem;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+  flex-grow: 0;
+}
+
+.projects {
+  overflow: auto;
+  flex-grow: 1;
+}
+
+.project:not(:last-child) {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+.project {
+  padding: 1rem 2rem;
+  cursor: pointer;
+}
+
+.description {
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+.footer {
+  padding: 2rem;
+  display: flex;
+  justify-content: flex-end;
+}
+</style>

+ 10 - 18
webui/src/components/projects/project-main-window-settings.vue → webui/src/components/projects/project-settings-window.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="project-main-window-settings">
+  <div class="project-settings-window">
     <h1>Project Settings</h1>
 
     <div class="content">
@@ -44,13 +44,12 @@ import ConfirmedButtonInput from "@/components/base/confirmed-button-input";
 import ButtonInput from "@/components/base/button-input";
 
 export default {
-  name: "project-main-window-settings",
+  name: "project-settings-window",
   components: {ButtonInput, ConfirmedButtonInput, TextareaInput, TextInput},
-  props: ['status', 'socket'],
+  props: ['currentProject', 'currentProjectPath', 'socket'],
   data: function() {
     return {
       project: null,
-      projectPath: null,
       name: '',
       nameError: false,
       description: '',
@@ -59,36 +58,29 @@ export default {
   },
   methods: {
     nameF: function(value) {
-      this.socket.set(this.projectPath + '/name', value);
+      this.socket.set(this.currentProjectPath + '/name', value);
       this.update();
     },
     descriptionF: function(value) {
-      this.socket.set(this.projectPath + '/description', value);
+      this.socket.set(this.currentProjectPath + '/description', value);
       this.update();
     },
     update: function() {
-      this.socket.set(this.projectPath + '/action', 'update');
+      this.socket.set(this.currentProjectPath + '/action', 'update');
     },
     deleteProject: function() {
-      this.socket.set(this.projectPath + '/action', 'delete');
+      this.socket.set(this.currentProjectPath + '/action', 'delete');
     }
   },
   created: function() {
-    for (let i = 0; i < this.status.projects.length; i++) {
-      if (this.status.projects[i].status === 'open') {
-        this.projectPath = 'projects/' + i;
-        this.project = this.status.projects[i];
-      }
-    }
-
-    this.name = this.project.name;
-    this.description = this.project.description;
+    this.name = this.currentProject.name;
+    this.description = this.currentProject.description;
   }
 }
 </script>
 
 <style scoped>
-.project-main-window-settings {
+.project-settings-window {
   padding: 1rem;
 }
 

+ 84 - 0
webui/src/components/window/side-navigation-bar.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="side-navigation"
+       :class="{ wide: window.wide, narrow: !window.wide, active: window.menu }">
+    <!-- project settings -->
+    <div class="item"
+         :class="{active: window.content === 'settings', inactive: !projectPath}"
+         @click="ifProjectIsOpened(show, 'settings')">
+      Project Settings
+    </div>
+
+    <div class="item"
+         :class="{inactive: !projectPath}"
+         @click="close">
+      Close
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "side-navigation-bar",
+  props: ['window', 'socket', 'projectPath'],
+  methods: {
+    ifProjectIsOpened: function(fun, ...args) {
+      if (this.projectPath)
+        fun.bind(fun)(...args);
+    },
+    show: function(value) {
+      this.window.content = value;
+    },
+    close: function() {
+      if (this.projectPath)
+        this.socket.set(this.projectPath + '/status', 'save');
+    }
+  }
+}
+</script>
+
+<style scoped>
+.side-navigation {
+  background-color: #2f2f2f;
+  color: whitesmoke;
+}
+
+.side-navigation.wide {
+  flex-grow: 0;
+  height: 100%;
+}
+
+.side-navigation.narrow {
+  width: 85vw;
+  left: -85vw;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  transition: 0.2s ease-out;
+}
+
+.side-navigation.narrow.active {
+  left: 0;
+}
+
+.side-navigation .item {
+  padding: 1rem 3rem 1rem 1rem;
+  cursor: pointer;
+  white-space: nowrap;
+}
+
+.side-navigation .item:hover:not(.inactive) {
+  background-color: #292929;
+}
+
+.side-navigation .item.active {
+  background-color: #1f1f1f;
+}
+
+.side-navigation .item.inactive {
+  color: #7f7f7f;
+}
+
+.side-navigation .item:not(:last-child) {
+  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
+}
+</style>

+ 49 - 0
webui/src/components/window/top-navigation-bar.vue

@@ -0,0 +1,49 @@
+<template>
+  <div class="top-navigation">
+    <img class="burger"
+         src="@/assets/icons/three-bars.svg"
+         alt="Menü"
+         v-if="!window.wide"
+         :class="{ rotation: window.menu }"
+         @click="$emit('side', null)">
+
+    <div class="name">
+      {{ currentProject ? currentProject.name : 'PyCS' }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "top-navigation-bar",
+  props: ['window', 'currentProject']
+}
+</script>
+
+<style scoped>
+.top-navigation {
+  background-color: #3f3f3f;
+  color: whitesmoke;
+  display: flex;
+  padding: 1rem;
+
+  flex-direction: row;
+  align-items: center;
+}
+
+.top-navigation .burger {
+  transition: transform 0.6s;
+  filter: invert(1);
+  width: 2rem;
+  margin-right: 0.75rem;
+}
+
+.top-navigation .burger.rotation {
+  transform: rotate(90deg);
+}
+
+.top-navigation .name {
+  font-weight: bold;
+  font-family: "Roboto Condensed";
+}
+</style>