ProgressBar

Create and manage simple progress bars

目前為 2019-10-16 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.gf.qytechs.cn/scripts/391236/741165/ProgressBar.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ProgressBar library
//
// Create and manage simple progress bars.
//
// https://greasyfork.org/scripts/**TBD**-progressbar
// Copyright (C) 2019, Guido Villa
// Original version of the script is taken from IMDb 'My Movies' enhancer:
// Copyright (C) 2008-2018, Ricardo Mendonça Ferreira ([email protected])
// Released under the GPL license - http://www.gnu.org/copyleft/gpl.html
//
// For instructions on user scripts, see:
// https://greasyfork.org/help/installing-user-scripts
//
// To use this library in a userscript you must add to script header:
  // @require  https://greasyfork.org/scripts/**TBD**-progressbar/code/ProgressBar.js
  // @grant    GM_addStyle
//
// --------------------------------------------------------------------
//
// ==UserScript==
// @namespace       https://greasyfork.org/users/373199-guido-villa
// @exclude         *
//
// ==UserLibrary==
// @name            ProgressBar
// @description     Create and manage simple progress bars
// @version         1.0
// @author          guidovilla
// @date            16.10.2019
// @copyright       2019, Guido Villa (https://greasyfork.org/users/373199-guido-villa)
// @license         GPL-3.0-or-later
// @homepageURL     https://greasyfork.org/scripts/**TBD**-progressbar
// @supportURL      https://gitlab.com/gv-browser/userscripts/issues
// @contributionURL https://tinyurl.com/gv-donate-2e
// ==/UserScript==
//
// ==/UserLibrary==
//
// --------------------------------------------------------------------
//
// To-do (priority: [H]igh, [M]edium, [L]ow):
//   - [M] width must be an integer multiple of background-size, otherwise animation will skip => address
//   - [M] speed of the animation depends on width => fix?
//   - [m] speed of transition is not constant (time is constant, regardless of the "space" to be travelled) => can it be fixed?
//   - [H] wrap in order to hide global variables
//   - [M] nicer presentation style (maybe small vertical bars), graphical improvements
//
// Changelog:
// ----------
// 2019.10.16  [1.0] First version
// 2019.10.14  [0.1] First test version, private use only
//

/* jshint esversion: 6, supernew: true, laxbreak: true */
/* exported EL, Library_Version_PROGRESSBAR */

const Library_Version_PROGRESSBAR = '1.0';

