How to add Start and Stop buttons for a timer / stopwatch using Tampermonkey - userscripts

How do I create a Tampermonkey code with start stop buttons for timer?
When I select "start", the start time should be noted (ex: 1.30 PM).
When I select "stop", the stop time should be noted (ex: 1.35 PM)
And the time taken (ex: 5 min) should be displayed.
Is it possible to create this?

This similar to Add a JavaScript button using Greasemonkey or Tampermonkey?, see that question for formatting and positioning ideas.
It is recommended that you use the Performance API or the Moment.js library to handle the timing and/or time formatting. Or see: How to convert time milliseconds to hours, min, sec format in JavaScript? to do formatting the hard way.
Here is a complete working Tampermonkey userscript to illustrate the basics.
You can run the code snippet or install it using Tampermonkey to see it in action.
// ==UserScript==
// #name _Add a stopwatch / elapsed time button
// #match *://YOUR_SERVER.COM/YOUR_PATH/*
// #require https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js
// #grant GM_addStyle
// ==/UserScript==
var gblButtonClickTime;
$("body").prepend ( `
<div id="tmStopWatchBlck">
<button id="tmStopWatchBttn">Start</button>
<span id="tmTimeStat"> </span>
</div>
` );
$("#tmStopWatchBttn").click ( zEvent => {
var statusNode = $("#tmTimeStat");
var tmrButton = $(zEvent.target);
//--- Button text is either "Start" or "Stop".
if (tmrButton.text() === "Start") {
//-- Start the timer
tmrButton.text ("Stop");
statusNode.css ("background", "lightyellow");
gblButtonClickTime = performance.now ();
console.log (
"Timer started at: ", gblButtonClickTime.toFixed(0), new Date()
);
}
else {
//-- Stop the timer
tmrButton.text ("Start");
statusNode.css ("background", "lightgreen");
var stopTime = performance.now ();
var elapsedtime = stopTime - gblButtonClickTime; // Milliseconds
var purtyElpsdTime = (elapsedtime / 1000).toFixed(3) + " seconds";
console.log (
"Timer stopped at: ", stopTime.toFixed(0), new Date(),
"Elapsed: ", purtyElpsdTime
);
statusNode.text (purtyElpsdTime);
}
} );
GM_addStyle ( `
#tmStopWatchBttn {
font-size: 1.2em;
padding: 0.5ex 1em;
width: 5em;
}
#tmTimeStat {
margin-left: 1em;
padding: 0.2ex 2ex;
border: 1px solid lightgray;
border-radius: 0.5ex;
}
` );
/********************************************************************
******* Everything below this block is simulated target page. *******
******* It's NOT part of the userscript. *******
********************************************************************/
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://greasyfork.org/scripts/44560-gm-addstyle-shim/code/GM_addStyle_shim.js"></script>

Related

repeated path at different angle

I am working on a mandala art generator, and want to repeat the same free-drawing path at different angles.
On mouse:move, able to render it on the upper canvas. After mouse:up, it doesn't retain on lower canvas, read tried a few methods from here and on GitHub issues but none worked.
Got one possible solution, cloning the path and adding it to canvas, it's working with vanilla JS, but in react, it just modifies the existing path :(
Any help?
JavaScript solution, Fiddle (by Curtis Rock).
React solution I tried using the fiddle, Replit
var canvas = window._canvas = new fabric.Canvas('c');
canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
canvas.isDrawingMode = true;
canvas.on({
'path:created': pathHandler
});
function pathHandler(opt) {
var path = opt.path;
path.clone(function(clone) {
canvas.add(clone.set({
left: path.left + 10,
top: path.top + 10,
angle: 90
}));
});
canvas.requestRenderAll();
}
canvas {
border: 1px solid #999;
}
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="600" height="600"></canvas>

Cannot get lineHeight from dom element style using React

I'm trying to get the current number of lines in a dom element using react, but i can't seem to get the curent lineHeight style from it, it always returns me empty string ""
the first console.log shows me "" and the second one NaN because lineHeight is a string and it should return me an integer like 20 which represents the lineHeight
Also tried not using dangerouslySetInnerHTML and nothing changed
Also noticed every single style prop is empty
What am i missing out?
descriptionRef.current?.style?.lineHeight also returns ""
const descriptionRef = useRef<HTMLParagraphElement>();
const getNumberOfLines = () => {
if (!descriptionRef.current) return true;
const lineHeight: number = parseInt(
descriptionRef.current?.style?.lineHeight ||
document.defaultView.getComputedStyle(descriptionRef.current, null).getPropertyValue("lineHeight")
);
console.log(document.defaultView.getComputedStyle(descriptionRef.current).getPropertyValue("lineHeight"))
console.log(descriptionRef.current?.offsetHeight / lineHeight);
return lineHeight > 4 ? true : false;
}
return (
<Typography
ref={descriptionRef}
className={classes.description}
dangerouslySetInnerHTML={{ __html: text }}>
</Typography>
)
Me too was facing same issue. But using window worked for me,
const lineHeight= window
.getComputedStyle(descriptionRef.current, null)
.getPropertyValue("line-height");
you need to set the line-height via style prop to get by script. But bear in mind, 15 and 15px are different things for line-height attribute.
If we remove the style attribute, even we specify the line-height in CSS class, we cannot get its value as 12px and it will be empty as same as your case.
/* It doesn't matter you specify or not in CSS */
.div-class {
line-height: 12px;
}
(
<div ref={divRef} className="div-class" style={{ lineHeight: '15px' }}>
</div>
)
useEffect(() => {
console.log({ lineHeight: divRef.current.style?.lineHeight }); // {lineHeight: '15px'}
}, []);

