Merge branch 'v1.2'
* v1.2: (25 commits) Bump version to 1.2.0-beta.3 Update reset button label to always show ellipsis Release v1.2.0-beta.2 Create application menu on macOS including about page, quit and basic 'Edit' menu; Fixes #60 Add option to reset app from lock screen Send platform as header with each request Release v1.2.0-beta.1 Remove announcements.json (was used for testing) Update electron-builder and electron-auto-updater dependencies Upgrade to latest electron-builder; Add option to build for linux Fix paths in test runner page Fix modulo bias in padlock.rand.randomString(); add test to ensure unique strings, even character distribution When resetting app, also reset all settings Don't save version in settings; reorganise version-related code Add option to build electron app for windows Add support for electron auto update Update cordova build hook to use new build function Get app version though various platform apis instead of hardcoding it; Display in settings Update manifest file with current version, description and author fields before writing to build dir Clean up dependencies ...
|
@ -1,8 +1,13 @@
|
|||
{
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "2015"
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true,
|
||||
"browser": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
bower_components
|
||||
node_modules
|
||||
deploy
|
||||
dist
|
||||
|
||||
# files/directories generated by cordova
|
||||
cordova/platforms
|
||||
|
@ -8,10 +9,10 @@ cordova/plugins
|
|||
cordova/www
|
||||
|
||||
# stylus-generated css files
|
||||
src/components/**/*.css
|
||||
src/styles/mixins.css
|
||||
src/styles/config.css
|
||||
app/src/components/**/*.css
|
||||
app/src/styles/mixins.css
|
||||
app/src/styles/config.css
|
||||
|
||||
# generated html modules
|
||||
src/**/*-styles.html
|
||||
app/src/**/*-styles.html
|
||||
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
{
|
||||
"name": "Padlock",
|
||||
"version": "1.0.0",
|
||||
"name": "padlock",
|
||||
"version": "1.2.0",
|
||||
"homepage": "http://padlock.io",
|
||||
"authors": [
|
||||
"Martin Kleinschrodt <martin@maklesoft.com>"
|
||||
],
|
||||
"main": "index.html",
|
||||
"license": "GPL",
|
||||
"license": "GPL-3.0",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
"bower_components"
|
||||
],
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -20,6 +16,7 @@
|
|||
"polymer": "polymer/polymer#~1.2.1",
|
||||
"zxcvbn": "~3.1.0",
|
||||
"autosize": "~3.0.13",
|
||||
"papaparse": "~4.1.2"
|
||||
"papaparse": "~4.1.2",
|
||||
"sjcl": "^1.0.6"
|
||||
}
|
||||
}
|
|
@ -3,19 +3,39 @@
|
|||
<head>
|
||||
<title>Padlock</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
|
||||
<link rel="shortcut icon" type="image/png" href="assets/icon16.png"/>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="assets/fonts/fonts.css" rel="stylesheet" type="text/css">
|
||||
<script>
|
||||
/* global require, electron */
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
if (typeof require !== "undefined") {
|
||||
delete window.module;
|
||||
window.electron = require("electron");
|
||||
electron.ipcRenderer.on("auto-update", function(_, type) {
|
||||
const evt = new CustomEvent(type, { detail: Array.prototype.slice.call(arguments, 3) });
|
||||
window.dispatchEvent(evt);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Load blue background right from the start to avoid white 'flash' -->
|
||||
<style>
|
||||
html { background: #59c6ff; }
|
||||
</style>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link href="src/styles/fonts.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<!-- Overrides for platform-specific styling -->
|
||||
<link href="overrides.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
|
||||
<!-- Load the cordova api if available -->
|
||||
<script src="cordova.js"></script>
|
||||
|
||||
<link rel="import" href="src/padlock.html">
|
||||
<link rel="import" href="src/components/app/app.html">
|
||||
|
||||
|
@ -26,9 +46,7 @@
|
|||
|
||||
var source = new padlock.LocalSource(),
|
||||
store = new padlock.Store(source),
|
||||
settings = new padlock.Settings(store, {
|
||||
// "sync_require_subscription": true
|
||||
}),
|
||||
settings = new padlock.Settings(store),
|
||||
collection = new padlock.Collection("default", store),
|
||||
announcements = new padlock.Announcements("https://padlock.io/announcements.json", store);
|
||||
|
||||
|
@ -37,8 +55,6 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<!-- Load the cordova api if available -->
|
||||
<script src="cordova.js"></script>
|
||||
<script>
|
||||
/* global cordova */
|
||||
document.addEventListener("deviceready", function() {
|
|
@ -72,6 +72,7 @@ padlock.CloudSource = (function(Source) {
|
|||
}
|
||||
|
||||
req.setRequestHeader("X-Client-Version", padlock.version);
|
||||
req.setRequestHeader("X-Client-Platform", padlock.platform.getPlatformName());
|
||||
|
||||
return req;
|
||||
} catch(e) {
|
|
@ -41,7 +41,8 @@ padlock.Settings = (function(util, LocalSource) {
|
|||
"obfuscate_fields": false,
|
||||
"showed_backup_reminder": 0,
|
||||
"sync_require_subscription": false,
|
||||
"sync_id": ""
|
||||
"sync_id": "",
|
||||
"version": ""
|
||||
},
|
||||
parse: function(data) {
|
||||
try {
|
||||
|
@ -82,6 +83,11 @@ padlock.Settings = (function(util, LocalSource) {
|
|||
//* Saves the existing settings
|
||||
save: function(opts) {
|
||||
this.store.save(storeKey, this.toString(), opts);
|
||||
},
|
||||
reset: function() {
|
||||
for (var prop in this.properties) {
|
||||
this[prop] = this.properties[prop];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<padlock-header id="header" view="{{ _currentView }}" filter-string="{{ _filterString }}" on-filter-enter="_selectMarkedRecord"></padlock-header>
|
||||
<padlock-start-view id="startView" class="view" on-newpwd="_newPwd" on-restore="_restored" collection="{{ collection }}" settings="{{ settings }}"></padlock-start-view>
|
||||
<padlock-lock-view id="lockView" class="view" on-pwdenter="_pwdEnter"></padlock-lock-view>
|
||||
<padlock-lock-view id="lockView" class="view" on-pwdenter="_pwdEnter" on-reset-app="_resetData"></padlock-lock-view>
|
||||
<!-- LIST VIEW -->
|
||||
<padlock-list-view id="listView" class="view" records="{{ _records }}" filter-string="{{ _filterString }}"
|
||||
selected="{{ _selected }}" on-select="_recordSelected" on-add="_addRecord" on-back="_listViewBack"
|
||||
|
@ -39,7 +39,7 @@
|
|||
on-delete="_deleteRecord" on-categories="_openCategories" settings="{{ settings }}"></padlock-record-view>
|
||||
<!-- Settings VIEW -->
|
||||
<padlock-settings-view id="settingsView" class="view" on-back="_settingsBack" on-open-cloud-view="_openCloudView"
|
||||
on-import="_openImportView" on-export="_openExportView" on-reset="_openStartView" on-change-password="_changePwd"
|
||||
on-import="_openImportView" on-export="_openExportView" on-reset="_resetData" on-change-password="_changePwd"
|
||||
collection="{{ collection }}" settings="{{ settings }}"></padlock-settings-view>
|
||||
<!-- IMPORT VIEW -->
|
||||
<padlock-import-view id="importView" class="view" on-imported="_imported" collection="{{ collection }}"
|
|
@ -102,6 +102,10 @@ padlock.App = (function(Polymer, platform) {
|
|||
|
||||
// Init view when app resumes
|
||||
document.addEventListener("resume", this._resume.bind(this, true), false);
|
||||
|
||||
window.addEventListener("update-downloaded", function(e) {
|
||||
this._updateDownloaded.apply(this, e.detail);
|
||||
}.bind(this));
|
||||
},
|
||||
_cancelAutoLock: function() {
|
||||
this._pausedAt = null;
|
||||
|
@ -165,6 +169,7 @@ padlock.App = (function(Polymer, platform) {
|
|||
_newPwd: function(event, detail) {
|
||||
// Update master password
|
||||
this.collection.setPassword(detail.password);
|
||||
this.settings.save();
|
||||
// Navigate to list view
|
||||
this._popinOpen(this.$.listView);
|
||||
},
|
||||
|
@ -187,6 +192,7 @@ padlock.App = (function(Polymer, platform) {
|
|||
success: function() {
|
||||
// Fetch settings from persistent storage
|
||||
this.settings.fetch({success: function() {
|
||||
// Write version to settings
|
||||
this._notifySettings();
|
||||
this._unlockSuccess();
|
||||
}.bind(this), fail: function() {
|
||||
|
@ -924,6 +930,19 @@ padlock.App = (function(Polymer, platform) {
|
|||
|
||||
this._openForm(els, a.text, dismissed, dismissed, true);
|
||||
}
|
||||
},
|
||||
_updateDownloaded: function() {
|
||||
this._alert("New update available! Restart the app to install!");
|
||||
},
|
||||
_resetData: function() {
|
||||
this.settings.reset();
|
||||
this._notifySettings();
|
||||
this.collection.clear();
|
||||
this.collection.destroy();
|
||||
this._openStartView();
|
||||
this.async(function() {
|
||||
this._alert("Data deleted and app reset succesfully!");
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
<padlock-lock></padlock-lock>
|
||||
|
||||
<button class="reset-button ghost" on-tap="_openOptions" type="button">...</button>
|
||||
|
||||
</template>
|
||||
|
||||
<script src="lock-view.js"></script>
|
|
@ -0,0 +1,66 @@
|
|||
/* global Polymer, padlock */
|
||||
|
||||
(function(Polymer, ViewBehavior, platform) {
|
||||
"use strict";
|
||||
|
||||
Polymer({
|
||||
is: "padlock-lock-view",
|
||||
behaviors: [ViewBehavior],
|
||||
hide: function() {
|
||||
this.$$("padlock-lock").unlocked = true;
|
||||
var args = arguments;
|
||||
this.async(function() {
|
||||
ViewBehavior.hide.apply(this, args);
|
||||
}, 500);
|
||||
},
|
||||
show: function() {
|
||||
this._clear();
|
||||
this.$$("padlock-lock").unlocked = false;
|
||||
ViewBehavior.show.apply(this, arguments);
|
||||
if (!platform.isTouch()) {
|
||||
this.async(function() {
|
||||
this.$.pwdInput.focus();
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
enter: function() {
|
||||
this.$.pwdInput.blur();
|
||||
this.fire("pwdenter", {password: this.$.pwdInput.value});
|
||||
},
|
||||
getAnimationElement: function() {
|
||||
return this.$$("padlock-lock");
|
||||
},
|
||||
_clear: function() {
|
||||
this.$.pwdInput.value = "";
|
||||
},
|
||||
_openOptions: function() {
|
||||
this.fire("open-form", {
|
||||
components: [
|
||||
{element: "button", label: "Reset App", submit: true}
|
||||
],
|
||||
submit: this._confirmResetApp.bind(this, false)
|
||||
});
|
||||
},
|
||||
_confirmResetApp: function(failed) {
|
||||
var title = failed ? "Failed to Confirm. Make sure to type 'RESET' in the text field below." :
|
||||
"Are you sure you want to delete all your data and reset the app? " +
|
||||
"This action can not be undone! Type 'RESET' to Confirm.";
|
||||
|
||||
this.fire("open-form", {
|
||||
title: title,
|
||||
components: [
|
||||
{element: "input", name: "confirm", placeholder: "Type 'RESET' to Confirm"},
|
||||
{element: "button", label: "Reset App", submit: true}
|
||||
],
|
||||
submit: function(data) {
|
||||
if (data.confirm.toLowerCase() == "reset") {
|
||||
this.fire("reset-app");
|
||||
} else {
|
||||
this._confirmResetApp(true);
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(Polymer, padlock.ViewBehavior, padlock.platform);
|
|
@ -51,6 +51,16 @@
|
|||
text-shadow: rgba(0,0,0,0.2) 1px 1px;
|
||||
}
|
||||
|
||||
button.reset-button {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
animation reveal 0.5s ease 0.6s both;
|
||||
font-size: 115%;
|
||||
}
|
||||
|
||||
padlock-lock {
|
||||
align-self: center;
|
||||
animation: reveal 0.5s ease 0.8s both;
|
|
@ -47,7 +47,10 @@
|
|||
<section>
|
||||
<button on-tap="_openWebsite">Website</button>
|
||||
<button on-tap="_sendMail">Support</button>
|
||||
<div class="info" on-tap="_openHomepage">Developed and maintained by <strong>MaKleSoft</strong></div>
|
||||
<div class="info" on-tap="_openHomepage">
|
||||
<strong>Padlock {{ _version() }}</strong><br>
|
||||
Developed and maintained by <strong>MaKleSoft</strong>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- PROGRESS INDICATOR -->
|
|
@ -71,11 +71,6 @@
|
|||
"This action can not be undone! Please enter your master password to confirm.",
|
||||
submit: function(data) {
|
||||
if (data.password == this.collection.defaultPassword) {
|
||||
this.set("settings.sync_connected", false);
|
||||
this.set("settings.sync_key", "");
|
||||
this.set("settings.sync_email", "");
|
||||
this.collection.clear();
|
||||
this.collection.destroy();
|
||||
this.fire("reset");
|
||||
} else {
|
||||
this.fire("notify", {message: "Wrong password!", type: "error", duration: 2000});
|
||||
|
@ -85,6 +80,9 @@
|
|||
},
|
||||
_openCloudView: function() {
|
||||
this.fire("open-cloud-view");
|
||||
},
|
||||
_version: function() {
|
||||
return padlock.version;
|
||||
}
|
||||
});
|
||||
|
|
@ -34,8 +34,4 @@ button, .button {
|
|||
font-size: 15px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
strong {
|
||||
color: $color-primary;
|
||||
}
|
||||
}
|
|
@ -278,7 +278,7 @@
|
|||
// We're in a web worker! Let's create an interface for calling some of the modules methods
|
||||
|
||||
// Load the sjcl dependency.
|
||||
importScripts("../lib/sjcl.js");
|
||||
importScripts("../bower_components/sjcl/sjcl.js");
|
||||
// Create the module (Inject the dependy manually)
|
||||
var crypto = modFunc(sjcl);
|
||||
|
|
@ -1,16 +1,17 @@
|
|||
<script>
|
||||
/* global window, padlock */
|
||||
window.padlock = {
|
||||
version: "1.1.0"
|
||||
version: ""
|
||||
};
|
||||
</script>
|
||||
<script src="../lib/sjcl.js"></script>
|
||||
<script src="../bower_components/sjcl/sjcl.js"></script>
|
||||
<script src="../bower_components/zxcvbn/lib/zxcvbn.js"></script>
|
||||
<script src="../bower_components/papaparse/papaparse.js"></script>
|
||||
<script src="util.js"></script>
|
||||
<script src="rand.js"></script>
|
||||
<script src="crypto.js"></script>
|
||||
<script src="platform.js"></script>
|
||||
<script src="version.js"></script>
|
||||
<script src="Source.js"></script>
|
||||
<script src="LocalStorageSource.js"></script>
|
||||
<script src="ChromeStorageSource.js"></script>
|
|
@ -1,5 +1,5 @@
|
|||
/* jshint browser: true */
|
||||
/* global padlock, chrome, cordova */
|
||||
/* global padlock, chrome, cordova, electron */
|
||||
|
||||
/**
|
||||
* Module containing various platform-specific utilities
|
||||
|
@ -180,6 +180,32 @@ padlock.platform = (function() {
|
|||
}
|
||||
};
|
||||
|
||||
var getAppVersion = function(cb) {
|
||||
if (typeof electron !== "undefined") {
|
||||
cb(electron.remote.app.getVersion());
|
||||
} else if (typeof cordova !== "undefined" && cordova.getAppVersion) {
|
||||
cordova.getAppVersion.getVersionNumber(cb);
|
||||
} else if (isChromeApp()) {
|
||||
cb(chrome.runtime.getManifest().version);
|
||||
} else {
|
||||
cb("");
|
||||
}
|
||||
};
|
||||
|
||||
var getPlatformName = function() {
|
||||
if (typeof require !== "undefined" && require("os")) {
|
||||
return require("os").platform();
|
||||
} else if (isIOS()) {
|
||||
return "ios";
|
||||
} else if (isAndroid()) {
|
||||
return "android";
|
||||
} else if (isChromeApp()) {
|
||||
return "chrome";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
getVendorPrefix: getVendorPrefix,
|
||||
getTransitionEndEventName: getTransitionEndEventName,
|
||||
|
@ -193,6 +219,8 @@ padlock.platform = (function() {
|
|||
setClipboard: setClipboard,
|
||||
getClipboard: getClipboard,
|
||||
keyboardDisableScroll: keyboardDisableScroll,
|
||||
getAppStoreLink: getAppStoreLink
|
||||
getAppStoreLink: getAppStoreLink,
|
||||
getAppVersion: getAppVersion,
|
||||
getPlatformName: getPlatformName
|
||||
};
|
||||
})();
|
|
@ -25,17 +25,22 @@ padlock.rand = (function() {
|
|||
function randomString(length, charSet) {
|
||||
length = length || 32;
|
||||
charSet = charSet || charSets.full;
|
||||
var rnd = new Uint8Array(length);
|
||||
var rnd = new Uint8Array(1);
|
||||
var str = "";
|
||||
window.crypto.getRandomValues(rnd);
|
||||
for (var i=0; i<length; i++) {
|
||||
str += charSet[Math.floor(rnd[i] * charSet.length / 256)];
|
||||
while (str.length < length) {
|
||||
window.crypto.getRandomValues(rnd);
|
||||
// Prevent modulo bias by rejecting values larger than the highest muliple of `charSet.length`
|
||||
if (rnd[0] > 255 - 256 % charSet.length) {
|
||||
continue;
|
||||
}
|
||||
str += charSet[rnd[0] % charSet.length];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
return {
|
||||
randomString: randomString,
|
||||
chars: chars
|
||||
chars: chars,
|
||||
charSets: charSets
|
||||
};
|
||||
})();
|
|
@ -21,4 +21,4 @@
|
|||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Clear Sans Bold'), local('ClearSans-Bold'), url(../../assets/fonts/ClearSans-Bold.woff) format('woff');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* global padlock */
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
function fetchVersion() {
|
||||
padlock.platform.getAppVersion(function(version) {
|
||||
padlock.version = version;
|
||||
});
|
||||
}
|
||||
|
||||
fetchVersion();
|
||||
// Try to get app version again after deviceready event since the getAppVersion
|
||||
// plugin is not ready before
|
||||
document.addEventListener("deviceready", fetchVersion);
|
||||
})();
|
|
@ -1,19 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="192px" height="64px" viewBox="0 0 192 64" enable-background="new 0 0 192 64" xml:space="preserve">
|
||||
<g>
|
||||
<polygon fill="#59C6FF" points="40.75,11.578 42.164,10.164 32,0 21.836,10.164 23.25,11.578 31,3.828 31,35.486 33,35.486
|
||||
33,3.828 "/>
|
||||
<polygon fill="#59C6FF" points="37,19 37,21 50,21 50,55 14,55 14,21 27,21 27,19 12,19 12,57 52,57 52,19 "/>
|
||||
</g>
|
||||
<polygon fill="#333333" points="102.165,42.75 103.579,44.164 113.743,34 103.579,23.836 102.165,25.25 109.915,33 78.257,33
|
||||
78.257,35 109.915,35 "/>
|
||||
<g>
|
||||
<path fill="#59C6FF" d="M160,9c-13.809,0-25,11.191-25,25s11.191,25,25,25s25-11.191,25-25S173.809,9,160,9z M160,57
|
||||
c-12.682,0-23-10.318-23-23s10.318-23,23-23s23,10.318,23,23S172.682,57,160,57z"/>
|
||||
<polygon fill="#59C6FF" points="161,19 159,19 159,33 145.066,33 145.066,35 159,35 159,48.934 161,48.934 161,35 175,35 175,33
|
||||
161,33 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 63 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="1000px" height="1000px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<rect x="-225" y="-145" display="none" fill="#F5F5F5" width="1442" height="1342"/>
|
||||
<g>
|
||||
<rect x="103" y="632" fill="#59C6FF" width="794" height="625"/>
|
||||
<path fill="none" stroke="#59C6FF" stroke-width="110" stroke-miterlimit="10" d="M778,688V533.991
|
||||
C778,380.461,653.531,256,500,256c-153.53,0-278,124.461-278,277.991V728"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 826 B |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |