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

Add scene description #1778

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 17 additions & 5 deletions front/cypress/e2e/routes/scene/Scene.cy.js
Expand Up @@ -57,6 +57,18 @@ describe('Scene view', () => {

cy.url().should('eq', `${Cypress.config().baseUrl}/dashboard/scene/my-scene`);
});
it('Should edit the scene description', () => {
cy.visit('/dashboard/scene/my-scene');

cy.contains('editScene.editDescriptionPlaceholder').click();
cy.get('div[class="page-header mt-0"]').type('My scene description');

// I don't know why, but I'm unable to get this button with
// the text. Using the class but it's not recommended otherwise!!
cy.get('.btn-success').then(buttons => {
cy.wrap(buttons[0]).click();
});
});
it('Should add new condition house empty', () => {
cy.visit('/dashboard/scene/my-scene');
cy.contains('editScene.addActionButton')
Expand All @@ -65,7 +77,7 @@ describe('Scene view', () => {

const i18n = Cypress.env('i18n');

cy.get('[class*="-control"]')
cy.get('div[class*="-control"]')
.click(0, 0, { force: true })
.get('[class*="-menu"]')
.find('[class*="-option"]')
Expand All @@ -78,7 +90,7 @@ describe('Scene view', () => {
cy.wrap(buttons[1]).click();
});

cy.get('[class*="-control"]')
cy.get('div[class*="-control"]')
.click(0, 0, { force: true })
.get('[class*="-menu"]')
.find('[class*="-option"]')
Expand All @@ -93,7 +105,7 @@ describe('Scene view', () => {

const i18n = Cypress.env('i18n');

cy.get('[class*="-control"]')
cy.get('div[class*="-control"]')
.click(0, 0, { force: true })
.get('[class*="-menu"]')
.find('[class*="-option"]')
Expand All @@ -106,7 +118,7 @@ describe('Scene view', () => {
cy.wrap(buttons[1]).click();
});

cy.get('[class*="-control"]')
cy.get('div[class*="-control"]')
.click(0, 0, { force: true })
.get('[class*="-menu"]')
.find('[class*="-option"]')
Expand All @@ -121,7 +133,7 @@ describe('Scene view', () => {

const i18n = Cypress.env('i18n');

cy.get('[class*="-control"]')
cy.get('div[class*="-control"]')
.click(0, 0, { force: true })
.get('[class*="-menu"]')
.find('[class*="-option"]')
Expand Down
2 changes: 2 additions & 0 deletions front/src/config/i18n/en.json
Expand Up @@ -1187,6 +1187,8 @@
},
"editScene": {
"editNamePlaceholder": "Enter a scene name",
"descriptionTitle": "Description",
"editDescriptionPlaceholder": "Enter a scene description",
"startButton": "Start",
"saveButton": "Save",
"deleteButton": "Delete",
Expand Down
2 changes: 2 additions & 0 deletions front/src/config/i18n/fr.json
Expand Up @@ -1187,6 +1187,8 @@
},
"editScene": {
"editNamePlaceholder": "Entrez un nom de scène",
"descriptionTitle": "Description",
"editDescriptionPlaceholder": "Enter une description pour la scène",
"startButton": "Démarrer",
"saveButton": "Sauvegarder",
"deleteButton": "Supprimer",
Expand Down
20 changes: 10 additions & 10 deletions front/src/routes/scene/SceneCard.jsx
Expand Up @@ -25,15 +25,15 @@ class SceneCard extends Component {

render(props, { saving }) {
return (
<div class="col-sm-6 col-lg-3">
<div class="card h-100">
<div
class={cx('dimmer', {
active: saving
})}
>
<div class="loader" />
<div class="dimmer-content">
<div class="col-sm-6 col-lg-3 pt-3 pb-3">
<div
class={`${cx('dimmer', {
active: saving
})} h-100`}
>
<div class="loader" />
<div class="dimmer-content h-100">
<div class="card h-100">
<div class="card-body p-3 text-center">
<div class={style.scene_icon}>
<i class={`fe fe-${props.scene.icon}`} />
Expand All @@ -52,7 +52,7 @@ class SceneCard extends Component {
</label>
</div>
<h4>{props.scene.name}</h4>
<div class="text-muted">{props.scene.description}</div>
<div class={`text-muted ${style.descriptionSceneEllipsis}`}>{props.scene.description}</div>
</div>
<div class="card-footer">
<div class="btn-list text-center">
Expand Down
37 changes: 36 additions & 1 deletion front/src/routes/scene/edit-scene/EditScenePage.jsx
Expand Up @@ -9,7 +9,7 @@ const EditScenePage = ({ children, ...props }) => (
<div class="page-main">
<div class="my-3 my-md-5">
<div class="container">
<div class="page-header">
<div class="page-header mb-1">
<h1
class="page-title"
style={{
Expand Down Expand Up @@ -45,6 +45,7 @@ const EditScenePage = ({ children, ...props }) => (
<span onClick={props.toggleIsNameEditable}>{props.scene.name}</span>
)}
</h1>

<label class="custom-switch m-0 ml-4">
<input
type="checkbox"
Expand All @@ -71,6 +72,40 @@ const EditScenePage = ({ children, ...props }) => (
</button>
</div>
</div>
<div class="page-header mt-0">
{props.isDescriptionEditable ? (
<form class="w-100" onSubmit={props.saveScene}>
<div class="input-group">
<Localizer>
<input
type="text"
class="form-control form-control-sm "
maxlength="100"
onChange={props.updateSceneDescription}
value={props.scene.description}
ref={props.setDescriptionInputRef}
placeholder={<Text id="editScene.editDescriptionPlaceholder" />}
/>
</Localizer>
<div class="input-group-append">
<button class="btn btn-primary btn-sm" onClick={props.saveScene}>
<Text id="global.save" />
</button>
</div>
</div>
</form>
) : (
<Localizer>
<span class="text-muted" onClick={props.toggleIsDescriptionEditable}>
{props.scene.description ? (
<span>{props.scene.description}</span>
) : (
<Text id="editScene.editDescriptionPlaceholder" />
)}
</span>
</Localizer>
)}
</div>
<div>
{props.error && (
<div class="alert alert-danger">
Expand Down
34 changes: 32 additions & 2 deletions front/src/routes/scene/edit-scene/index.js
Expand Up @@ -89,7 +89,7 @@ class EditScene extends Component {
this.setState({ saving: true, error: false });
try {
await this.props.httpClient.patch(`/api/v1/scene/${this.props.scene_selector}`, this.state.scene);
this.setState({ isNameEditable: false });
this.setState({ isNameEditable: false, isDescriptionEditable: false });
} catch (e) {
console.error(e);
this.setState({ error: true });
Expand Down Expand Up @@ -303,6 +303,18 @@ class EditScene extends Component {
this.nameInput = nameInput;
};

toggleIsDescriptionEditable = async () => {
await this.setState(prevState => ({ isDescriptionEditable: !prevState.isDescriptionEditable }));
console.log(this.state.isDescriptionEditable);
if (this.state.isDescriptionEditable) {
this.descriptionInput.focus();
}
};

setDescriptionInputRef = descriptionInput => {
this.descriptionInput = descriptionInput;
};

updateSceneName = e => {
this.setState(prevState => {
const newState = update(prevState, {
Expand All @@ -315,6 +327,20 @@ class EditScene extends Component {
return newState;
});
};

updateSceneDescription = e => {
this.setState(prevState => {
const newState = update(prevState, {
scene: {
description: {
$set: e.target.value
}
}
});
return newState;
});
};

duplicateScene = () => {
route(`/dashboard/scene/${this.props.scene_selector}/duplicate`);
};
Expand All @@ -339,7 +365,7 @@ class EditScene extends Component {
);
}

render(props, { saving, error, variables, scene, isNameEditable, triggersVariables }) {
render(props, { saving, error, variables, scene, isNameEditable, isDescriptionEditable, triggersVariables }) {
return (
scene && (
<EditScenePage
Expand All @@ -366,6 +392,10 @@ class EditScene extends Component {
updateSceneName={this.updateSceneName}
setNameInputRef={this.setNameInputRef}
duplicateScene={this.duplicateScene}
updateSceneDescription={this.updateSceneDescription}
toggleIsDescriptionEditable={this.toggleIsDescriptionEditable}
isDescriptionEditable={isDescriptionEditable}
setDescriptionInputRef={this.setDescriptionInputRef}
/>
)
);
Expand Down
12 changes: 12 additions & 0 deletions front/src/routes/scene/style.css
Expand Up @@ -33,3 +33,15 @@
top: 5%;
right: 3%;
}

.descriptionScene {
color: #9aa0ac;
}

.descriptionSceneEllipsis {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
-webkit-box-orient: vertical;
}
1 change: 1 addition & 0 deletions server/lib/scene/scene.duplicate.js
Expand Up @@ -28,6 +28,7 @@ async function duplicate(selector, name, icon) {
const newScene = {
name,
icon,
description: plainExistingScene.description,
active: plainExistingScene.active,
actions: plainExistingScene.actions,
triggers: plainExistingScene.triggers,
Expand Down
3 changes: 1 addition & 2 deletions server/lib/scene/scene.get.js
Expand Up @@ -3,7 +3,7 @@ const Sequelize = require('sequelize');
const db = require('../../models');

const DEFAULT_OPTIONS = {
fields: ['id', 'name', 'icon', 'selector', 'active', 'last_executed', 'updated_at'],
fields: ['id', 'name', 'description', 'icon', 'selector', 'active', 'last_executed', 'updated_at'],
skip: 0,
order_dir: 'ASC',
order_by: 'name',
Expand Down Expand Up @@ -41,7 +41,6 @@ async function get(options) {
const scenes = await db.Scene.findAll(queryParams);

const scenesPlain = scenes.map((scene) => scene.get({ plain: true }));

return scenesPlain;
}

Expand Down
8 changes: 8 additions & 0 deletions server/migrations/20230511161620-description-scene.js
@@ -0,0 +1,8 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.addColumn('t_scene', 'description', {
type: Sequelize.STRING,
});
},
down: async (queryInterface, Sequelize) => {},
};
4 changes: 4 additions & 0 deletions server/models/scene.js
Expand Up @@ -113,6 +113,10 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false,
type: DataTypes.STRING,
},
description: {
allowNull: true,
type: DataTypes.STRING,
},
icon: {
allowNull: false,
type: DataTypes.ENUM(iconList),
Expand Down