Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module.exports = {
ecmaVersion: 2020
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
"@typescript-eslint/ban-ts-ignore": "off"
}
Expand Down
2 changes: 2 additions & 0 deletions client/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.DS_Store
node_modules
.yarn
./mustard
/dist

# local env files
Expand Down
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"vue": "^2.6.11",
"vue-apexcharts": "~1.5.2",
"vue-class-component": "^7.2.2",
"vue-grid-layout": "^2.4.0",
"vue-property-decorator": "^8.3.0",
"vue-sse": "~1.0.2",
"vuex": "~3.1.3"
Expand Down
278 changes: 202 additions & 76 deletions client/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
<template>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not modify the existing component. Create a new component/route and add your changes there.

<div id="app">
<div class="container">
<div
v-for="(l, index) in layout"
v-bind:key="index"
v-bind:class="getClass(index)"
>
<div :is="l.component" v-bind="getProps(index)" />
<div>
<div class="buttonClass">
<button class="button" @click="saveJson">Save widget dimensions</button>
</div>
<grid-layout
:layout.sync="layout"
:col-num="6"
:row-height="185"
:is-draggable="draggable"
:is-resizable="resizable"
:isBounded="bounded"
:vertical-compact="true"
:use-css-transforms="true"
>
<grid-item
v-for="(item, index) in layout"
v-bind:key=index
v-bind:class="getClass(index)"
:static="item.static"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:minW="minW"
:minH="minH"
@resized="resizedEvent"
@moved="movedEvent"
@container-resized="containerResizedEvent"
>
<div :is="item.component" v-bind="getProps(index)" class="widgetClass"/>
</grid-item>
</grid-layout>
</div>
</div>
</template>

<script lang="ts">
Expand All @@ -22,41 +45,44 @@ import WeatherWidget from "./components/WeatherWidget.vue";
import SlideshowWidget from "./components/SlideshowWidget.vue";
import { EventSink, eventType } from "./eventsink";
import { BaseUrl } from "./constants";
import { GridLayout, GridItem } from "vue-grid-layout";
import { VNodeChildrenArrayContents } from "vue/types/umd";

type layoutType = {
component: string;
x: number;
y: number;
w: number;
h?: number;
i: string;
index: number;
class: string | string[];
props: Record<string, unknown>;
state?: Record<string, unknown>;
component: string;
};

interface Classes {
column: boolean;
[key: string]: boolean;
}
const getLayout = async () => {
const url = window.location.pathname;
const path = url.substring(url.lastIndexOf("/") + 1);
const response = await fetch(`${BaseUrl}api/layout`, {
method: "POST",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({ path })
});
return response.json() as Promise<layoutType[]>;
};