/* How to use the library

- Create a new progress bar:
  var pb = new ProgressBar(...)

- Change the progress:
  pb.update(...)
  pb.advance(...)

- Remove the progress bar:
  pb.close()

Details
Progress bars are defined by three main parameters:
- finish:   value that defines what is 100%
            this is set at creation time and cannot be changed
- progress: value that defines current completion status (must be <= finish)
            initial progress is set a creation time, then it can be updated
            with update() and advance()
            When progress = -1, the bar is in "generic" loading mode, i.e. it
            does not show a specific progress but an unspecified loading status
- message:  the message printed inside the bar (e.g. "Loading...")
            initial message is set a creation time, then it can be changed
            with every update() and advance().
            The message can contain a few placeholders that are replaced with
            actual progress data:
            - {#}: replace with current progress number
            - {$}: replace with finish value
            - {%}: replace with completion percentage (= 100*progress/finish)
            E.g.: "Loading {#} of {$}..."  =>  "Loading 7 of 23..."

All numbers are integers.

Information for changing styles:
The HTML id of the container DIV can be accessed through the 'id' property
of the progress bar object.
All elements that constitute the bar have a generic "pb-progress-bar" class and
a specific "pb-progress-bar-XXX" class different for each element.
Generic loading is enabled by applying a "pb-generic" class to the
container DIV.

Parameters (all parameters are optional):

- ProgressBar(finish, msg, options)
  Create a new progress bar. Parameters:
  - finish: maximum value that can be reached (default is 100)
  - msg: message written in the bar, see above for substitutions
         default is "Loading {#}/{$}..."
  - options: an object that may contain:
    - start: initial progress status (default is 0, i.e. the beginning)
    - container: positioned element where the bar will be centered
                 null (the default): center bar on the screen
    - width: width in pixels of the progress bar (default is 226.3)
    - height: height in pixels of the progress bar (default is 30)

- update(progress, msg)
  Update the progress bar status. Parameters:
  - progress: the new progress value (default is 0)
  - msg: an optional new message (default is: don't change message)

- advance(value, msg)
  Increment the progress bar status. Parameters:
  - value: the increment value, can be negative (default is 1)
  - msg: an optional new message (default is: don't change message)

- close()
  Close the progress bar and remove it from the DOM.

*/


    var progress_bar_style_has_been_loaded = false;
    var progress_bar_index = 0;
    // Create progress bar
    // eslint-disable-next-line max-statements
    function ProgressBar(finish = 100, msg = 'Loading {#}/{$}...', options) {
        // style definition
        var STYLE = '.pb-progress-bar.pb-progress-bar-box{border:2px solid black;background-color:white;padding:4px;outline:white solid 6px;}'
                  + '.pb-progress-bar.pb-progress-bar-bar{background-color:green;height:100%;transition:width 300ms linear;}'
                  + '.pb-progress-bar.pb-progress-bar-txtcont{position:absolute;top:0;left:0;width:100%;height:100%;display:table;}'
                  + '.pb-progress-bar.pb-progress-bar-txt{display:table-cell;text-align:center;vertical-align:middle;font:16px verdana,sans-serif;color:black;}'
                  + '.pb-progress-bar.pb-progress-bar-box.pb-generic{background:repeating-linear-gradient(-45deg,#F0F0F0 0 20px,#ccc 20px 40px);background-size:56.56854px;animation:2s linear infinite loading;}'
                  + '.pb-progress-bar.pb-progress-bar-box.pb-generic .pb-progress-bar-bar{background-color:transparent;transition:none}'
                  + '@keyframes loading{from{background-position-x:0%;} to{background-position-x:100%;}}';
        if (!progress_bar_style_has_been_loaded) {
            GM_addStyle(STYLE);
            progress_bar_style_has_been_loaded = true;
        }

        var self = this;

        // basic configuration
        this.id       = 'pb-progress-bar-' + ++progress_bar_index; // 'id' is public
        var start     = 0;
        var container = null;
        var width     = 226.27417;
        var height    = 30;
        var message   = msg;

        var current;  // completion status of the progress bar

        var pbBox, pb, pbTxtCont, pbTxt;  // elements of the progress bar

        // helper function to create the elements
        function createElement(father, elementType, className, id) {
            var elem = document.createElement(elementType);
            if (typeof id !== 'undefined') elem.id = id;
            elem.className = 'pb-progress-bar ' + className;
            father.appendChild(elem);
            return elem;
        }

        // initialization function
        function init() {
            // check for options in the call
            if (options && typeof options === 'object') {
                if (typeof options.id        !== 'undefined') self.id   = options.id;
                if (typeof options.start     !== 'undefined') start     = options.start;
                if (typeof options.container !== 'undefined') container = options.container;
                if (typeof options.width     !== 'undefined') width     = options.width;
                if (typeof options.height    !== 'undefined') height    = options.height;
            }

            // calculate positioning
            var containerWidth, containerHeight,
                cntElem,
                positioningStyle;

            function setPositioningVars(cnt, pos, w, h) {
                containerWidth  = w;
                containerHeight = h;
                cntElem = cnt;
                positioningStyle = pos;
            }

            if (container) {
                var rect = container.getBoundingClientRect();
                setPositioningVars(container, 'absolute', rect.width, rect.height);
            } else {
                setPositioningVars(document.body, 'fixed', window.innerWidth, window.innerHeight);
            }
            var top  = containerHeight / 2 - height / 2;
            var left = containerWidth  / 2 - width  / 2;

            // create the elements
            pbBox = createElement(cntElem, 'div', 'pb-progress-bar-box', self.id);
            pbBox.style.cssText = 'position:' + positioningStyle
                                + '; height:' + height + 'px;width:' + width
                                + 'px;top:'   + top    + 'px;left:'  + left + 'px;';

            pb        = createElement(pbBox,     'div', 'pb-progress-bar-bar');
            pbTxtCont = createElement(pbBox,     'div', 'pb-progress-bar-txtcont');
            pbTxt     = createElement(pbTxtCont, 'div', 'pb-progress-bar-txt');

            // set the initial progress
            self.update(start);
        }


        /* PUBLIC members */

        // update the progress to "currentVal" and optionally change the message
        this.update = function(currentVal = 0, newMsg) {
            if (newMsg) message = newMsg;
            var newVal = (currentVal > finish ? finish : currentVal);

            if (newVal < 0) {
                // setting the width to zero is not really needed, but ensures a
                // more consistent behaviour in cases where the delay (see
                // below) is not enough.
                pb.style.width = '0';
                // try to make the message more appealing in "generic" case
                pbTxt.textContent = message
                                    .replace(/ *{#}.*{\$} */g, '')
                                    .replace(/ *{#} */g,       '')
                                    .replace(/ *{\$} */g,      '')
                                    .replace(/ *{%} *%? */g,   '');
                pbBox.classList.add('pb-generic');
            } else {
                pb.style.width = (100*newVal/finish) + '%';
                if (current < 0) {
                    // if exiting from "generic" mode a small delay is needed,
                    // otherwise the class may be removed when changing the
                    // width, and the width transition takes place anyway
                    setTimeout(function() {
                        pbBox.classList.remove('pb-generic');
                    }, 33);
                } else {
                    pbBox.classList.remove('pb-generic');
                }
                // replace placeholders with actual numbers
                pbTxt.textContent = message
                                    .replace(/{#}/g, newVal)
                                    .replace(/{\$}/g, finish)
                                    .replace(/{%}/g, Math.round(100*newVal/finish));
            }
            current = newVal;
        };


        // advance the progress by "value" and optionally change the message
        this.advance = function(value = 1, newMsg) {
            self.update(current + value, newMsg);
        };


        // close/remove the progress bar
        this.close = function() {
            pbBox.parentNode.removeChild(pbBox);
        };


        /* INITIALIZATION */
        init();
    }