From d0a852fbcb60fd2393fee99e150bd0514fc425b2 Mon Sep 17 00:00:00 2001
From: Ben Francis
Date: Thu, 10 Aug 2023 22:15:56 +0100
Subject: [PATCH] Refine groups UI - fixes #2938
---
static/css/add-group.css | 77 ++++++++++
static/css/add-thing.css | 40 -----
static/css/app.css | 238 ++++++++++++++++++++----------
static/css/group-context-menu.css | 136 ++++++++---------
static/css/group.css | 138 ++++++++---------
static/css/things.css | 25 ++--
static/fluent/en-CA/main.ftl | 13 ++
static/fluent/en-GB/main.ftl | 15 +-
static/fluent/en-US/main.ftl | 16 +-
static/images/add-group-icon.svg | 53 +++++++
static/images/add-thing-icon.svg | 33 +++++
static/images/down-arrow.svg | 26 ++++
static/images/group.svg | 66 +++++++++
static/images/overflow-small.svg | 32 ++++
static/images/up-arrow.svg | 27 ++++
static/index.html | 86 +++++++----
static/js/app.js | 4 +
static/js/context-menu.js | 3 +
static/js/group-context-menu.js | 37 ++---
static/js/views/add-group.js | 62 ++++++++
static/js/views/add-thing.js | 15 --
static/js/views/group.js | 69 +++++----
static/js/views/things.js | 39 ++++-
webpack.config.js | 1 +
24 files changed, 887 insertions(+), 364 deletions(-)
create mode 100644 static/css/add-group.css
create mode 100644 static/images/add-group-icon.svg
create mode 100644 static/images/add-thing-icon.svg
create mode 100644 static/images/down-arrow.svg
create mode 100644 static/images/group.svg
create mode 100644 static/images/overflow-small.svg
create mode 100644 static/images/up-arrow.svg
create mode 100644 static/js/views/add-group.js
diff --git a/static/css/add-group.css b/static/css/add-group.css
new file mode 100644
index 000000000..6e3c6e913
--- /dev/null
+++ b/static/css/add-group.css
@@ -0,0 +1,77 @@
+#add-group-screen {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+#add-group-screen.hidden {
+ display: none;
+}
+
+#add-group-screen h1 {
+ position: fixed;
+ top: 0;
+ line-height: 9.6rem;
+ margin: 0;
+ font-size: 1.6rem;
+ text-align: center;
+ font-family: 'Open Sans', sans-serif;
+ font-weight: normal;
+ width: calc(100% - 18rem);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+#add-group-screen h1 img {
+ vertical-align: middle;
+ margin-right: 0.3rem;
+}
+
+#add-group-form {
+ width: calc(100% - 4rem);
+ max-width: 48rem;
+ padding: 4rem;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: row;
+ background-color: #597285;
+ border-radius: 0.5rem;
+}
+
+#add-group-title-input {
+ height: 3.2rem;
+ flex: 1;
+ margin: 0;
+ padding: 0 1rem;
+ box-sizing: border-box;
+}
+
+#add-group-create-button {
+ height: 3.2rem;
+ font-size: 1.5rem;
+ margin: 0 0 0 1rem;
+ padding: 0 1rem;
+ box-sizing: border-box;
+ background-color: #7f93a1;
+}
+
+#add-group-create-button:hover,
+#add-group-create-button:active {
+ background-color: #97aebf;
+}
+
+@media only screen and (max-width: 959px) {
+ #add-group-form {
+ flex-direction: column;
+ }
+
+ #add-group-title-input {
+ flex: none;
+ }
+
+ #add-group-create-button {
+ margin: 2rem auto 0 auto;
+ width: 9rem;
+ }
+}
diff --git a/static/css/add-thing.css b/static/css/add-thing.css
index a540f5e82..06d2bf27b 100644
--- a/static/css/add-thing.css
+++ b/static/css/add-thing.css
@@ -327,43 +327,3 @@
width: 12rem;
}
}
-
-#add-group {
- background-color: #597285;
- width: 50%;
- min-width: 28rem;
- max-width: 65rem;
- margin: 1rem auto;
- padding: 1rem;
- display: flex;
- flex-direction: row;
- border-radius: 0.5rem;
- text-align: center;
- flex-wrap: wrap;
-}
-
-#add-group-label {
- flex-basis: 100%;
- flex-grow: 1;
-}
-
-#add-group-title-input {
- height: 1.75rem;
- background-color: #d2d9de;
- border: none;
- border-radius: 0.5rem;
- padding: 0.5rem;
- margin: 0.5rem 10px;
- font-size: 1.6rem;
- flex-grow: 1;
- margin-top: 15px;
-}
-
-#add-group-add-button {
- background-color: #7f93a1;
- border: none;
- border-radius: 0.5rem;
- padding: 1rem;
- color: #fff;
- margin-top: 15px;
-}
diff --git a/static/css/app.css b/static/css/app.css
index c6f08ff27..e5554cf60 100644
--- a/static/css/app.css
+++ b/static/css/app.css
@@ -189,115 +189,117 @@ h6 {
left: 9.5rem;
}
-#update-message-area {
- position: fixed;
+#menu-button,
+#back-button {
top: 2rem;
- width: 33%;
- left: calc(33% - 1rem);
- background-color: rgba(76, 124, 160, 0.6);
- border-radius: 0.5rem;
- padding: 2rem;
- text-align: center;
- color: white;
- font-size: 2rem;
- transform: translateY(0);
- transition: transform 0.25s ease;
- z-index: 100;
-}
-
-#update-message-area-text {
- padding-bottom: 2rem;
+ left: 2rem;
+ z-index: 0;
}
-#update-message-area > button {
- height: 4rem;
- background-color: #48779a;
- font-size: 1.5rem;
- margin: auto 0.5rem;
+#menu-button.menu-shown {
+ z-index: 1000;
}
-#update-message-area > button:hover,
-#update-message-area > button:active {
- background-color: #658196;
+#menu-button {
+ background: no-repeat center/100% url('/images/menu.svg');
}
-#update-message-area-reload {
- margin-right: 1rem;
+#back-button {
+ background: no-repeat center/100% url('/images/back.png');
}
-#update-message-area.hidden {
- transform: translateY(-100%) translateY(-4rem);
+#overflow-button {
+ bottom: 2rem;
+ right: 2rem;
+ background: no-repeat center/100% url('/images/overflow.svg');
}
-#message-area {
+/** New Overflow Menu **/
+.overflow-menu {
+ display: block;
position: fixed;
- bottom: 3rem;
- width: 33%;
- left: calc(33% - 1rem);
- background-color: rgba(76, 124, 160, 0.6);
+ width: 24rem;
+ padding: 1rem 0 1rem 0;
border-radius: 0.5rem;
- padding: 2rem;
- text-align: center;
- color: white;
- font-size: 2rem;
- transform: translateY(0);
- transition: transform 0.25s ease;
- z-index: 100;
+ background-color: #48779a;
+ color: #fff;
}
-#message-area.hidden {
- transform: translateY(100%) translateY(4rem);
+.overflow-menu.hidden {
+ display: none;
}
-#message-area.disconnected {
- background-color: #374956;
- z-index: 10001;
- color: #ccc;
+.overflow-menu.above ::after {
+ content: '';
+ position: absolute;
+ bottom: -1rem;
+ right: 1.8rem;
+ border-width: 1rem 1rem 0;
+ border-style: solid;
+ border-color: #48779a transparent;
+ display: block;
+ width: 0;
}
-@media only screen and (max-width: 730px) {
- #update-message-area,
- #message-area {
- width: calc(100% - 10rem);
- left: 3rem;
- }
+.overflow-menu.below ::before {
+ content: '';
+ position: absolute;
+ top: -1rem;
+ right: 1.8rem;
+ border-width: 0 1rem 1rem;
+ border-style: solid;
+ border-color: #48779a transparent;
+ display: block;
+ width: 0;
}
-#message-area > a:link,
-#message-area > a:visited,
-#message-area > a:hover,
-#message-area > a:active {
- color: white;
- display: block;
- width: 100%;
- height: 100%;
+.overflow-menu-item {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+ list-style-type: none;
}
-#menu-button,
-#back-button {
- top: 2rem;
- left: 2rem;
- z-index: 0;
+.overflow-menu-item:last-child {
+ border: none;
}
-#menu-button.menu-shown {
- z-index: 1000;
+.overflow-menu-item button {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ height: 4rem;
+ margin: 0;
+ padding: 0 1rem;
+ border-radius: 0;
+ border: none;
+ background-color: transparent;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
}
-#menu-button {
- background: no-repeat center/100% url('/images/menu.svg');
+.overflow-menu-item button:hover,
+.overflow-menu-item button:active {
+ background-color: #3a607c;
}
-#back-button {
- background: no-repeat center/100% url('/images/back.png');
+.overflow-menu-item button img {
+ width: 1.6rem;
+ height: 1.6rem;
+ padding: 0.8rem;
}
-#overflow-button {
- bottom: 2rem;
- right: 2rem;
- background: no-repeat center/100% url('/images/overflow.svg');
+.overflow-menu-item button span {
+ flex: 1;
+ text-align: left;
+ font-size: 1.6rem;
+ margin-left: 1rem;
}
+/* Old Overflow Menu (to be deprecated) */
#overflow-menu {
position: fixed;
bottom: 9rem;
@@ -565,3 +567,87 @@ body.hidden {
color: white;
background-color: #5288af;
}
+
+#update-message-area {
+ position: fixed;
+ top: 2rem;
+ width: 33%;
+ left: calc(33% - 1rem);
+ background-color: rgba(76, 124, 160, 0.6);
+ border-radius: 0.5rem;
+ padding: 2rem;
+ text-align: center;
+ color: white;
+ font-size: 2rem;
+ transform: translateY(0);
+ transition: transform 0.25s ease;
+ z-index: 100;
+}
+
+#update-message-area-text {
+ padding-bottom: 2rem;
+}
+
+#update-message-area > button {
+ height: 4rem;
+ background-color: #48779a;
+ font-size: 1.5rem;
+ margin: auto 0.5rem;
+}
+
+#update-message-area > button:hover,
+#update-message-area > button:active {
+ background-color: #658196;
+}
+
+#update-message-area-reload {
+ margin-right: 1rem;
+}
+
+#update-message-area.hidden {
+ transform: translateY(-100%) translateY(-4rem);
+}
+
+#message-area {
+ position: fixed;
+ bottom: 3rem;
+ width: 33%;
+ left: calc(33% - 1rem);
+ background-color: rgba(76, 124, 160, 0.6);
+ border-radius: 0.5rem;
+ padding: 2rem;
+ text-align: center;
+ color: white;
+ font-size: 2rem;
+ transform: translateY(0);
+ transition: transform 0.25s ease;
+ z-index: 100;
+}
+
+#message-area.hidden {
+ transform: translateY(100%) translateY(4rem);
+}
+
+#message-area.disconnected {
+ background-color: #374956;
+ z-index: 10001;
+ color: #ccc;
+}
+
+@media only screen and (max-width: 730px) {
+ #update-message-area,
+ #message-area {
+ width: calc(100% - 10rem);
+ left: 3rem;
+ }
+}
+
+#message-area > a:link,
+#message-area > a:visited,
+#message-area > a:hover,
+#message-area > a:active {
+ color: white;
+ display: block;
+ width: 100%;
+ height: 100%;
+}
diff --git a/static/css/group-context-menu.css b/static/css/group-context-menu.css
index 73c8b7689..4885886ac 100644
--- a/static/css/group-context-menu.css
+++ b/static/css/group-context-menu.css
@@ -1,99 +1,103 @@
#group-context-menu {
- text-align: center;
-}
-
-#group-context-menu-heading {
- line-height: 9.6rem;
-}
-
-#group-context-menu-heading-text {
- font-size: 1.6rem;
-}
-
-#group-context-menu-content {
- text-align: center;
- position: relative;
- top: calc(50% - 3.8rem);
- transform: translateY(-50%);
+ display: flex;
+ align-items: center;
+ justify-content: center;
}
-#group-context-menu-heading .hidden,
-#group-context-menu-content .hidden {
+#group-context-menu.hidden {
display: none;
}
-.group-context-menu-button {
- border: none;
- border-radius: 0.5rem;
- color: white;
+#group-context-menu h1 {
+ position: fixed;
+ top: 0;
+ line-height: 9.6rem;
+ margin: 0;
font-size: 1.6rem;
- width: 60%;
- min-width: 15rem;
- height: 6rem;
-}
-
-.group-context-menu-button.danger {
- background-color: #f55;
+ text-align: center;
+ font-family: 'Open Sans', sans-serif;
+ font-weight: normal;
+ width: calc(100% - 18rem);
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
-#group-context-menu-back-button {
- background-color: transparent;
+#group-context-menu h1 img {
+ vertical-align: middle;
+ margin-right: 0.3rem;
}
-#group-context-menu-content-edit {
- background-color: #597285;
- width: 50%;
- min-width: 28rem;
- max-width: 65rem;
- margin: 1rem auto;
- padding: 1rem;
+/* Edit Group Form */
+#edit-group-form {
+ width: calc(100% - 4rem);
+ max-width: 48rem;
+ padding: 4rem;
+ box-sizing: border-box;
display: flex;
flex-direction: row;
+ background-color: #597285;
border-radius: 0.5rem;
}
-#edit-group-metadata {
- flex: 1;
- padding: 1rem 1rem 0 1rem;
- text-align: left;
+#edit-group-form.hidden {
+ display: none;
}
-#edit-group-title {
- display: block;
- width: calc(100% - 1rem);
+#edit-group-title-input {
+ height: 3.2rem;
+ flex: 1;
+ margin: 0;
+ padding: 0 1rem;
+ box-sizing: border-box;
}
-.edit-group-button {
- height: 4rem;
+#edit-group-save-button {
+ height: 3.2rem;
+ font-size: 1.5rem;
+ margin: 0 0 0 1rem;
+ padding: 0 1rem;
+ box-sizing: border-box;
background-color: #7f93a1;
- margin: 1.25rem 0;
}
-.edit-group-button:disabled,
-.edit-group-button:disabled:hover,
-.edit-group-button:hover:active {
- background-color: #597285;
+#edit-group-save-button:hover,
+#edit-group-save-button:active {
+ background-color: #97aebf;
}
-#edit-group-label {
- display: block;
- text-align: left;
- color: #ccc;
- margin: 1rem 0;
- font-size: 1.5rem;
+/* Remove Group Form */
+#remove-group-form {
+ width: 100%;
+ text-align: center;
+}
+
+#remove-group-form.hidden {
+ display: none;
}
-#edit-group-label.error {
- color: orange;
+#remove-group-button {
+ border: none;
+ border-radius: 0.5rem;
+ color: white;
+ font-size: 1.6rem;
+ width: 60%;
+ min-width: 15rem;
+ height: 6rem;
+ background-color: #f55;
}
-@media only screen and (max-width: 800px) {
- #edit-group-spacer {
- display: block;
- margin-top: 1rem;
+@media only screen and (max-width: 959px) {
+ #edit-group-form {
+ flex-direction: column;
+ }
+
+ #edit-group-title-input {
+ flex: none;
}
- #edit-group-metadata {
- padding-bottom: 1rem;
+ #edit-group-save-button {
+ margin: 2rem auto 0 auto;
+ width: 9rem;
}
}
diff --git a/static/css/group.css b/static/css/group.css
index e5254f0d8..98d15293d 100644
--- a/static/css/group.css
+++ b/static/css/group.css
@@ -1,69 +1,75 @@
.group {
background: #5288af;
- border-radius: 5px;
- border: 1px solid #48779a;
- margin: 12px 0;
- min-width: 250px;
- min-height: 50px;
- max-height: 32px;
- padding-left: 5px;
- padding-right: 5px;
- position: relative;
- overflow: inherit;
+ border-radius: 0.5rem;
+ margin: 2rem 5rem;
+ min-height: 5rem;
+ max-height: 3.2rem;
}
.group.open {
- max-height: 100000px;
+ min-height: 24.4rem;
+ max-height: none;
}
.group:not(.open) .thing {
- opacity: 0;
+ display: none;
}
-.group div.bar {
- flex-direction: row;
- justify-content: space-between;
+.group .bar {
display: flex;
- padding: 5px 10px;
+ flex-direction: row;
+ padding: 0.5rem;
position: relative;
+ height: 5rem;
+ box-sizing: border-box;
}
-.group div.bar div.title {
+.group .bar .title {
+ flex: 1;
color: white;
+ line-height: 4rem;
+ text-align: left;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
}
-.group div.bar .leftcontainer {
- display: flex;
+.group .bar .overflow-button {
+ width: 3.2rem;
+ height: 3.2rem;
+ margin: 0.4rem;
+ padding: 0;
+ background-image: url('/images/overflow-small.svg');
+ background-color: transparent;
+ background-repeat: no-repeat;
+ background-size: 2.4rem;
+ background-position: center;
+ border: none;
+ border-radius: 2.5rem;
}
-.group div.bar button.edit {
- border: none;
- position: absolute;
- top: 5px;
- right: 1rem;
- width: 25px;
- border-radius: 50%;
- height: 25px;
- background: no-repeat center/100% url('/images/overflow.svg');
+.group .bar .overflow-button:hover {
+ background-color: #78abce;
+}
+
+.group .bar .overflow-button:active {
+ background-color: #7dafd2;
}
-.group div.bar button.foldIn {
- background-image: url('/images/right-arrow.png');
- transform: rotate(90deg);
- background-size: 15px 15px;
+.group .bar .expand-button {
+ background-image: url('/images/down-arrow.svg');
+ background-size: 2.4rem 2.4rem;
background-position: center;
background-repeat: no-repeat;
border: none;
- width: 25px;
- margin-right: 5px;
+ width: 3.2rem;
+ height: 3.2rem;
+ margin: 0.4rem;
background-color: transparent;
- height: 25px;
- transition: transform 0.3s;
- cursor: grab;
}
-.group.open div.bar button.foldIn {
- transform: rotate(-90deg);
+.group.open .bar .expand-button {
+ transform: rotate(-180deg);
}
.group.open.drag-target {
@@ -71,27 +77,35 @@
}
.drag-before::before {
+ display: block;
content: '';
- position: absolute;
+ position: relative;
border-top: 0.2rem dashed white;
width: 100%;
- top: -14px;
+ top: -1rem;
left: 0;
+ margin-bottom: -2px; /* Prevent it taking up space */
}
.drag-after::after {
+ display: block;
content: '';
- position: absolute;
+ position: relative;
border-top: 0.2rem dashed white;
width: 100%;
- bottom: -14px;
+ bottom: -1rem;
left: 0;
+ margin-bottom: -2px; /* Prevent it taking up space */
+}
+
+.group.open .bar:last-child {
+ margin-bottom: 19.2rem; /* Fill up an empty group */
}
.group-overflow-menu {
position: absolute;
- top: 4.5rem;
- right: 0.3rem;
+ top: 3.8rem;
+ right: -0.25rem;
background-color: #48779a;
color: white;
padding: 1rem 0;
@@ -102,37 +116,3 @@
z-index: 100;
text-align: left;
}
-
-.group-overflow-menu::after {
- content: '';
- position: absolute;
- top: -0.9rem;
- right: 1rem;
- border-width: 0 1rem 1rem;
- border-style: solid;
- border-color: #48779a transparent;
- display: block;
- width: 0;
-}
-
-.group-overflow-menu > a {
- color: #fff;
- font-size: 2rem;
- display: block;
- padding: 0.4rem 1rem;
- text-decoration: none;
-}
-
-.group-overflow-menu > a > img {
- height: 2rem;
- padding-right: 1rem;
- margin-bottom: -0.3rem;
-}
-
-.group-overflow-menu.hidden {
- transform: scale(0);
-}
-
-.group-overflow-menu > a:hover {
- background-color: #4d80a5;
-}
diff --git a/static/css/things.css b/static/css/things.css
index 3d5c84fe1..3edb8d4ec 100644
--- a/static/css/things.css
+++ b/static/css/things.css
@@ -14,29 +14,28 @@
top: 50%;
transform: translateY(-50%);
max-height: 100%;
- display: flex;
- flex-direction: column;
}
#things {
text-align: center;
font-size: 1.6rem;
color: #fff;
- flex-direction: column;
margin: auto;
- padding-left: 5px;
- padding-right: 5px;
- min-height: 50px;
+ min-height: 5rem;
width: calc(100% - 10rem);
}
#groups {
text-align: center;
font-size: 1.6rem;
- flex-direction: column;
color: #fff;
- display: flex;
- margin: auto;
+ width: 100%;
+ padding-bottom: 7.6rem;
+}
+
+#groups.hidden,
+#things.single-thing + #groups {
+ display: none;
}
#add-button {
@@ -45,6 +44,14 @@
background: no-repeat center/100% url('/images/add.svg');
}
+/* Add Thing Menu */
+#add-thing-menu {
+ position: fixed;
+ bottom: 8rem;
+ right: 2rem;
+ margin-right: 0;
+}
+
#add-thing-back-button {
background-color: transparent;
}
diff --git a/static/fluent/en-CA/main.ftl b/static/fluent/en-CA/main.ftl
index fb9c825e8..978eeed9c 100644
--- a/static/fluent/en-CA/main.ftl
+++ b/static/fluent/en-CA/main.ftl
@@ -30,6 +30,8 @@ thing-details =
.aria-label = View Properties
add-things =
.aria-label = Add New Things
+add-thing = Add thing
+add-group = Add group
## Floorplan
@@ -251,6 +253,10 @@ context-menu-save = Save
context-menu-remove = Remove
context-menu-show-on-floorplan = Show in floorplan view?
+## Group Context Menu
+edit-group-save =
+ .value = Save
+
## Capabilities
OnOffSwitch = On/Off Switch
@@ -426,6 +432,13 @@ loading = Loading…
new-web-thing-multiple = Multiple web things found
new-web-thing-from = from
+## New Group Screen
+new-group-heading = New Group
+new-group-input =
+ .placeholder = Enter group name
+new-group-save =
+ .value = Create
+
## Empty div Messages
no-things = No devices yet. Click + to scan for available devices.
diff --git a/static/fluent/en-GB/main.ftl b/static/fluent/en-GB/main.ftl
index 3c4c115ca..ac4136c58 100644
--- a/static/fluent/en-GB/main.ftl
+++ b/static/fluent/en-GB/main.ftl
@@ -30,6 +30,8 @@ thing-details =
.aria-label = View Properties
add-things =
.aria-label = Add New Things
+add-thing = Add thing
+add-group = Add group
## Floorplan
@@ -251,6 +253,10 @@ context-menu-save = Save
context-menu-remove = Remove
context-menu-show-on-floorplan = Show in floorplan view?
+## Group Context Menu
+edit-group-save =
+ .value = Save
+
## Capabilities
OnOffSwitch = On/Off Switch
@@ -414,8 +420,6 @@ new-thing-password =
new-thing-credentials-error = Incorrect credentials
new-thing-saved = Saved
new-thing-done = Done
-add-group = Add new group
-new-group-save = Create
## New Web Thing View
@@ -426,6 +430,13 @@ loading = Loading…
new-web-thing-multiple = Multiple web things found
new-web-thing-from = from
+## New Group Screen
+new-group-heading = New Group
+new-group-input =
+ .placeholder = Enter group name
+new-group-save =
+ .value = Create
+
## Empty div Messages
no-things = No devices yet. Click + to scan for available devices.
diff --git a/static/fluent/en-US/main.ftl b/static/fluent/en-US/main.ftl
index cb9426fe1..daa3944a3 100644
--- a/static/fluent/en-US/main.ftl
+++ b/static/fluent/en-US/main.ftl
@@ -30,6 +30,9 @@ thing-details =
.aria-label = View Properties
add-things =
.aria-label = Add New Things
+add-thing = Add thing
+add-group = Add group
+
## Floorplan
@@ -251,6 +254,10 @@ context-menu-save = Save
context-menu-remove = Remove
context-menu-show-on-floorplan = Show in floorplan view?
+## Group Context Menu
+edit-group-save =
+ .value = Save
+
## Capabilities
OnOffSwitch = On/Off Switch
@@ -414,8 +421,6 @@ new-thing-password =
new-thing-credentials-error = Incorrect credentials
new-thing-saved = Saved
new-thing-done = Done
-add-group = Add new group
-new-group-save = Create
## New Web Thing View
@@ -426,6 +431,13 @@ loading = Loading…
new-web-thing-multiple = Multiple web things found
new-web-thing-from = from
+## New Group Screen
+new-group-heading = New Group
+new-group-input =
+ .placeholder = Enter group name
+new-group-save =
+ .value = Create
+
## Empty div Messages
no-things = No devices yet. Click + to scan for available devices.
diff --git a/static/images/add-group-icon.svg b/static/images/add-group-icon.svg
new file mode 100644
index 000000000..21bc6dfd3
--- /dev/null
+++ b/static/images/add-group-icon.svg
@@ -0,0 +1,53 @@
+
+
+
+
diff --git a/static/images/add-thing-icon.svg b/static/images/add-thing-icon.svg
new file mode 100644
index 000000000..10f3cb0bb
--- /dev/null
+++ b/static/images/add-thing-icon.svg
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/static/images/down-arrow.svg b/static/images/down-arrow.svg
new file mode 100644
index 000000000..877313774
--- /dev/null
+++ b/static/images/down-arrow.svg
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/static/images/group.svg b/static/images/group.svg
new file mode 100644
index 000000000..0b784fe21
--- /dev/null
+++ b/static/images/group.svg
@@ -0,0 +1,66 @@
+
+
+
+
diff --git a/static/images/overflow-small.svg b/static/images/overflow-small.svg
new file mode 100644
index 000000000..25d862bea
--- /dev/null
+++ b/static/images/overflow-small.svg
@@ -0,0 +1,32 @@
+
+
+
+
diff --git a/static/images/up-arrow.svg b/static/images/up-arrow.svg
new file mode 100644
index 000000000..86a8d258e
--- /dev/null
+++ b/static/images/up-arrow.svg
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/static/index.html b/static/index.html
index eba62d8c3..702e1d19c 100644
--- a/static/index.html
+++ b/static/index.html
@@ -29,6 +29,20 @@
+
@@ -777,11 +791,6 @@
-
@@ -794,6 +803,28 @@
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
diff --git a/static/js/app.js b/static/js/app.js
index 2f5ab682c..b6def72e0 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -18,6 +18,8 @@ let ThingsScreen;
// eslint-disable-next-line prefer-const
let AddThingScreen;
// eslint-disable-next-line prefer-const
+let AddGroupScreen;
+// eslint-disable-next-line prefer-const
let Menu;
// eslint-disable-next-line prefer-const
let ContextMenu;
@@ -99,6 +101,7 @@ const App = {
});
AddThingScreen.init();
+ AddGroupScreen.init();
ContextMenu.init();
GroupContextMenu.init();
ThingsScreen.init();
@@ -479,6 +482,7 @@ API = require('./api').default;
GatewayModel = require('./models/gateway-model');
ThingsScreen = require('./views/things');
AddThingScreen = require('./views/add-thing');
+AddGroupScreen = require('./views/add-group');
Menu = require('./views/menu');
ContextMenu = require('./context-menu');
GroupContextMenu = require('./group-context-menu');
diff --git a/static/js/context-menu.js b/static/js/context-menu.js
index cc7b04fe0..378911174 100644
--- a/static/js/context-menu.js
+++ b/static/js/context-menu.js
@@ -3,6 +3,9 @@
*
* A menu of functions to perform on a Thing.
*
+ * TODO: Re-factor this as two separate dialogs.
+ * https://github.com/WebThingsIO/gateway/issues/3098
+ *
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/static/js/group-context-menu.js b/static/js/group-context-menu.js
index e75221075..3f3a39c5d 100644
--- a/static/js/group-context-menu.js
+++ b/static/js/group-context-menu.js
@@ -1,11 +1,14 @@
/**
- * Context Menu.
+ * Group Context Menu.
*
* A menu of functions to perform on a Group.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * TODO: Re-factor this as two separate dialogs
+ * https://github.com/WebThingsIO/gateway/issues/3098
*/
'use strict';
@@ -19,20 +22,20 @@ const GroupContextMenu = {
*/
init: function () {
this.element = document.getElementById('group-context-menu');
- this.editContent = document.getElementById('group-context-menu-content-edit');
- this.removeContent = document.getElementById('group-context-menu-content-remove');
+ this.editForm = document.getElementById('edit-group-form');
+ this.removeForm = document.getElementById('remove-group-form');
this.backButton = document.getElementById('group-context-menu-back-button');
this.headingText = document.getElementById('group-context-menu-heading-text');
this.saveButton = document.getElementById('edit-group-save-button');
- this.titleInput = document.getElementById('edit-group-title');
+ this.titleInput = document.getElementById('edit-group-title-input');
this.removeButton = document.getElementById('remove-group-button');
this.groupId = '';
// Add event listeners
window.addEventListener('_groupcontextmenu', this.show.bind(this));
this.backButton.addEventListener('click', this.hide.bind(this));
- this.saveButton.addEventListener('click', this.handleEdit.bind(this));
- this.removeButton.addEventListener('click', this.handleRemove.bind(this));
+ this.editForm.addEventListener('submit', this.handleEdit.bind(this));
+ this.removeForm.addEventListener('submit', this.handleRemove.bind(this));
},
/**
@@ -41,21 +44,20 @@ const GroupContextMenu = {
show: function (e) {
this.headingText.textContent = e.detail.groupTitle;
this.groupId = e.detail.groupId;
+ this.editForm.classList.add('hidden');
+ this.removeForm.classList.add('hidden');
this.element.classList.remove('hidden');
- this.editContent.classList.add('hidden');
- this.removeContent.classList.add('hidden');
-
switch (e.detail.action) {
case 'edit': {
this.titleInput.disabled = false;
this.saveButton.disabled = false;
this.titleInput.value = e.detail.groupTitle;
- this.editContent.classList.remove('hidden');
+ this.editForm.classList.remove('hidden');
break;
}
case 'remove':
- this.removeContent.classList.remove('hidden');
+ this.removeForm.classList.remove('hidden');
break;
}
},
@@ -70,16 +72,14 @@ const GroupContextMenu = {
},
/**
- * Handle click on edit option.
+ * Handle submission of edit form.
*/
- handleEdit: function () {
+ handleEdit: function (event) {
+ event.preventDefault();
this.titleInput.disabled = true;
this.saveButton.disabled = true;
const title = this.titleInput.value.trim();
- if (title.length === 0) {
- return;
- }
App.gatewayModel
.updateGroup(this.groupId, { title })
@@ -98,9 +98,10 @@ const GroupContextMenu = {
},
/**
- * Handle click on remove option.
+ * Handle submission of remove form.
*/
- handleRemove: function () {
+ handleRemove: function (event) {
+ event.preventDefault();
App.gatewayModel
.removeGroup(this.groupId)
.then(() => {
diff --git a/static/js/views/add-group.js b/static/js/views/add-group.js
new file mode 100644
index 000000000..215b9a586
--- /dev/null
+++ b/static/js/views/add-group.js
@@ -0,0 +1,62 @@
+/**
+ * Add Group Screen.
+ *
+ * UI for adding a group of things.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+'use strict';
+
+const App = require('../app');
+
+const AddGroupScreen = {
+ /**
+ * Initialise Add Group Screen.
+ */
+ init: function () {
+ this.element = document.getElementById('add-group-screen');
+ this.backButton = document.getElementById('add-group-back-button');
+ this.form = document.getElementById('add-group-form');
+ this.titleInput = document.getElementById('add-group-title-input');
+ // Add event listeners
+ this.backButton.addEventListener('click', this.hide.bind(this));
+ this.form.addEventListener('submit', this.handleFormSubmit.bind(this));
+ },
+
+ /**
+ * Show Add Group Screen.
+ */
+ show: function () {
+ this.element.classList.remove('hidden');
+ },
+
+ /**
+ * Hide Add Group Screen.
+ */
+ hide: function () {
+ this.element.classList.add('hidden');
+ this.titleInput.value = '';
+ },
+
+ /**
+ * Handle submission of the add group form.
+ *
+ * @param {Event} event A submit event.
+ */
+ handleFormSubmit: function (event) {
+ event.preventDefault();
+
+ App.gatewayModel
+ .addGroup({
+ title: this.titleInput.value,
+ })
+ .then(() => {
+ this.hide();
+ });
+ // TODO: Handle errors
+ },
+};
+
+module.exports = AddGroupScreen;
diff --git a/static/js/views/add-thing.js b/static/js/views/add-thing.js
index f89a9d296..1b78f776e 100644
--- a/static/js/views/add-thing.js
+++ b/static/js/views/add-thing.js
@@ -34,9 +34,6 @@ const AddThingScreen = {
this.addonsHint = document.getElementById('add-adapters-hint');
this.addonsHintAnchor = document.getElementById('add-adapters-hint-anchor');
this.addByUrlAnchor = document.getElementById('add-by-url-anchor');
- this.addGroupContainer = document.getElementById('add-group');
- this.addGroupInput = document.getElementById('add-group-title-input');
- this.addGroupButton = document.getElementById('add-group-add-button');
this.pairingTimeout = null;
this.visibleThings = new Set();
// Add event listeners
@@ -44,7 +41,6 @@ const AddThingScreen = {
this.cancelButton.addEventListener('click', this.hide.bind(this));
this.addonsHintAnchor.addEventListener('click', this.hide.bind(this));
this.addByUrlAnchor.addEventListener('click', this.showNewWebThing.bind(this));
- this.addGroupButton.addEventListener('click', this.addGroup.bind(this));
this.closing = false;
},
@@ -172,7 +168,6 @@ const AddThingScreen = {
*/
hide: function () {
this.element.classList.add('hidden');
- this.addGroupInput.value = '';
this.requestCancelPairing();
App.gatewayModel.refreshThings();
},
@@ -188,16 +183,6 @@ const AddThingScreen = {
e.preventDefault();
new NewWebThing();
},
-
- addGroup: function () {
- App.gatewayModel
- .addGroup({
- title: this.addGroupInput.value,
- })
- .then(() => {
- this.hide();
- });
- },
};
module.exports = AddThingScreen;
diff --git a/static/js/views/group.js b/static/js/views/group.js
index 9acedb108..a05e1d2c4 100644
--- a/static/js/views/group.js
+++ b/static/js/views/group.js
@@ -62,11 +62,8 @@ class Group {
bar.setAttribute('draggable', 'true');
bar.setAttribute('data-layout-index', '-1');
- const leftcontainer = document.createElement('DIV');
- leftcontainer.setAttribute('class', 'leftcontainer');
-
- const foldInButton = document.createElement('BUTTON');
- foldInButton.setAttribute('class', 'foldIn');
+ const expandButton = document.createElement('BUTTON');
+ expandButton.setAttribute('class', 'expand-button');
const cookie = `group-${this.id}-closed=1`;
if (
!document.cookie
@@ -76,7 +73,7 @@ class Group {
) {
this.element.classList.add('open');
}
- foldInButton.addEventListener('click', () => {
+ expandButton.addEventListener('click', () => {
document.cookie = `${cookie};expires=Thu, 01 Jan 1970 00:00:01 GMT`;
if (this.element.classList.contains('open')) {
this.element.classList.remove('open');
@@ -87,25 +84,23 @@ class Group {
this.element.classList.add('open');
}
});
- leftcontainer.appendChild(foldInButton);
+ bar.appendChild(expandButton);
const title = document.createElement('DIV');
title.setAttribute('class', 'title');
title.innerText = this.title;
- leftcontainer.appendChild(title);
-
- bar.appendChild(leftcontainer);
+ bar.appendChild(title);
- const editGroupButton = document.createElement('BUTTON');
- editGroupButton.setAttribute('class', 'edit');
- editGroupButton.addEventListener('click', () => {
+ const groupOverflowButton = document.createElement('BUTTON');
+ groupOverflowButton.setAttribute('class', 'overflow-button');
+ groupOverflowButton.addEventListener('click', () => {
if (this.overflowMenu.classList.contains('hidden')) {
this.overflowMenu.classList.remove('hidden');
} else {
this.overflowMenu.classList.add('hidden');
}
});
- bar.appendChild(editGroupButton);
+ bar.appendChild(groupOverflowButton);
this.buildOverflowMenu();
bar.appendChild(this.overflowMenu);
@@ -114,13 +109,21 @@ class Group {
}
buildOverflowMenu() {
- this.overflowMenu = document.createElement('DIV');
- this.overflowMenu.setAttribute('class', 'group-overflow-menu hidden');
-
- const editEntry = document.createElement('A');
- editEntry.href = '#';
- editEntry.innerHTML = `
${fluent.getMessage('edit')}`;
- editEntry.addEventListener('click', () => {
+ this.overflowMenu = document.createElement('menu');
+ this.overflowMenu.setAttribute('class', 'hidden overflow-menu below group-overflow-menu');
+
+ // Edit group menu item
+ const editItem = document.createElement('li');
+ editItem.classList.add('overflow-menu-item');
+ const editButton = document.createElement('button');
+ editItem.appendChild(editButton);
+ const editIcon = document.createElement('img');
+ editIcon.src = '/images/edit-plain.svg';
+ editButton.appendChild(editIcon);
+ const editLabel = document.createElement('span');
+ editLabel.textContent = fluent.getMessage('edit');
+ editButton.appendChild(editLabel);
+ editButton.addEventListener('click', () => {
const newEvent = new CustomEvent('_groupcontextmenu', {
detail: {
groupId: this.id,
@@ -129,13 +132,22 @@ class Group {
},
});
window.dispatchEvent(newEvent);
+ this.overflowMenu.classList.add('hidden');
});
- this.overflowMenu.appendChild(editEntry);
-
- const deleteEntry = document.createElement('A');
- deleteEntry.href = '#';
- deleteEntry.innerHTML = `
${fluent.getMessage('remove')}`;
- deleteEntry.addEventListener('click', () => {
+ this.overflowMenu.appendChild(editItem);
+
+ // Remove group menu item
+ const removeItem = document.createElement('li');
+ removeItem.classList.add('overflow-menu-item');
+ const removeButton = document.createElement('button');
+ removeItem.appendChild(removeButton);
+ const removeIcon = document.createElement('img');
+ removeIcon.src = '/images/remove.svg';
+ removeButton.appendChild(removeIcon);
+ const removeLabel = document.createElement('span');
+ removeLabel.textContent = fluent.getMessage('remove');
+ removeButton.appendChild(removeLabel);
+ removeButton.addEventListener('click', () => {
const newEvent = new CustomEvent('_groupcontextmenu', {
detail: {
groupId: this.id,
@@ -144,8 +156,9 @@ class Group {
},
});
window.dispatchEvent(newEvent);
+ this.overflowMenu.classList.add('hidden');
});
- this.overflowMenu.appendChild(deleteEntry);
+ this.overflowMenu.appendChild(removeItem);
}
handleDragStart(e) {
diff --git a/static/js/views/things.js b/static/js/views/things.js
index 60ee0d22e..add91ba9c 100644
--- a/static/js/views/things.js
+++ b/static/js/views/things.js
@@ -13,6 +13,7 @@
const page = require('page');
const ActionInputForm = require('./action-input-form');
const AddThingScreen = require('./add-thing');
+const AddGroupScreen = require('./add-group');
const App = require('../app');
const Constants = require('../constants');
const EventList = require('./event-list');
@@ -34,9 +35,14 @@ const ThingsScreen = {
this.addButton = document.getElementById('add-button');
this.menuButton = document.getElementById('menu-button');
this.backButton = document.getElementById('back-button');
+ this.addThingMenu = document.getElementById('add-thing-menu');
+ this.addThingButton = document.getElementById('add-thing-button');
+ this.addGroupButton = document.getElementById('add-group-button');
this.backRef = '/things';
this.backButton.addEventListener('click', () => page(this.backRef));
- this.addButton.addEventListener('click', AddThingScreen.show.bind(AddThingScreen));
+ this.addButton.addEventListener('click', this.toggleAddThingMenu.bind(this));
+ this.addThingButton.addEventListener('click', this.handleAddThingButtonClick.bind(this));
+ this.addGroupButton.addEventListener('click', this.handleAddGroupButtonClick.bind(this));
this.refreshThings = this.refreshThings.bind(this);
this.things = [];
@@ -115,9 +121,13 @@ const ThingsScreen = {
}
this.groupsElement.innerHTML = '';
if (groups.size !== 0) {
+ this.groupsElement.classList.remove('hidden');
groups.forEach((description) => {
new Group(description);
});
+ } else {
+ // Hide the groups div so it doesn't take up space
+ this.groupsElement.classList.add('hidden');
}
if (things.size !== 0) {
things.forEach((description, thingId) => {
@@ -192,6 +202,33 @@ const ThingsScreen = {
App.gatewayModel.subscribe(Constants.DELETE_THINGS, this.refreshThing);
},
+ /**
+ * Toggle the visibility of the add thing menu.
+ */
+ toggleAddThingMenu: function () {
+ if (this.addThingMenu.classList.contains('hidden')) {
+ this.addThingMenu.classList.remove('hidden');
+ } else {
+ this.addThingMenu.classList.add('hidden');
+ }
+ },
+
+ /**
+ * Handle a click on the add thing button in the add thing menu.
+ */
+ handleAddThingButtonClick: function () {
+ this.addThingMenu.classList.add('hidden');
+ AddThingScreen.show();
+ },
+
+ /**
+ * Handle a click on the add group button in the add thing menu.
+ */
+ handleAddGroupButtonClick: function () {
+ this.addThingMenu.classList.add('hidden');
+ AddGroupScreen.show();
+ },
+
/**
* Display an action input form.
*
diff --git a/webpack.config.js b/webpack.config.js
index 5ddb63121..5e4c2cf91 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -122,6 +122,7 @@ const webpackWeb = {
'./static/css/things.css',
'./static/css/menu.css',
'./static/css/add-thing.css',
+ './static/css/add-group.css',
'./static/css/context-menu.css',
'./static/css/group-context-menu.css',
'./static/css/thing.css',