Countdown component: how to prevent re-rendering in Angular2?

I have an Angular2 master component which includes a number of child components and an independant countdown component (named "clock" in the code below). Countdown component changes it's label every second and that causes the master component and all others to (needlessly) re-render. How can I prevent that?
This is the source of my countdown component:
import {Component, Input} from 'angular2/core';
#Component({
selector: 'clock',
template: `
<span>
{{caption}}
</span>
`
})
export class ClockComponent {
public caption;
#Input('seconds') seconds :number = 0;
constructor() {
var self = this;
setInterval(function(){
self.seconds--;
self.caption = self.getCaption(self.seconds);
}, 1000);
this.caption = this.getCaption(this.seconds);
}
getCaption (seconds): string {
let h = Math.floor(seconds / (60*60));
let m = Math.floor((seconds - 60 * 60 * h) / 60);
let s = seconds % 60;
return ((h < 10) ? '0' : '') + h + ':'
+ ((m < 10) ? '0' : '') + m + ':'
+ ((s < 10) ? '0' : '') + s ;
}
}
and you can imagine it being embedded alongside others in "my-app"; something like:
<clock [seconds]="1800"></clock>
<other-comps>...</other-comps>...
EDIT (per comment):
When I mean re-render, this is what happens:
I've added a console.log printout to other components (nav and question, see image below) on various rendering actions, for instance, a component has a class binder, eg:
[class.selected]="isSelected"
and I've added console.log() to the isSelected() method and can thus spot that it is called every one second, every time the countdown (clock) refreshes itself. I'd like for the countdown to change label (count down from eg 30 minutes) WITHOUT affecting nav and question components and causing them to re-render.
EDIT (2):
And here is the plunker: http://plnkr.co/edit/PwBfUQXyZyTrqPaqrwRm?p=preview
Fire up the console and watch for six those "q-nav isSelected?" appearing every second (printed from qnav component).
That's Angular's change detection that is invoked on every event, and setInterval calling the callback is such an event.
You can switch change detection to OnPush so change detection only takes place when an #Input() is updated or when you invoke change detection explicitly, for example by calling methods on ChangeDetectorRefs
import {Component, Input, OnInit, EventEmitter, Output, OnChanges, ChangeDetectionStrategy} from 'angular2/core';
#Component({
selector: 'q-nav',
template: `
<span *ngFor="#a of currAnswers; #i = index" class="aii-qn"
[class.selected]="isSelected(i)"
(click)="onSelect(i)">
<span class="badge">{{ i+1 }}</span>
  </span>
`,
styles: [`
.aii-qn {
color: #0000ff;
cursor: pointer;
font-size: 2rem;
}
.selected {
border: black solid 2px;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush
})
For more details see:
http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Combining <ons-sliding-menu> and <ons-carousel>

I have an app with <ons-sliding-menu> and a page with <ons-toolbar> and a horizontal <ons-carousel> covering the remaining space.
For the <ons-sliding-menu> the parameter swipe-target-width="50px" is set.
Is there a way to tell the <ons-carousel> to ignore events originating from the most left 50px and let these go to the menu?
Currently there is no option to make the carousel ignore events on one side, but perhaps you can make a trick. You can put a div at the same level than the carousel and let it take the clicks instead of the carousel in the area you need:
<div class="cover"></div>
<ons-carousel>
...
</ons-carousel>
You can change these values to fit your case:
.cover {
position: absolute;
left: 0;
height: 100%;
width: 200px;
z-index: 1;
}
Check it out here: http://codepen.io/frankdiox/pen/YqKOJE
Hope it helps!
After some experimentation, I came to the solution to inject the necessary functionality directly in the drag event handlers of the OnsCarouselElement. For this purpose I have introduced the attribute swipe-ignore-left for the <ons-carousel>. The other sites could easily be added when needed.In order to inject the functionality, load this JS-Code after loading onsenui.js:
(function () {
'use strict';
/****************************************************************
Checks the current event against the attribute swipe-ignore-left.
****************************************************************/
window.OnsCarouselElement.prototype._ignoreDrag = function (event) {
var attr = this.getAttribute('swipe-ignore-left');
if (attr === undefined) return false;
var left = parseInt(attr, 10);
if (left === undefined || left < 1) return false;
var startX = event.gesture.center.clientX - event.gesture.deltaX;
return startX < left;
};
/****************************************************************
Save the original drag-event-handlers
****************************************************************/
var originalCarouselOnDrag = window.OnsCarouselElement.prototype._onDrag;
var originalCarouselOnDragEnd = window.OnsCarouselElement.prototype._onDragEnd;
/****************************************************************
Override: OnsCarouselElement.prototype._onDrag
****************************************************************/
window.OnsCarouselElement.prototype._onDrag = function (event) {
if (this._ignoreDrag(event)) return;
originalCarouselOnDrag.apply(this, arguments);
};
/****************************************************************
Override: OnsCarouselElement.prototype._onDragEnd
****************************************************************/
window.OnsCarouselElement.prototype._onDragEnd = function (event) {
if (this._ignoreDrag(event)) return;
originalCarouselOnDragEnd.apply(this, arguments);
};
})();
To preserve for example the left 20 pixel for the <ons-sliding-menu>, this HTML is to provide:
<ons-sliding-menu ... side="left" swipeable swipe-target-width="20px" />
...
<ons-carousel ... swipeable swipe-ignore-left="20px" />

Angular $scope.$apply exceptions when maximing a Malhar widget

I am working in the Malhar widget framework, which is based on jQuery sortable widgets. ex/ https://github.com/DataTorrent/malhar-angular-dashboard
I am working on some DOM manipulation on each widget (maximize/minimize/refresh), and running into some Angular $scope.$apply exceptions below.
Function details:
The $scope.grabSouthResizer function (working fine) is the Mahlar function that came with the framework; I just modified it slight to also refresh the Kendo UI charts.
The $scope.maxResizer function is my custom function, which is throwing $rootScope:inprog exceptions every time is hits my $scope.$apply();.
$scope.grabSouthResizer = function (e) {
var widgetElm = $element.find('.widget');
e.stopPropagation();
e.originalEvent.preventDefault();
// get the starting horizontal position
// .. code ommitted for brevity
// sets new widget width on mouseup
var mouseup = function (e) {
// calculate height change
var curY = e.clientY;
var pixelChange = curY - initY;
var widgetContainer = widgetElm.find('.widget-content');
var diff = pixelChange;
var height = parseInt(widgetContainer.css('height'), 10);
var newHeight = (height + diff);
$scope.widget.setHeight(newHeight + 'px');
$scope.$emit('widgetChanged', $scope.widget);
$scope.$apply(); // *** NO EXCEPTIONS THROWN ***
$scope.$broadcast('widgetResized', {
height: newHeight
});
// kendo chart - refresh height
var chart = widgetElm.find('.k-chart').data("kendoChart");
if (chart != undefined) {
chart.setOptions({ chartArea: { height: newHeight - (newHeight * .10) } });
chart.resize($(".k-chart"));
}
};
};
$scope.maxResizer = function (e) {
// TODO: properly restore the window to original position..
var widgetElm = $element.find('.widget');
e.stopPropagation(); // testing - same as grabSouthResizer() below
e.originalEvent.preventDefault();
var pixelHeight = widgetElm.height();
var pixelWidth = widgetElm.width();
// fyi: '.k-tree' will auto-resize, so no need to find that
var chart = widgetElm.find('.k-chart').data("kendoChart");
var treelist = widgetElm.find('.k-treelist').data("kendoTreeList");
// height differential (reduce height of container if inner widget is a treelist)
var ht_diff = (chart != undefined ? 200 : 600);
var newHeight = window.innerHeight - ht_diff;
if (!widget.maximized) {
// widget container maximize
widget.maximized = true;
$scope.widget.setWidth(window.innerWidth);
$scope.widget.setHeight(newHeight); //window.innerHeight - ht_diff);
$scope.$emit('widgetChanged', widget);
$scope.$apply(); // *** THROWS $rootScope:inprog EXCEPTIONS !!! ***
$scope.$broadcast('widgetResized', {
width: window.innerWidth,
height: newHeight
});
if (chart != undefined) {
// refresh Kendo chart
chart.setOptions({ chartArea: { height: widgetElm.height()*.9, width: widgetElm.width()*.95 } });
chart.resize($(".k-chart"));
}
}
kendoRefreshTimer(); // this work-around used instead of $scope.$apply()
}
var timer;
function kendoRefreshTimer() {
timer = $timeout(function () {
refreshKendo();
}, 1);
}
function refreshKendo() {
// Kendo chart refresh here...
}
Big question: why is $scope.$apply(); causing errors in my maxResizer function, but not in the Malhar original grabSouthResizer function ? I also understand that $scope.$apply() is NOT recommended, but it seems to be widely used as a work-around.
I would create an online plunk, but I still haven't set up this Malhar widget framework online as of yet. It's a bit complicated to set up.
Your advice is appreciated.
regards,
Bob
* UPDATE *
I updated my post to show how I've worked around this scope.apply issue by using a $timeout function, but I don't like the split-second delay in the UI. i.e. You can see the Kendo chart resizing itself, so it doesn't look so smooth.

Resources