refactor(extension): complete extension rewrite #68

Merged
kiyui merged 10 commits from bicycle-rewrite into master 9 months ago
  1. 233
      .eslintrc.yml
  2. 1
      .gitignore
  3. 43
      night-light-slider.timur@linux.com/convenience.js
  4. 520
      night-light-slider.timur@linux.com/extension.js
  5. 3
      night-light-slider.timur@linux.com/metadata.json
  6. 6
      night-light-slider.timur@linux.com/org.gnome.shell.extensions.nightlightslider.data.gresource.xml
  7. 329
      night-light-slider.timur@linux.com/prefs.js
  8. 226
      night-light-slider.timur@linux.com/prefs.ui
  9. 32
      night-light-slider.timur@linux.com/schemas/org.gnome.shell.extensions.nightlightslider.gschema.xml
  10. 11
      package.json
  11. 509
      yarn.lock

233
.eslintrc.yml

@ -0,0 +1,233 @@
env:
es6: true
extends: 'eslint:recommended'
rules:
array-bracket-newline:
- error
- consistent
array-bracket-spacing:
- error
- never
array-callback-return: error
arrow-parens:
- error
- as-needed
arrow-spacing: error
block-scoped-var: error
block-spacing: error
brace-style: error
# Waiting for this to have matured a bit in eslint
# camelcase:
# - error
# - properties: never
# allow: [^vfunc_, ^on_, _instance_init]
comma-dangle:
- error
- always-multiline
comma-spacing:
- error
- before: false
after: true
comma-style:
- error
- last
computed-property-spacing: error
curly:
- error
- multi-or-nest
- consistent
dot-location:
- error
- property
eol-last: error
eqeqeq: error
func-call-spacing: error
func-name-matching: error
func-style:
- error
- declaration
- allowArrowFunctions: true
indent:
- error
- 4
- ignoredNodes:
# Allow not indenting the body of GObject.registerClass, since in the
# future it's intended to be a decorator
- 'CallExpression[callee.object.name=GObject][callee.property.name=registerClass] > ClassExpression:first-child'
# Allow dedenting chained member expressions
MemberExpression: 'off'
key-spacing:
- error
- beforeColon: false
afterColon: true
keyword-spacing:
- error
- before: true
after: true
linebreak-style:
- error
- unix
lines-between-class-members: error
max-nested-callbacks: error
max-statements-per-line: error
new-parens: error
no-array-constructor: error
no-await-in-loop: error
no-caller: error
no-constant-condition:
- error
- checkLoops: false
no-div-regex: error
no-empty:
- error
- allowEmptyCatch: true
no-extra-bind: error
no-extra-parens:
- error
- all
- conditionalAssign: false
nestedBinaryExpressions: false
returnAssign: false
no-implicit-coercion:
- error
- allow:
- '!!'
no-invalid-this: error
no-iterator: error
no-label-var: error
no-lonely-if: error
no-loop-func: error
no-nested-ternary: error
no-new-object: error
no-new-wrappers: error
no-octal-escape: error
no-proto: error
no-prototype-builtins: 'off'
no-restricted-properties:
- error
- object: Lang
property: bind
message: Use arrow notation or Function.prototype.bind()
- object: Lang
property: Class
message: Use ES6 classes
- object: imports
property: mainloop
message: Use GLib main loops and timeouts
no-restricted-syntax:
- error
- selector: >-
MethodDefinition[key.name="_init"] >
FunctionExpression[params.length=1] >
BlockStatement[body.length=1]
CallExpression[arguments.length=1][callee.object.type="Super"][callee.property.name="_init"] >
Identifier:first-child
message: _init() that only calls super._init() is unnecessary
- selector: >-
MethodDefinition[key.name="_init"] >
FunctionExpression[params.length=0] >
BlockStatement[body.length=1]
CallExpression[arguments.length=0][callee.object.type="Super"][callee.property.name="_init"]
message: _init() that only calls super._init() is unnecessary
no-return-assign: error
no-return-await: error
no-self-compare: error
no-shadow: error
no-shadow-restricted-names: error
no-spaced-func: error
no-tabs: error
no-template-curly-in-string: error
no-throw-literal: error
no-trailing-spaces: error
no-undef-init: error
no-unneeded-ternary: error
no-unused-expressions: error
no-unused-vars:
- error
# Vars use a suffix _ instead of a prefix because of file-scope private vars
- varsIgnorePattern: (^unused|_$)
argsIgnorePattern: ^(unused|_)
no-useless-call: error
no-useless-computed-key: error
no-useless-concat: error
no-useless-constructor: error
no-useless-rename: error
no-useless-return: error
no-whitespace-before-property: error
no-with: error
nonblock-statement-body-position:
- error
- below
object-curly-newline:
- error
- consistent: true
object-curly-spacing: error
object-shorthand: error
operator-assignment: error
operator-linebreak: error
# These may be a bit controversial, we can try them out and enable them later
# prefer-const: error
# prefer-destructuring: error
prefer-numeric-literals: error
prefer-promise-reject-errors: error
prefer-rest-params: error
prefer-spread: error
prefer-template: error
quotes:
- error
- single
- avoidEscape: true
require-await: error
rest-spread-spacing: error
semi:
- error
- always
semi-spacing:
- error
- before: false
after: true
semi-style: error
space-before-blocks: error
space-before-function-paren:
- error
- named: never
# for `function ()` and `async () =>`, preserve space around keywords
anonymous: always
asyncArrow: always
space-in-parens: error
space-infix-ops:
- error
- int32Hint: false
space-unary-ops: error
spaced-comment: error
switch-colon-spacing: error
symbol-description: error
template-curly-spacing: error
template-tag-spacing: error
unicode-bom: error
valid-jsdoc:
- error
- requireReturn: false
wrap-iife:
- error
- inside
yield-star-spacing: error
yoda: error
globals:
ARGV: readonly
Debugger: readonly
GIRepositoryGType: readonly
globalThis: readonly
imports: readonly
Intl: readonly
log: readonly
logError: readonly
print: readonly
printerr: readonly
global: readonly
_: readonly
C_: readonly
N_: readonly
ngettext: readonly
parserOptions:
ecmaVersion: 2019

