diff --git a/src/assets/images/ic_generate.svg b/src/assets/images/ic_generate.svg
new file mode 100644
index 0000000..feee38d
--- /dev/null
+++ b/src/assets/images/ic_generate.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/plugins/Identifiers.js b/src/plugins/Identifiers.js
index 7a85a44..ea70a84 100644
--- a/src/plugins/Identifiers.js
+++ b/src/plugins/Identifiers.js
@@ -1,6 +1,7 @@
'use strict';
const OverlayPlugin = require('./util/OverlayPlugin');
+const {textInput, chipTag} = require('./util/components');
const HEX = '0123456789abcdef';
const DIGITS = '0123456789';
@@ -29,10 +30,6 @@ module.exports = class Identifiers extends OverlayPlugin {
// Register plugin
this.instance.identifiers = this;
- // Initial state
- this.invalidAndroidId = false;
- this.invalidDeviceId = false;
-
// Render components
this.renderToolbarButton();
this.renderWidget();
@@ -46,11 +43,11 @@ module.exports = class Identifiers extends OverlayPlugin {
const deviceId = values[1].match(/(device_id:)(\w+)/);
if (deviceId) {
- this.deviceInput.value = deviceId[2];
+ this.deviceInput.setValue(deviceId[2]);
}
const androidId = values[1].match(/(android_id:)(\w+)/);
if (androidId) {
- this.androidInput.value = androidId[2];
+ this.androidInput.setValue(androidId[2]);
}
});
}
@@ -79,55 +76,80 @@ module.exports = class Identifiers extends OverlayPlugin {
*/
renderWidget() {
// Create elements
- this.widget = document.createElement('div');
- this.container = document.createElement('div');
-
- // Generate title
- const title = document.createElement('div');
- title.className = 'gm-title';
- title.innerHTML = this.i18n.IDENTIFIERS_TITLE || 'Identifiers';
- this.container.appendChild(title);
+ const {modal, container} = this.createTemplateModal({
+ title: this.i18n.IDENTIFIERS_TITLE || 'Identifiers',
+ classes: 'gm-identifiers-plugin',
+ width: 378,
+ height: 422,
+ });
+ this.widget = modal;
+ this.container = container;
// Generate input rows
const inputs = document.createElement('div');
inputs.className = 'gm-inputs';
// Build field list
- const android = this.generateInput(
- 'android',
- 'Android ID',
- '',
- this.generateRandomAndroidId.bind(this),
- this.validateAndroidId.bind(this),
- );
- const device = this.generateInput(
- 'device',
- 'Device ID',
- '(IMEI/MEID)',
- this.generateRandomDeviceId.bind(this),
- this.validateDeviceId.bind(this),
- );
- inputs.appendChild(android);
- inputs.appendChild(device);
+ const androidInputDiv = document.createElement('div');
+ androidInputDiv.className = 'gm-identifier-android';
+ const labelAndroidId = document.createElement('label');
+ labelAndroidId.innerHTML = 'Android ID';
+ this.androidInput = textInput.createTextInput({
+ regexFilter: new RegExp(`^[${HEX}]{0,16}$`),
+ regexValidField: new RegExp(`^[${HEX}]{16}$`),
+ classes: 'gm-identifiers-android-input',
+ onChange: () => {
+ this.container.classList.remove('gm-identifiers-saved');
+ this.checkIDsValidity();
+ },
+ });
+ const generateAndroidIdBtn = document.createElement('div');
+ generateAndroidIdBtn.className = 'gm-icon-button gm-identifiers-android-generate';
+ generateAndroidIdBtn.onclick = this.generateRandomAndroidId.bind(this);
+ androidInputDiv.appendChild(this.androidInput.element);
+ androidInputDiv.appendChild(generateAndroidIdBtn);
+ inputs.appendChild(labelAndroidId);
+ inputs.appendChild(androidInputDiv);
+
+ const deviceInputDiv = document.createElement('div');
+ deviceInputDiv.className = 'gm-identifier-device';
+ const labelDeviceId = document.createElement('label');
+ labelDeviceId.innerHTML = 'Device ID (IMEI/MEID)';
+ this.deviceInput = textInput.createTextInput({
+ regexFilter: new RegExp(`^[${HEX}]{0,15}$`),
+ regexValidField: new RegExp(`^[${HEX}]{14,15}$`),
+ classes: 'gm-identifiers-device-input',
+ onChange: () => {
+ this.container.classList.remove('gm-identifiers-saved');
+ this.checkIDsValidity();
+ },
+ });
+ const generateDeviceIdBtn = document.createElement('div');
+ generateDeviceIdBtn.className = 'gm-icon-button gm-identifiers-device-generate';
+ generateDeviceIdBtn.onclick = this.generateRandomDeviceId.bind(this);
+ deviceInputDiv.appendChild(this.deviceInput.element);
+ deviceInputDiv.appendChild(generateDeviceIdBtn);
+ inputs.appendChild(labelDeviceId);
+ inputs.appendChild(deviceInputDiv);
+
+ const actionsDiv = document.createElement('div');
+ actionsDiv.className = 'gm-actions';
+ const separator = document.createElement('div');
+ separator.className = 'gm-separator';
+
+ const appliedTag = chipTag.createChip();
+ actionsDiv.appendChild(appliedTag.element);
this.submitBtn = document.createElement('button');
- this.submitBtn.innerHTML = this.i18n.IDENTIFIERS_UPDATE || 'Update';
- this.submitBtn.className = 'gm-action gm-identifiers-update';
- inputs.appendChild(this.submitBtn);
+ this.submitBtn.innerHTML = this.i18n.IDENTIFIERS_APPLY || 'Apply';
+ this.submitBtn.className = 'gm-btn gm-identifiers-update';
+ actionsDiv.appendChild(this.submitBtn);
+ this.submitBtn.disabled = true;
this.submitBtn.onclick = this.sendDataToInstance.bind(this);
this.container.appendChild(inputs);
-
- // Setup
- this.widget.className = 'gm-overlay gm-identifiers-plugin gm-hidden';
-
- // Add close button
- const close = document.createElement('div');
- close.className = 'gm-close-btn';
- close.onclick = this.toggleWidget.bind(this);
-
- this.widget.appendChild(close);
- this.widget.appendChild(this.container);
+ this.container.appendChild(separator);
+ this.container.appendChild(actionsDiv);
// Render into document
this.instance.root.appendChild(this.widget);
@@ -141,10 +163,10 @@ module.exports = class Identifiers extends OverlayPlugin {
sendDataToInstance(event) {
event.preventDefault();
- const androidId = this.androidInput.value;
- const deviceId = this.deviceInput.value;
+ const androidId = this.androidInput.getValue();
+ const deviceId = this.deviceInput.getValue();
- if (androidId && this.androidInput.checkValidity()) {
+ if (androidId) {
const json = {
channel: 'framework',
messages: ['set parameter android_id:' + androidId],
@@ -152,14 +174,15 @@ module.exports = class Identifiers extends OverlayPlugin {
this.instance.sendEvent(json);
}
- if (deviceId && this.deviceInput.checkValidity()) {
+ if (deviceId) {
const json = {
channel: 'settings',
messages: ['set parameter device_id:' + deviceId],
};
this.instance.sendEvent(json);
}
- this.toggleWidget();
+
+ this.container.classList.add('gm-identifiers-saved');
}
/**
@@ -172,17 +195,8 @@ module.exports = class Identifiers extends OverlayPlugin {
event.preventDefault();
}
- this.androidInput.value = this.generateHash(16, HEX);
- this.invalidAndroidId = false;
- this.checkErrors();
- }
-
- /**
- * Validate Android ID input value.
- */
- validateAndroidId() {
- this.invalidAndroidId = !this.androidInput.checkValidity();
- this.checkErrors();
+ this.androidInput.setValue(this.generateHash(16, HEX));
+ this.checkIDsValidity();
}
/**
@@ -195,74 +209,19 @@ module.exports = class Identifiers extends OverlayPlugin {
event.preventDefault();
}
- this.deviceInput.value = this.generateHash(15, DIGITS);
- this.invalidDeviceId = false;
- this.checkErrors();
+ this.deviceInput.setValue(this.generateHash(15, DIGITS));
+ this.checkIDsValidity();
}
/**
- * Validate Device ID input value.
+ * Check if the IDs are valid.
*/
- validateDeviceId() {
- this.invalidDeviceId = !this.deviceInput.checkValidity();
- this.checkErrors();
- }
-
- /**
- * Input form validation.
- */
- checkErrors() {
- this.androidInput.classList[this.invalidAndroidId ? 'add' : 'remove']('gm-error');
- this.deviceInput.classList[this.invalidDeviceId ? 'add' : 'remove']('gm-error');
-
- this.submitBtn.disabled = this.invalidAndroidId || this.invalidDeviceId;
- }
-
- /**
- * Create a form field element.
- *
- * @param {string} type Input type.
- * @param {string} label Input label.
- * @param {string} description Input description.
- * @param {string} generationMethod Input random value generation method.
- * @param {string} validationMethod Input value validation method.
- * @return {HTMLElement} The created input.
- */
- generateInput(type, label, description, generationMethod, validationMethod) {
- const field = document.createElement('div');
- const inputWrap = document.createElement('div');
- const input = document.createElement('input');
- const button = document.createElement('button');
-
- inputWrap.className = 'gm-input-group';
- input.className = 'gm-identifier-' + type + '-input';
- input.type = 'text';
- input.required = true;
- this.instance.addListener(input, 'keyup', validationMethod);
- inputWrap.appendChild(input);
-
- // Some customization
- if (type === 'device') {
- input.maxLength = 15;
- input.pattern = '[' + HEX + ']{14,15}';
- } else if (type === 'android') {
- input.maxLength = 16;
- input.pattern = '[' + HEX + ']{16}';
+ checkIDsValidity() {
+ if (this.androidInput.checkValidity() && this.deviceInput.checkValidity()) {
+ this.submitBtn.disabled = false;
+ } else {
+ this.submitBtn.disabled = true;
}
-
- button.className = 'gm-identifier-' + type + '-generate';
- button.innerHTML = this.i18n.IDENTIFIERS_GENERATE || 'Generate';
- button.onclick = generationMethod;
- inputWrap.appendChild(button);
-
- field.className = 'gm-identifier-' + type;
- field.innerHTML = '';
- field.appendChild(inputWrap);
-
- this[type + 'Input'] = input;
- this[type + 'Gen'] = button;
-
- return field;
}
/**
diff --git a/src/plugins/util/components.js b/src/plugins/util/components.js
index 1fffd38..570b2af 100644
--- a/src/plugins/util/components.js
+++ b/src/plugins/util/components.js
@@ -181,7 +181,8 @@ const slider = (() => {
*/
const textInput = (() => {
- const createTextInput = ({onChange = null, value = '', regexFilter, appendText = '', classes=''}) => {
+ const createTextInput = ({
+ onChange = null, value = '', regexFilter, regexValidField, appendText = '', classes=''}) => {
const inputDiv = document.createElement('div');
inputDiv.className = classes;
const inputDivContainer = document.createElement('div');
@@ -221,8 +222,16 @@ const textInput = (() => {
input.readOnly = readOnly;
};
+ const checkValidity = () => {
+ if (regexValidField && regexValidField.test(input.value)) {
+ return true;
+ }
+ return false;
+ };
+
input.addEventListener('input', (event) => {
const {value: v, selectionStart} = event.target;
+
if (regexFilter && !regexFilter.test(v)) {
// delete the last character if it doesn't match the regex
const correctedValue = v.slice(0, selectionStart - 1) + v.slice(selectionStart);
@@ -240,6 +249,7 @@ const textInput = (() => {
element: inputDiv,
setValue,
getValue,
+ checkValidity,
setReadOnly,
};
};
diff --git a/src/scss/base/_genymotion.scss b/src/scss/base/_genymotion.scss
index 0c476f6..d649e0c 100644
--- a/src/scss/base/_genymotion.scss
+++ b/src/scss/base/_genymotion.scss
@@ -236,8 +236,12 @@
border-radius: 4px;
&:hover{
- background: var(--gm-primary-color);
background: color-mix(in srgb, var(--gm-primary-color), transparent 85%);
color: var(--gm-primary-color)
}
+ &:disabled{
+ color: color-mix(in srgb, var(--gm-text-color), transparent 70%);
+ background: none;
+ cursor: default;
+ }
}
diff --git a/src/scss/components/_chipTag.scss b/src/scss/components/_chipTag.scss
index 3fb3d7f..884376c 100644
--- a/src/scss/components/_chipTag.scss
+++ b/src/scss/components/_chipTag.scss
@@ -2,7 +2,7 @@
.gm-tag{
&-success{
.gm-tag-container{
- display: flex;
+ display: inline-flex;
align-items: center;
border-radius: 8px;
padding: 2px 6px;
diff --git a/src/scss/components/_imei.scss b/src/scss/components/_imei.scss
index 961f1bd..1d7306d 100644
--- a/src/scss/components/_imei.scss
+++ b/src/scss/components/_imei.scss
@@ -2,30 +2,50 @@
* Identifiers (IMEI) plugin styles
*/
.device-renderer-instance .gm-identifiers-plugin {
- .gm-identifier-device,
- .gm-identifier-android {
- .gm-description {
- font-size: 90%;
- padding-left: 10px;
- }
-
- margin-bottom: 16px;
-
- .gm-input-group {
- display: flex;
+ .gm-modal-body {
+ display: flex;
+ flex-direction: column;
+ }
- button {
- margin-top: -5px;
+ .gm-identifiers-saved {
+ .gm-actions{
+ .gm-tag-success {
+ visibility: visible;
}
}
}
- input {
- margin-right: 6px;
+ .gm-inputs{
flex: 1;
-
- &.gm-error {
- border-bottom: 1px solid var(--gm-btn-bg-color);
+ .gm-identifier-android{
+ margin-bottom: $modal-section-mb;
+ }
+ .gm-identifier-android,
+ .gm-identifier-device{
+ display: flex;
+ justify-content: space-between;
+ gap: calc($modal-x-padding / 2);
+ align-items: center;
+ .gm-identifiers-device-input,
+ .gm-identifiers-android-input {
+ flex: 1;
+ }
+ .gm-identifiers-android-generate,
+ .gm-identifiers-device-generate {
+ mask-image: url('../assets/images/ic_generate.svg');
+ -webkit-mask-image: url('../assets/images/ic_generate.svg');
+ background-color: var(--gm-text-color);
+ cursor: pointer;
+ }
+ }
+ }
+ .gm-actions{
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 60px;
+ .gm-tag-success {
+ visibility: hidden;
}
}
}
diff --git a/src/scss/components/_widgetwindow.scss b/src/scss/components/_widgetwindow.scss
index f057313..30fd7ec 100644
--- a/src/scss/components/_widgetwindow.scss
+++ b/src/scss/components/_widgetwindow.scss
@@ -222,7 +222,7 @@
}
.gm-separator {
- width: calc(100% + $modal-x-padding + $modal-x-padding);
+ width: calc(100% + $modal-x-padding * 2);
margin-left: -$modal-x-padding;
margin-bottom: $spacing-m;
}
diff --git a/tests/unit/identifiers.test.js b/tests/unit/identifiers.test.js
index 4c57b6b..6d75626 100644
--- a/tests/unit/identifiers.test.js
+++ b/tests/unit/identifiers.test.js
@@ -25,8 +25,7 @@ describe('Identifiers Plugin', () => {
instance = new Instance();
new Identifiers(instance, {
IDENTIFIERS_TITLE: 'TEST IDENTIFIERS PLUGIN TITLE',
- IDENTIFIERS_UPDATE: 'TEST IDENTIFIERS PLUGIN UPDATE BUTTON',
- IDENTIFIERS_GENERATE: 'TEST IDENTIFIERS PLUGIN GENERATE BUTTON',
+ IDENTIFIERS_APPLY: 'TEST IDENTIFIERS PLUGIN UPDATE BUTTON',
});
plugin = document.getElementsByClassName('gm-identifiers-plugin')[0];
});
@@ -41,19 +40,18 @@ describe('Identifiers Plugin', () => {
test('has translations', () => {
expect(plugin.innerHTML).toEqual(expect.stringContaining('TEST IDENTIFIERS PLUGIN TITLE'));
expect(plugin.innerHTML).toEqual(expect.stringContaining('TEST IDENTIFIERS PLUGIN UPDATE BUTTON'));
- expect(plugin.innerHTML).toEqual(expect.stringContaining('TEST IDENTIFIERS PLUGIN GENERATE BUTTON'));
});
describe('field validation', () => {
- ['android', 'device'].forEach((field) => {
+ ['android', 'device'].forEach(() => {
test('button disabled', () => {
- const fieldInput = document.getElementsByClassName(`gm-identifier-${field}-input`)[0];
- const button = document.getElementsByClassName('gm-action gm-identifiers-update')[0];
- fieldInput.value = 'jean-michel';
- fieldInput.dispatchEvent(new Event('keyup'));
-
- expect(fieldInput.classList.contains('gm-error')).toBeTruthy();
- expect(button.disabled).toBeTruthy();
+ expect(identifiers.submitBtn.disabled).toBeTruthy();
+ identifiers.androidInput.setValue('jean-michel', true);
+ identifiers.deviceInput.setValue('jean-michel', true);
+ expect(identifiers.submitBtn.disabled).toBeTruthy();
+ identifiers.androidInput.setValue('0123456789abcdef', true);
+ identifiers.deviceInput.setValue('0123456789abcde', true);
+ expect(identifiers.submitBtn.disabled).toBeFalsy();
});
});
});
@@ -64,37 +62,32 @@ describe('Identifiers Plugin', () => {
test('unrelevant topics', () => {
['unrelevant', 'parameters unrelevant:value', ''].forEach((invalidValue) => {
instance.emit('settings', `${invalidValue}`);
- identifiers.validateAndroidId();
- identifiers.validateDeviceId();
- expect(identifiers.invalidAndroidId).toBeTruthy();
- expect(identifiers.invalidDeviceId).toBeTruthy();
+ expect(identifiers.androidInput.checkValidity()).toBeFalsy();
+ expect(identifiers.deviceInput.checkValidity()).toBeFalsy();
});
});
test('android_id', () => {
['jean-michel', '123', '0123456789abcdef0', ''].forEach((invalidValue) => {
instance.emit('settings', `parameter android_id:${invalidValue}`);
- identifiers.validateAndroidId();
- expect(identifiers.invalidAndroidId).toBeTruthy();
+ expect(identifiers.androidInput.checkValidity()).toBeFalsy();
});
['0123456789abcdef'].forEach((validValue) => {
instance.emit('settings', `parameter android_id:${validValue}`);
- identifiers.validateAndroidId();
- expect(identifiers.invalidAndroidId).toBeFalsy();
+ expect(identifiers.androidInput.checkValidity()).toBeTruthy();
});
});
test('device_id', () => {
['jean-michel', '123', '', '0123456789abcdef'].forEach((invalidValue) => {
instance.emit('settings', `parameter device_id:${invalidValue}`);
- identifiers.validateDeviceId();
- expect(identifiers.invalidDeviceId).toBeTruthy();
+ expect(identifiers.deviceInput.checkValidity()).toBeFalsy();
});
['0123456789abcd', '0123456789abcde'].forEach((validValue) => {
instance.emit('settings', `parameter device_id:${validValue}`);
- identifiers.validateDeviceId();
+ identifiers.deviceInput.checkValidity();
expect(identifiers.invalidDeviceId).toBeFalsy();
});
});
@@ -104,13 +97,13 @@ describe('Identifiers Plugin', () => {
test('outgoing events', () => {
const sendEventSpy = jest.spyOn(instance, 'sendEvent');
- identifiers.androidInput.value = 'jean-michel';
- identifiers.deviceInput.value = 'jean-michel';
+ identifiers.androidInput.setValue('jean-michel');
+ identifiers.deviceInput.setValue('jean-michel');
identifiers.sendDataToInstance(new Event(''));
expect(sendEventSpy).toHaveBeenCalledTimes(0);
- identifiers.androidInput.value = 'jean-michel';
- identifiers.deviceInput.value = '0123456789abcde';
+ identifiers.androidInput.setValue('jean-michel');
+ identifiers.deviceInput.setValue('0123456789abcde');
identifiers.sendDataToInstance(new Event(''));
expect(sendEventSpy).toHaveBeenCalledTimes(1);
expect(instance.outgoingMessages[0]).toEqual({
@@ -118,33 +111,38 @@ describe('Identifiers Plugin', () => {
messages: ['set parameter device_id:0123456789abcde'],
});
- identifiers.androidInput.value = '0123456789abcdef';
- identifiers.deviceInput.value = 'jean-michel';
+ identifiers.androidInput.setValue('0123456789abcdef');
+ // input jean-michel is invalid so we keep the previous value
+ identifiers.deviceInput.setValue('jean-michel');
identifiers.sendDataToInstance(new Event(''));
- expect(sendEventSpy).toHaveBeenCalledTimes(2);
+ expect(sendEventSpy).toHaveBeenCalledTimes(3);
expect(instance.outgoingMessages[1]).toEqual({
channel: 'framework',
messages: ['set parameter android_id:0123456789abcdef'],
});
+ expect(instance.outgoingMessages[2]).toEqual({
+ channel: 'settings',
+ messages: ['set parameter device_id:0123456789abcde'],
+ });
- identifiers.androidInput.value = '0123456789abcdef';
- identifiers.deviceInput.value = '0123456789abcde';
+ identifiers.androidInput.setValue('0123456789abcdef');
+ identifiers.deviceInput.setValue('0123456789abcdb');;
identifiers.sendDataToInstance(new Event(''));
- expect(sendEventSpy).toHaveBeenCalledTimes(4);
- expect(instance.outgoingMessages[2]).toEqual({
+ expect(sendEventSpy).toHaveBeenCalledTimes(5);
+ expect(instance.outgoingMessages[3]).toEqual({
channel: 'framework',
messages: ['set parameter android_id:0123456789abcdef'],
});
- expect(instance.outgoingMessages[3]).toEqual({
+ expect(instance.outgoingMessages[4]).toEqual({
channel: 'settings',
- messages: ['set parameter device_id:0123456789abcde'],
+ messages: ['set parameter device_id:0123456789abcdb'],
});
- identifiers.androidInput.value = '1234567891234@é%';
- identifiers.deviceInput.value = '0123456789abcde';
+ identifiers.androidInput.setValue('1234567891234@é%');
+ identifiers.deviceInput.setValue('0123456789abcde');
identifiers.sendDataToInstance(new Event(''));
- expect(sendEventSpy).toHaveBeenCalledTimes(5);
- expect(instance.outgoingMessages[4]).toEqual({
+ expect(sendEventSpy).toHaveBeenCalledTimes(7);
+ expect(instance.outgoingMessages[6]).toEqual({
channel: 'settings',
messages: ['set parameter device_id:0123456789abcde'],
});