Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

widgets drag and drop; save widgets json on click of button; #60

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
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
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