1
.gitignore

@ -1,3 +1,4 @@
node_modules/
night-light-slider.timur@linux.com.zip
*.compiled
*.gresource

43
night-light-slider.timur@linux.com/convenience.js

@ -1,27 +1,24 @@
/* global imports log */
const Gio = imports.gi.Gio;
const Me = imports.misc.extensionUtils.getCurrentExtension();
/* exported debounce setInterval */
const {GLib} = imports.gi;
function getSettings() {
// eslint-disable-line no-unused-vars
const schema = Me.metadata["settings-schema"];
const schemaDir = Me.dir.get_child("schemas");
function debounce(func, wait, options = {priority: GLib.PRIORITY_DEFAULT}) {
let sourceId;
return function (...args) {
const debouncedFunc = () => {
sourceId = null;
func.apply(this, args);
};
log(`Attempting to load schema ${schema} from path ${schemaDir.get_path()}`);
const schemaSource = schemaDir.query_exists(null)
? Gio.SettingsSchemaSource.new_from_directory(
schemaDir.get_path(),
Gio.SettingsSchemaSource.get_default(),
false
)
: Gio.SettingsSchemaSource.get_default();
const settingsSchema = schemaSource.lookup(schema, true);
if (!settingsSchema) {
throw new Error(
`Schema ${schema} could not be loaded for night light slider extension.`
);
}
// It is a programmer error to attempt to remove a non-existent source
if (sourceId)
GLib.Source.remove(sourceId);
sourceId = GLib.timeout_add(options.priority, wait, debouncedFunc);
};
}
return new Gio.Settings({ settings_schema: settingsSchema });
function setInterval(func, delay, ...args) {
const wrappedFunc = () => {
return func.apply(this, args) || true;
};
return GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, wrappedFunc);
}

520
night-light-slider.timur@linux.com/extension.js