@Component({
components: {
Clock,
TextWidget,
ListWidget,
ComparisonWidget,
WeatherWidget,
SlideshowWidget
SlideshowWidget,
GridLayout,
GridItem
}
})
export default class App extends Vue {
private eventServer = new EventSink();
private layout: Array<layoutType> = [];
private tempDimensionJson: Array<layoutType> = [];
private dimensionJson: Array<layoutType> = [];

created() {
setTimeout(
Expand All @@ -69,9 +95,7 @@ export default class App extends Vue {
}
mounted() {
(async () => {
// get the layout, for now we get it from local
this.layout = await getLayout();
// set the Vuex state modules
this.tempDimensionJson = this.layout;
this.layout.forEach(layoutItem => {
if (!layoutItem.state) {
return;
Expand All @@ -89,82 +113,184 @@ export default class App extends Vue {
);
})();
}

data() {
return {
layout: [
{
"x":0,
"y":0,
"w":2,
"h":2,
"class": ["x1 y2"],
"i":"bsGenerator",
"props": {
"eventId": "bsGenerator",
"background": "#ffdb58"
},
"static": false,
"component": "TextWidget",
"state": {
"bsGenerator": {
"title": "Y",
"subtitle": "X"
}
}
},
{
"x":2,
"y":0,
"w":2,
"h":5,
"class": ["x2 y4"],
"i":"jotd",
"props": {
"eventId": "jotd"
},
"static": false,
"component": "TextWidget",
"state": {
"jotd": {
"title": "Y",
"subtitle": "X"
}
}
},
{
"x":4,
"y":0,
"w":2,
"h":2,
"class": ["x1 y2"],
"i":"clock",
"props": {
"eventId": "clockWidget",
"clockOneTz": "America/Los_Angeles",
"clockThreeTz": "America/New_York"
},
"static": false,
"clockOneTz": "America/Los_Angeles",
"clockThreeTz": "America/New_York",
"component": "Clock",
"state": {
"clockWidget": {}
}
}
],
draggable: true,
resizable: true,
bounded: false,
index: 0,
minW: 2,
minH: 2,
}
}

movedEvent(i:string, newX:number, newY:number) {
const index = i== 'bsGenerator' ? 0 : (i== 'jotd' ? 1 : 2)
this.tempDimensionJson[index]['i'] = i;
this.tempDimensionJson[index]['x'] = newX;
this.tempDimensionJson[index]['y'] = newY;
}
resizedEvent(i:string, newH:number, newW:number, newHPx:number, newWPx:number) {
const index = i== 'bsGenerator' ? 0 : (i== 'jotd' ? 1 : 2)
this.tempDimensionJson[index]['i'] = i;
this.tempDimensionJson[index]['h'] = newH;
this.tempDimensionJson[index]['w'] = newW;
}
containerResizedEvent(i:string, newH:number, newW:number, newHPx:number, newWPx:number) {
const index = i== 'bsGenerator' ? 0 : (i== 'jotd' ? 1 : 2)
this.tempDimensionJson[index]['i'] = i;
this.tempDimensionJson[index]['h'] = newH;
this.tempDimensionJson[index]['w'] = newW;
}

getClass(index: number): { column: boolean } & Record<string, boolean> {
const classes: Classes = {
column: true
column: false
};
const classNames = Array.isArray(this.layout[index].class)
? this.layout[index].class
: [this.layout[index].class];
(classNames as string[]).forEach(cls => {
classes[cls] = true;
(classNames as string[]).forEach((cls: string|number) => {
classes[cls] = false;
});
return classes;
}

getProps(index: number): Record<string, unknown> {
return this.layout[index].props;
}

saveJson() {
this.dimensionJson = this.tempDimensionJson;
console.log("🚀 ~ file: App.vue:252 ~ App ~ saveJson ~ dimensionJson:", this.dimensionJson)
console.log('button clicked!!!!!!!')
}
beforeDestroy() {
this.eventServer.destory();
}
}
</script>

<style lang="scss">
body {
margin: 0;
padding: 0;
background: #333;
color: #efefef;
font-family: Karla, sans-serif;
}

body * {
margin: 0;
padding: 0;
box-sizing: border-box;
font-size: 15px;
.vue-grid-layout {
background: #333;
width: 100%;
height: 100vh !important;
overflow: hidden;
}

$base: 20;

@for $i from 1 through 4 {
h#{$i} {
font-size: $base + (4-$i) * 10px;
}
.vue-grid-item:not(.vue-grid-placeholder) {
background: #ccc;
border: 0.5px solid black;
}

.container {
height: 100vh;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 4px;
width: 100%;
grid-auto-rows: 1fr;
.vue-grid-item .resizing {
opacity: 0.9;
}

.column {
@for $i from 1 through 4 {
&.x#{$i} {
grid-column-end: span $i;
}
&.y#{$i} {
grid-row-end: span $i;
}
}
& > div {
.vue-grid-item .static {
background: #cce;
}
.vue-grid-item .no-drag {
height: 100%;
}
width: 100%;
}
.vue-grid-item .minMax {
font-size: 12px;
}
.text-center {
text-align: center;
.vue-grid-item .add {
cursor: pointer;
}
.center {
display: flex;
.vue-draggable-handle {
position: absolute;
width: 20px;
height: 20px;
top: 0;
left: 0;
background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10'><circle cx='5' cy='5' r='5' fill='#999999'/></svg>") no-repeat;
background-position: bottom right;
padding: 0 8px 8px 0;
background-repeat: no-repeat;
background-origin: content-box;
box-sizing: border-box;
cursor: pointer;
}
.buttonClass {
background: #333;
display: flex;
justify-content: center;
align-items: center;
> div {
text-align: center;
width: 100%;
}
}
.button {
background-color: #035880;
color: white;
width: fit-content;
max-width: 100%;
min-width: 100px;
padding: 10px 24px;
font-weight: bold;
}
.widgetClass {
position: relative !important;
}
</style>
2 changes: 2 additions & 0 deletions client/src/components/Clock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ export default class Clock extends Vue {
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%;
width: 100%;
& .clock1,
& .clock3 {
flex: 1 1 auto;
Expand Down
4 changes: 3 additions & 1 deletion client/src/components/TextWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ export default class TextWidget extends Vue {

get containerStyle() {
return {
background: this.background ? this.background : "#2a9d8f"
background: this.background ? this.background : "#2a9d8f",
height: "100%",
width: "100%"
};
}
}
Expand Down
Loading