@ -1,282 +1,310 @@
/* global imports log */
const St = imports.gi.St;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Main = imports.ui.main;
const Slider = imports.ui.slider;
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator init */
const {Gio, GLib, GObject, St} = imports.gi;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
const GObject = imports.gi.GObject;
// Globals
const INDEX = 2;
const BUS_NAME = "org.gnome.SettingsDaemon.Color";
const OBJECT_PATH = "/org/gnome/SettingsDaemon/Color";
const COLOR_SCHEMA = "org.gnome.settings-daemon.plugins.color";
/* eslint-disable */
const ColorInterface =
'<node> \
<interface name="org.gnome.SettingsDaemon.Color"> \
<property name="Temperature" type="d" access="readwrite"/> \
<property name="NightLightActive" type="b" access="read"/> \
</interface> \
</node>';
/* eslint-enable */
const NightLightSlider = GObject.registerClass(
{
GType: "NightLightSlider",
},
class NightLightSlider extends PanelMenu.SystemIndicator {
_init(schema, settings) {
super._init("night-light-symbolic");
this._schema = schema;
this._min = settings.minimum;
this._max = settings.maximum;
this._listeners = [];
// Set up view
this._item = new PopupMenu.PopupBaseMenuItem({ activate: false });
this.menu.addMenuItem(this._item);
if (settings.enable_icon) {
this._icon = new St.Icon({
icon_name: "night-light-symbolic",
style_class: "popup-menu-icon",
const Slider = imports.ui.slider;
// Get running panel instance
const {panel} = imports.ui.main;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const {debounce, setInterval} = Me.imports.convenience;
// GSettings schema
const COLOR_SCHEMA = 'org.gnome.settings-daemon.plugins.color';
// D-Bus
const BUS_NAME = 'org.gnome.SettingsDaemon.Color';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Color';
const ColorInterface = `<node>
<interface name="org.gnome.SettingsDaemon.Color">
<property name="NightLightActive" type="b" access="read"/>
<property name="Temperature" type="d" access="read"/>
</interface>
</node>`;
const ColorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface);
// Brightness D-Bus
const {loadInterfaceXML} = imports.misc.fileUtils;
const BRIGHTNESS_BUS_NAME = 'org.gnome.SettingsDaemon.Power';
const BRIGHTNESS_OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';
const BrightnessInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Power.Screen');
const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface);
var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
_init(indicator, options) {
super._init();
// Decorate _sync method
this._sync = debounce(this.__sync.bind(this), 500);
// Hijacked indicator instance
this._indicator = indicator;
// Indicator options
this._options = options;
// Night Light GSettings
this._settings = new Gio.Settings({schema_id: COLOR_SCHEMA});
// Night Light D-Bus
this._proxy = new ColorProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
(proxy, error) => {
if (error) {
log(`ColorProxy: ${error.message}`);
return;
}
this._proxyChangedId = this._proxy.connect('g-properties-changed',
this._sync.bind(this));
this._sync();
});
// Write-only Brightness D-Bus
this._brightnessProxy = new BrightnessProxy(Gio.DBus.session, BRIGHTNESS_BUS_NAME, BRIGHTNESS_OBJECT_PATH,
(proxy, error) => {
if (error)
log(`BrightnessProxy: ${error.message}`);
});
// We create our slider for the Panel AggregateMenu
this._item = new PopupMenu.PopupBaseMenuItem({activate: false});
this.menu.addMenuItem(this._item);
// Create the slider
this._slider = new Slider.Slider(0);
this._sliderChangedId = this._slider.connect('notify::value',
this._sliderChanged.bind(this));
this._slider.accessible_name = _('Night Light Temperature');
this._slider_icon = new St.Icon({icon_name: 'night-light-symbolic',
style_class: 'popup-menu-icon'});
// Add the slider & its icon to the base menu
this._item.add(this._slider_icon);
this._item.add_child(this._slider);
// Connect menu signals to the slider
this._item.connect('button-press-event', (actor, event) => {
return this._slider.startDragging(event);
});
this._item.connect('key-press-event', (actor, event) => {
return this._slider.emit('key-press-event', event);
});
this._item.add(this._icon);
}
// Slider
this._slider = new Slider.Slider(0);
this._slider.connect("notify::value", this._sliderChanged.bind(this));
this._slider.accessible_name = "Temperature";
this._item.add(this._slider, { expand: true });
// Connect events
this._item.connect("button-press-event", (o, event) =>
this._slider.startDragging(event)
);
this._item.connect("key-press-event", (o, event) =>
this._slider.onKeyPressEvent(actor, event)
);
// Update initial view
this._updateView();
this._item.connect('scroll-event', (actor, event) => {
return this._slider.emit('scroll-event', event);
});
// Connect indicator signals to the slider
this._indicatorShowId = this._indicator.connect('show', () => {
this._updateIndicatorVisibility();
});
this._indicatorScrollId = this._indicator.connect('scroll-event', (actor, event) => {
return this._slider.emit('scroll-event', event);
});
// Because SystemIndicator is a ClutterActor, overriding the destroy()
// method directly is bad idea. Instead PanelMenu.Button connects to
// the signal, so we can override that callback and chain-up.
this.connect('destroy', this._onDestroy.bind(this));
}
_proxyHandler(proxy, error) {
if (error) {
log(error.message);
return;
}
this.proxy.connect("g-properties-changed", this.update_view.bind(this));
_sliderChanged() {
const {swapAxis, minimum, maximum, brightnessSync} = this._options;
const percent = swapAxis
? 1 - this._slider.value
: this._slider.value;
const temperature = percent * (maximum - minimum) + minimum;
// Block updates from ColorProxy over the 5s smear duration
this._proxy.block_signal_handler(this._proxyChangedId);
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 5000,
() => this._proxy.unblock_signal_handler(this._proxyChangedId));
// Update GSettings
this._settings.set_uint('night-light-temperature', temperature);
if (brightnessSync && this._brightnessProxy.Brightness >= 0)
this._brightnessProxy.Brightness = this._slider.value * 100;
}
_sliderChanged(slider) {
const temperature =
parseInt(this._slider.value * (this._max - this._min)) + this._min;
this._schema.set_uint("night-light-temperature", temperature);
_changeSlider(value) {
this._slider.block_signal_handler(this._sliderChangedId);
this._slider.value = value;
this._slider.unblock_signal_handler(this._sliderChangedId);
}
this._listeners.forEach((callback) => {
callback(temperature, this._slider.value);
});
_updateIndicatorVisibility() {
this._indicator.visible = this._indicator_visibility;
}
_onSliderChanged(callback) {
this._listeners.push(callback);
__sync() {
const {showAlways, showStatusIcon, swapAxis, minimum, maximum} = this._options;
const active = this._proxy.NightLightActive;
this._item.visible = active || showAlways;
this._indicator_visibility = active && showStatusIcon;
this._updateIndicatorVisibility();
if (active) {
const percent = (this._proxy.Temperature - minimum) / (maximum - minimum);
if (swapAxis)
this._changeSlider(1 - percent);
else
this._changeSlider(percent);
}
}
_updateView() {
// Update temperature view
const temperature = this._schema.get_uint("night-light-temperature");
const value = (temperature - this._min) / (this._max - this._min);
this._slider.value = value;
updateOption(option, value) {
this._options[option] = value;
switch (option) {
case 'showAlways':
case 'showStatusIcon':
return this._sync();
}
}
_scroll(event) {
this._slider.scroll(event);
_onDestroy() {
// Unassign DBus proxies
this._proxy.disconnect(this._proxyChangedId);
this._proxy = null;
this._brightnessProxy = null;
// Delete top-level items
this._item.destroy();
this._slider = null;
this._slider_icon = null;
this._item = null;
// Disconnect external signals
this._indicator.disconnect(this._indicatorShowId);
this._indicator.disconnect(this._indicatorScrollId);
}
}
);
});
class NightLightSchedule {
constructor(schema) {
this._schema = schema;
this._enabled = false;
}
_updateSchedule() {
if (!this._enabled) {
return false;
constructor(settings) {
this._settings = settings;
}
const date = new Date();
const hours = date.getHours();
date.setHours(hours - 6);
const from = date.getHours();
date.setHours(hours + 6);
const to = date.getHours();
log(
`[night-light-slider] Setting night light schedule from ${from} to ${to}`
);
this._schema.set_boolean("night-light-schedule-automatic", false);
this._schema.set_double("night-light-schedule-to", to);
this._schema.set_double("night-light-schedule-from", from);
return true;
}
_enableLoop() {
this._enabled = true;
// Get original values to reset to
this._to = this._schema.get_double("night-light-schedule-to");
this._from = this._schema.get_double("night-light-schedule-from");
this._auto = this._schema.get_boolean("night-light-schedule-automatic");
// Start loop
this.loopId = GLib.timeout_add(
GLib.PRIORITY_DEFAULT,
1000 * 60 * 60,
this._updateSchedule.bind(this)
);
this._updateSchedule();
}
_disableLoop() {
if (this._enabled) {
this._schema.set_double("night-light-schedule-to", this._to);
this._schema.set_double("night-light-schedule-from", this._from);
this._schema.set_boolean("night-light-schedule-automatic", this._auto);
enableTimer() {
this._settings.set_boolean('night-light-schedule-automatic', false);
// Update schedule every 1 hour
this._timerId = setInterval(this._updateSchedule.bind(this), 60 * 60 * 1000);
this._updateSchedule();
}
}
}
class NightLightExtension {
constructor() {
this._schema = new Gio.Settings({ schema: COLOR_SCHEMA });
this._colorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface);
this._scheduleUpdater = new NightLightSchedule(this._schema);
// Night light icon
this._icon = Main.panel.statusArea.aggregateMenu._nightLight;
this._indicator = null;
this._construct = () =>
new Error("[night-light-slider] View construct stub not set up!");
this._deconstruct = () =>
new Error("[night-light-slider] View deconstruct stub not set up!");
}
enable() {
// Settings
const settings = Convenience.getSettings();
// Enable night light, otherwise why use this extension :D?
this._schema.set_boolean("night-light-enabled", true);
// Create and add widget
this._indicator = new NightLightSlider(this._schema, {
minimum: settings.get_int("minimum"),
maximum: settings.get_int("maximum"),
enable_icon: !settings.get_boolean("show-in-submenu"),
});
// Set up display construction stubs
if (settings.get_boolean("show-in-submenu")) {
this._construct = () => {
Main.panel.statusArea.aggregateMenu._nightLight.menu
._getMenuItems()[0]
.menu.addMenuItem(this._indicator.menu);
};
} else {
this._construct = () => {
Main.panel.statusArea.aggregateMenu.menu.addMenuItem(
this._indicator.menu,
INDEX
);
};
disableTimer() {
if (this._timerId) {
this._settings.set_boolean('night-light-schedule-automatic', true);
GLib.Source.remove(this._timerId);
this._timerId = null;
}
}
this._deconstruct = () => this._indicator.menu.destroy();
_updateSchedule() {
const now = Date.now();
// Set a schedule span of 6 hours to & from now
const to = new Date(now + 6 * 60 * 60 * 1000);
const from = new Date(now - 6 * 60 * 60 * 1000);
this._settings.set_double('night-light-schedule-to', to.getHours());
this._settings.set_double('night-light-schedule-from', from.getHours());
}
}
// Run construct function
this._construct();
class Extension {
constructor() {
this._settings = new Gio.Settings({schema_id: COLOR_SCHEMA});
this._scheduler = new NightLightSchedule(this._settings);
this._preferences = ExtensionUtils.getSettings();
// We set up listeners for GSettings last because:
// > Note that @settings only emits this signal if you have read key at
// > least once while a signal handler was already connected for key.
this._preferences.connect('changed::minimum', () =>
this._updateOption('minimum', this._preferences.get_int('minimum')));
this._preferences.connect('changed::maximum', () =>
this._updateOption('maximum', this._preferences.get_int('maximum')));
this._preferences.connect('changed::swap-axis', () =>
this._updateOption('swapAxis', this._preferences.get_boolean('swap-axis')));
this._preferences.connect('changed::show-always', () =>
this._updateOption('showAlways', this._preferences.get_boolean('show-always')));
this._preferences.connect('changed::show-status-icon', () =>
this._updateOption('showStatusIcon', this._preferences.get_boolean('show-status-icon')));
this._preferences.connect('changed::brightness-sync', () =>
this._updateOption('brightnessSync', this._preferences.get_boolean('brightness-sync')));
// Set up hook to recreate indicator on settings change
this._preferences.connect('changed::show-in-submenu', () => {
if (!this._nightLight)
return;
this._nightLight.destroy();
this._create();
});
// Set up updater loop to set night light schedule if update always is enabled
if (settings.get_boolean("enable-always")) {
this._scheduleUpdater._enableLoop();
// Set up hook to update scheduler
this._preferences.connect('changed::enable-always', () => {
if (!this._nightLight)
return;
this._setupScheduler();
});
}
// Hide status icon if set to disable
if (!settings.get_boolean("show-status-icon") && this._icon) {
log(`[night-light-slider] Hiding status icon`);
this._icon.hide();
// TODO: Rewrite the extension
this._hackyShowCallback = this._icon.connect("show", () => {
this._icon.hide();
});
_setupScheduler() {
if (this._preferences.get_boolean('enable-always'))
this._scheduler.enableTimer();
else
this._scheduler.disableTimer();
}
// When scrolling the indicator, change night light intensity
this._icon.connect("scroll-event", (o, event) => {
this._indicator._scroll(event);
return true;
});
// Set up proxy to update slider view
this._colorProxy(
Gio.DBus.session,
BUS_NAME,
OBJECT_PATH,
(proxy, error) => {
if (error) {
log(error.message);
return;
}
_create() {
const indicator = panel.statusArea.aggregateMenu._nightLight;
this._nightLight = new Indicator(indicator, {
minimum: this._preferences.get_int('minimum'),
maximum: this._preferences.get_int('maximum'),
swapAxis: this._preferences.get_boolean('swap-axis'),
showAlways: this._preferences.get_boolean('show-always'),
showStatusIcon: this._preferences.get_boolean('show-status-icon'),
brightnessSync: this._preferences.get_boolean('brightness-sync'),
});
// Assign slider to AggregateMenu, just like other indicators
// This also makes it easier to debug the extension
panel.statusArea.aggregateMenu._nightLightSlider = this._nightLight;
if (this._preferences.get_boolean('show-in-submenu'))
panel.statusArea.aggregateMenu._nightLight._item.menu.addMenuItem(this._nightLight.menu);
else
panel.statusArea.aggregateMenu.menu.addMenuItem(this._nightLight.menu, 2);
}
const updateView = () => {
this._indicator._updateView();
if (!settings.get_boolean("show-always")) {
const active = proxy.NightLightActive;
const menuItems = Main.panel.statusArea.aggregateMenu.menu._getMenuItems();
menuItems[INDEX].actor.visible = active;
}
};
proxy.connect("g-properties-changed", updateView);
// Update view once on init
updateView();
}
);
// Event hooks
this._indicator._onSliderChanged((temperature, value) => {
// Set up night light to sync with brightness if changed
if (settings.get_boolean("brightness-sync")) {
Main.panel.statusArea.aggregateMenu._brightness._slider.value = value;
}
});
}
disable() {
// TODO: Figure out the proper way to disconnect signals
if (this._hackyShowCallback) {
this._icon.disconnect(this._hackyShowCallback);
this._icon.show();
_updateOption(key, value) {
if (!this._nightLight)
return;
this._nightLight.updateOption(key, value);
}
// Run deconstruct function
this._deconstruct();
enable() {
this._create();
this._setupScheduler();
}
// Disable updater loop
this._scheduleUpdater._disableLoop();
}
disable() {
this._nightLight.destroy();
this._nightLight = null;
panel.statusArea.aggregateMenu._nightLightSlider = null;
this._scheduler.disableTimer();
}
}
function init() {
// eslint-disable-line
return new NightLightExtension();
return new Extension();
}

3
night-light-slider.timur@linux.com/metadata.json

@ -2,8 +2,9 @@
"name": "Night Light Slider",
"description": "Change night light temperature",
"settings-schema": "org.gnome.shell.extensions.nightlightslider",
"data-gresource": "org.gnome.shell.extensions.nightlightslider.data.gresource",
"uuid": "night-light-slider.timur@linux.com",
"version": 15,
"version": 16,
"url": "https://github.com/kiyui/gnome-shell-night-light-slider-extension",
"shell-version": ["3.36"]
}

6
night-light-slider.timur@linux.com/org.gnome.shell.extensions.nightlightslider.data.gresource.xml

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/shell/extensions/nightlightslider">
<file>prefs.ui</file>
</gresource>
</gresources>

329
night-light-slider.timur@linux.com/prefs.js

@ -1,241 +1,108 @@
/* global imports log */
/* exported buildPrefsWidget init */
imports.gi.versions.Gtk = '3.0';
imports.gi.versions.Handy = '0.0';
const {GObject, Gio, Gtk, Handy} = imports.gi;
const Gtk = imports.gi.Gtk;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
// Extension specific
const Me = imports.misc.extensionUtils.getCurrentExtension();
const Convenience = Me.imports.convenience;
// Register resources
const resource = Me.metadata['data-gresource'];
const resourceFile = Me.dir.get_child(resource);
Gio.resources_register(Gio.Resource.load(resourceFile.get_path()));
function buildPrefsWidget() {
// eslint-disable-line no-unused-vars
const schema = Convenience.getSettings();
// Text and descriptions
const showAlwaysName = schema.settings_schema
.get_key("show-always")
.get_summary();
const showAlwaysDescription = schema.settings_schema
.get_key("show-always")
.get_description();
const showIconName = schema.settings_schema
.get_key("show-status-icon")
.get_summary();
const showIconDescription = schema.settings_schema
.get_key("show-status-icon")
.get_description();
const enableAlwaysName = schema.settings_schema
.get_key("enable-always")
.get_summary();
const enableAlwaysDescription = schema.settings_schema
.get_key("enable-always")
.get_description();
const minimumName = schema.settings_schema.get_key("minimum").get_summary();
const minimumDescription = schema.settings_schema
.get_key("minimum")
.get_description();
const maximumName = schema.settings_schema.get_key("maximum").get_summary();
const maximumDescription = schema.settings_schema
.get_key("maximum")
.get_description();
const brightnessSyncName = schema.settings_schema
.get_key("brightness-sync")
.get_summary();
const brightnessSyncDescription = schema.settings_schema
.get_key("brightness-sync")
.get_description();
const showInSubmenuName = schema.settings_schema
.get_key("show-in-submenu")
.get_summary();
const showInSubmenuDescription = schema.settings_schema
.get_key("show-in-submenu")
.get_description();
// Create children objects
const widgets = [
{
type: "Label",
params: { label: `${showAlwaysName}: ` },
tooltip: showAlwaysDescription,
align: Gtk.Align.END,
attach: [0, 1, 1, 1],
},
{
type: "Switch",
params: { active: schema.get_boolean("show-always") },
tooltip: showAlwaysDescription,
align: Gtk.Align.START,
attach: [1, 1, 1, 1],
connect: {
"state-set": (self) => {
schema.set_boolean("show-always", self.active);
},
},
},
{
type: "Label",
params: { label: `${showIconName}: ` },
tooltip: showAlwaysDescription,
align: Gtk.Align.END,
attach: [0, 2, 1, 1],
},
{
type: "Switch",
params: { active: schema.get_boolean("show-status-icon") },
tooltip: showIconDescription,
align: Gtk.Align.START,
attach: [1, 2, 1, 1],
connect: {
"state-set": (self) => {
schema.set_boolean("show-status-icon", self.active);
},
},
},
{
type: "Label",
params: { label: `${enableAlwaysName}: ` },
tooltip: enableAlwaysDescription,
align: Gtk.Align.END,
attach: [0, 3, 1, 1],
},
{
type: "Switch",
params: { active: schema.get_boolean("enable-always") },
tooltip: enableAlwaysDescription,
align: Gtk.Align.START,
attach: [1, 3, 1, 1],
connect: {
"state-set": (self) => {
schema.set_boolean("enable-always", self.active);
},
},
},
{
type: "Label",
params: { label: `${brightnessSyncName}: ` },
tooltip: brightnessSyncDescription,
align: Gtk.Align.END,
attach: [0, 4, 1, 1],
},
{
type: "Switch",
params: { active: schema.get_boolean("brightness-sync") },
tooltip: brightnessSyncDescription,
align: Gtk.Align.START,
attach: [1, 4, 1, 1],
connect: {
"state-set": (self) => {
schema.set_boolean("brightness-sync", self.active);
},
},
},
{
type: "Label",
params: { label: `${showInSubmenuName}: ` },
tooltip: showInSubmenuDescription,
align: Gtk.Align.END,
attach: [0, 5, 1, 1],
},
{
type: "Switch",
params: { active: schema.get_boolean("show-in-submenu") },
tooltip: showInSubmenuDescription,
align: Gtk.Align.START,
attach: [1, 5, 1, 1],
connect: {
"state-set": (self) => {
schema.set_boolean("show-in-submenu", self.active);
},
},
},
{
type: "Label",
params: { label: `${minimumName}: ` },
tooltip: minimumDescription,
align: Gtk.Align.END,
attach: [0, 6, 1, 1],
},
{
type: "Entry",
params: { text: schema.get_int("minimum").toString() },
tooltip: minimumDescription,
align: Gtk.Align.START,
attach: [1, 6, 1, 1],
connect: {
changed: (self) => {
schema.set_int("minimum", parseInt(self.text));
},
},
},
{
type: "Label",
params: { label: `${maximumName}: ` },
tooltip: maximumDescription,
align: Gtk.Align.END,
attach: [0, 7, 1, 1],
},
{
type: "Entry",
params: { text: schema.get_int("maximum").toString() },
tooltip: maximumDescription,
align: Gtk.Align.START,
attach: [1, 7, 1, 1],
connect: {
changed: (self) => {
schema.set_int("maximum", parseInt(self.text));
},
},
},
{
type: "Label",
params: {
label:
"Changes require restarting shell (logging in and out) to take place.",
},
tooltip: showAlwaysDescription,
align: Gtk.Align.CENTER,
attach: [0, 8, 2, 1],
},
];
// Perform side-effects
const vbox = new Gtk.Grid({
column_spacing: 20,
row_spacing: 20,
margin: 10,
});
widgets.map(function createWidget({
type,
params,
tooltip,
align,
attach,
connect,
}) {
const widget = new Gtk[type](params);
// Set description
widget.set_tooltip_text(tooltip);
// Set alignment
widget.set_halign(align);
widget.set_hexpand(true);
// Add event handler if exists
if (connect) {
Object.keys(connect).map(function performConnect(signal) {
widget.connect(signal, () => connect[signal](widget));
});
// GSettings schema
const COLOR_SCHEMA = 'org.gnome.settings-daemon.plugins.color';
var NightLightExtensionPrefs = GObject.registerClass({
GTypeName: 'NightLightExtensionPrefs',
Template: 'resource:///org/gnome/shell/extensions/nightlightslider/prefs.ui',
InternalChildren: [
/* Night Light status infobar */
'infobar_status', 'btn_enable_night_light',
/* Slider position option */
'show_in_submenu_combo',
/* Boolean switch options */
'show_always_toggle_switch',
'show_status_icon_toggle_switch',
'swap_axis_toggle_switch',
'brightness_sync_toggle_switch',
'enable_always_toggle_switch',
/* Temperature range */
'spinbutton_maximum', 'spinbutton_minimum',
],
}, class NightLightExtensionPrefs extends Gtk.Box {
_init(preferences) {
super._init();
this._preferences = preferences;
this._settings = new Gio.Settings({schema_id: COLOR_SCHEMA});
// Initialize application state
this._syncInfobar();
this._syncPreferences();
// Connect settings change signals
this._settings.connect('changed::night-light-enabled', this._syncInfobar.bind(this));
this._preferences.connect('changed', this._syncPreferences.bind(this));
// Set up alert CTA to enable night light
this._btn_enable_night_light.connect('clicked',
() => this._settings.set_boolean('night-light-enabled', true));
// Set up combo changed listener
this._show_in_submenu_combo.connect('changed',
self => this._preferences.set_boolean('show-in-submenu',
// The possible options are show_in_submenu_{true,false}
self.active_id === 'show_in_submenu_true'));
// Set up switch state-set listeners
// We negate the returns of `set_boolean` such that the state updates
this._show_always_toggle_switch.connect('state-set',
(_, state) => !this._preferences.set_boolean('show-always', state));
this._show_status_icon_toggle_switch.connect('state-set',
(_, state) => !this._preferences.set_boolean('show-status-icon', state));
this._swap_axis_toggle_switch.connect('state-set',
(_, state) => !this._preferences.set_boolean('swap-axis', state));
this._brightness_sync_toggle_switch.connect('state-set',
(_, state) => !this._preferences.set_boolean('brightness-sync', state));
this._enable_always_toggle_switch.connect('state-set',
(_, state) => !this._preferences.set_boolean('enable-always', state));
// Set up spinner value-changed listeners
this._spinbutton_maximum.connect('value-changed',
self => this._preferences.set_int('maximum', self.value));
this._spinbutton_minimum.connect('value-changed',
self => this._preferences.set_int('minimum', self.value));
}
_syncInfobar() {
const visible = !this._settings.get_boolean('night-light-enabled');
this._infobar_status.set_revealed(visible);
}
vbox.attach(widget, ...attach);
});
_syncPreferences() {
// Focus on slider position option based on index
this._show_in_submenu_combo.set_active(this._preferences.get_boolean('show-in-submenu') ? 1 : 0);
// Update switch active states
this._show_always_toggle_switch.active = this._preferences.get_boolean('show-always');
this._show_status_icon_toggle_switch.active = this._preferences.get_boolean('show-status-icon');
this._swap_axis_toggle_switch.active = this._preferences.get_boolean('swap-axis');
this._brightness_sync_toggle_switch.active = this._preferences.get_boolean('brightness-sync');
this._enable_always_toggle_switch.active = this._preferences.get_boolean('enable-always');
vbox.show_all();
return vbox;
// Update temperature range values
this._spinbutton_maximum.value = this._preferences.get_int('maximum');
this._spinbutton_minimum.value = this._preferences.get_int('minimum');
}
});
function buildPrefsWidget() {
const preferences = ExtensionUtils.getSettings();
return new NightLightExtensionPrefs(preferences);
}
function init() {
// eslint-disable-line
log("Setting up night light slider preferences");
Gtk.init(null);
Handy.init(null);
}

226
night-light-slider.timur@linux.com/prefs.ui

@ -0,0 +1,226 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.36.0 -->
<interface>
<requires lib="gtk+" version="3.22"/>
<requires lib="libhandy" version="0.0"/>
<template class="NightLightExtensionPrefs" parent="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkInfoBar" id="infobar_status">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="message_type">warning</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="btn_enable_night_light">
<property name="label" translatable="yes">Enable</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child internal-child="content_area">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="spacing">16</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Night Light is disabled in system preferences</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkListBox">
<property name="name">listbox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="selection_mode">none</property>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Manage where in the aggregate menu the slider should show</property>
<property name="title" translatable="yes">Slider position</property>
<child type="action">
<object class="GtkComboBoxText" id="show_in_submenu_combo">
<property name="visible">True</property>
<property name="valign">center</property>
<items>
<item id="show_in_submenu_false" translatable="yes">Top-level menu</item>
<item id="show_in_submenu_true" translatable="yes">Night Light submenu</item>
</items>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Show the slider even when night light is disabled or off</property>
<property name="title" translatable="yes">Always show slider</property>
<property name="activatable-widget">show_always_toggle_switch</property>
<child type="action">
<object class="GtkSwitch" id="show_always_toggle_switch">
<property name="visible">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Show the night light indicator in the status area when night light is enabled</property>
<property name="title" translatable="yes">Show indicator</property>
<property name="activatable-widget">show_status_icon_toggle_switch</property>
<child type="action">
<object class="GtkSwitch" id="show_status_icon_toggle_switch">
<property name="visible">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Invert the slider axis such that lower is cooler and higher is warmer</property>
<property name="title" translatable="yes">Swap slider axis</property>
<property name="activatable-widget">swap_axis_toggle_switch</property>
<child type="action">
<object class="GtkSwitch" id="swap_axis_toggle_switch">
<property name="visible">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Adjust both brightness and night light warmth</property>
<property name="title" translatable="yes">Sync brightness percentage</property>
<property name="activatable-widget">brightness_sync_toggle_switch</property>
<child type="action">
<object class="GtkSwitch" id="brightness_sync_toggle_switch">
<property name="visible">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Constantly update the night light schedule such that it is enabled throughout the day</property>
<property name="title" translatable="yes">Enable permanent night light</property>
<property name="activatable-widget">enable_always_toggle_switch</property>
<child type="action">
<object class="GtkSwitch" id="enable_always_toggle_switch">
<property name="visible">True</property>
<property name="valign">center</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Maximum slider value, higher is cooler</property>
<property name="title" translatable="yes">Highest temperature</property>
<child type="action">
<object class="GtkSpinButton" id="spinbutton_maximum">
<property name="visible">True</property>
<property name="valign">center</property>
<property name="can_focus">True</property>
<property name="placeholder_text">5000</property>
<property name="input_purpose">number</property>
<property name="adjustment">temperature_adjustment_maximum</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="subtitle" translatable="yes">Minimum slider value, lower is warmer</property>
<property name="title" translatable="yes">Lowest temperature</property>
<child type="action">
<object class="GtkSpinButton" id="spinbutton_minimum">
<property name="visible">True</property>
<property name="valign">center</property>
<property name="can_focus">True</property>
<property name="placeholder_text">1500</property>
<property name="input_purpose">number</property>
<property name="adjustment">temperature_adjustment_minimum</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</template>
<object class="GtkAdjustment" id="temperature_adjustment_maximum">
<property name="lower" bind-source="temperature_adjustment_minimum" bind-property="value" bind-flags="default|sync-create" />
<property name="upper">9000</property>
<property name="step_increment">100</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="temperature_adjustment_minimum">
<property name="lower">1000</property>
<property name="upper" bind-source="temperature_adjustment_maximum" bind-property="value" bind-flags="default|sync-create" />
<property name="step_increment">100</property>
<property name="page_increment">10</property>
</object>
</interface>

32
night-light-slider.timur@linux.com/schemas/org.gnome.shell.extensions.nightlightslider.gschema.xml

@ -1,39 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema id="org.gnome.shell.extensions.nightlightslider" path="/org/gnome/shell/extensions/nightlightslider/">
<key name="show-always" type="b">
<default>false</default>
<summary>Show always</summary>
<description>Show slider even when night light is off</description>
<summary>Always show slider</summary>
<description>Show the slider even when night light is disabled or off</description>
</key>
<key name="show-status-icon" type="b">
<default>true</default>
<summary>Show status icon</summary>
<description>Show status icon in status area</description>
<summary>Show indicator</summary>
<description>Show the night light indicator in the status area when night light is enabled</description>
</key>
<key name="enable-always" type="b">
<default>false</default>
<summary>Enable always</summary>
<description>Enable night light throughout the day</description>
<summary>Enable permanent night light</summary>
<description>Constantly update the night light schedule such that it is enabled throughout the day</description>
</key>
<key name="minimum" type="i">
<default>1500</default>
<summary>Minimum value</summary>
<description>Minimum night light slider value</description>
<summary>Lowest temperature</summary>
<description>Minimum slider value, lower is warmer</description>
</key>
<key name="maximum" type="i">
<default>5000</default>
<summary>Maximum value</summary>
<description>Maximum night light slider value</description>
<summary>Highest temperature</summary>
<description>Maximum slider value, higher is cooler</description>
</key>
<key name="brightness-sync" type="b">
<default>false</default>
<summary>Brightness sync</summary>
<description>Sync brightness slider with night light slider</description>
<summary>Sync brightness percentage</summary>
<description>Adjust both brightness and night light warmth</description>
</key>
<key name="show-in-submenu" type="b">
<default>false</default>
<summary>Show in submenu</summary>
<description>Display slider in night light submenu</description>
<description>Display the slider in the night light submenu instead of at the panel menu</description>
</key>
<key name="swap-axis" type="b">
<default>false</default>
<summary>Swap slider axis</summary>
<description>Invert the slider axis such that lower is cooler and higher is warmer</description>
</key>
</schema>
</schemalist>

11
package.json

@ -2,10 +2,11 @@
"name": "gnome-shell-night-light-slider-extension",
"description": "Change night light temperature",
"scripts": {
"build:resources": "glib-compile-resources --sourcedir night-light-slider.timur@linux.com/ night-light-slider.timur@linux.com/org.gnome.shell.extensions.nightlightslider.data.gresource.xml",
"build:schema": "glib-compile-schemas night-light-slider.timur@linux.com/schemas/",
"build:copy": "cp -rvf night-light-slider.timur@linux.com/ ~/.local/share/gnome-shell/extensions/",
"build:link": "ln -s $(pwd)/night-light-slider.timur@linux.com/ ~/.local/share/gnome-shell/extensions/",
"build:zip": "cd night-light-slider.timur@linux.com/ && zip -r ../night-light-slider.timur@linux.com.zip ./*",
"build": "npm run build:schema && npm run build:zip"
"build": "npm run build:resources && npm run build:schema && npm run build:zip"
},
"repository": {
"type": "git",
@ -25,9 +26,9 @@
},
"homepage": "https://github.com/kiyui/gnome-shell-night-light-slider-extension#readme",
"devDependencies": {
"eslint": "^7.8.1",
"husky": ">=4",
"lint-staged": ">=10",
"prettier": "^2.0.5"
"lint-staged": ">=10"
},
"dependencies": {},
"husky": {
@ -36,6 +37,6 @@
}
},
"lint-staged": {
"*.{js,css,md}": "prettier --write"
"*.js": "eslint --fix"
}
}

509
yarn.lock

@ -23,6 +23,22 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@eslint/eslintrc@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085"
integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==
dependencies:
ajv "^6.12.4"
debug "^4.1.1"
espree "^7.3.0"
globals "^12.1.0"
ignore "^4.0.6"
import-fresh "^3.2.1"
js-yaml "^3.13.1"
lodash "^4.17.19"
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -33,6 +49,16 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
acorn-jsx@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe"
integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==
acorn@^7.4.0:
version "7.4.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
aggregate-error@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
@ -41,6 +67,16 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4:
version "6.12.4"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-colors@^3.2.1:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
@ -53,12 +89,17 @@ ansi-escapes@^4.3.0:
dependencies:
type-fest "^0.11.0"
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb8437