I am a new user to awesomewm (but have used other WMs before: i3, bspwm, xmonad, etc). I like to have some shell scripts that I have written in my wibar (I think that's what its called, the bar at the top of the screen with the taglist) to display stuff like battery, audio, etc (as I know is common). Currently I am using the "wibar.widget.watch" to do this, as shown below.
-- Right widgets
layout = wibox.layout.fixed.horizontal,
awful.widget.watch('musicbar', 5),
wibox.widget.textbox(' | '),
awful.widget.watch('wifibar', 5),
wibox.widget.textbox(' | '),
awful.widget.watch('audiobar', 0.5),
wibox.widget.textbox(' | '),
awful.widget.watch('batbar', 5),
In the code above stuff like "audiobar" are scripts that return information as standard output. It all works perfectly, even displays the emojis well :). I have one problem (maybe just an optimization).
Currently I have the audiobar running twice a second, this is because that is the only one which directly changes based on my input (changing volume) and so I want it to change immediately (obviously this still has a <= 0.5 second delay, which is annoying). This means that most of the time it is updating twice a second unnecesarily.
So, I'm wondering if there is a way to have it update when I change the volume, which I've bound to the XF86 audio keys in rc.lua, instead of changing based on a timer. Based on my reading of the documentation, there is no way to do this with the watch widget, but as I said I am new to awesome.
Below is how I bound the keys (shouldn't make a difference, but I imagine that this is where the change would be made).
awful.key(
{},
"XF86AudioLowerVolume",
function()
awful.spawn("amixer -q set Master 5%-")
end,
{description = "lower volume", group = "control"}
),
I know that I can use some of the pre-made widgets on github to display volume and stuff but I like the shell scripts because they let me easily move between WMs and are simpler than those widgets (which I like because it means that I can more easily address problems with them and make them display exactly what I want, as well as learn).
edit: I am willing to learn to do this with lua, I just first want to see if I can easily do it with shell scripts.
You need to keep a reference to the timer around that awful.widget.watch creates internally. To do this, you need to do something like this in global context (i.e. outside of the definitions of the widgets or keybindings):
local musicbar_widget, musicbar_timer = awful.widget.watch('musicbar', 5)
You now add musicbar_widget to your wibox (instead of calling awful.widget.watch there). Now, you can "force-update" the widget via musicbar_timer:emit_signal("timeout"). This "pretends" to the widget that the timeout happened again.
In your keybinding (yes, I am mixing up your widgets here, most likely musicbar has nothing to do with the volume):
awful.key(
{},
"XF86AudioLowerVolume",
function()
awful.spawn("amixer -q set Master 5%-")
musicbar_timer:emit_signal("timeout")
end,
{description = "lower volume", group = "control"}
),
Note that this might or might not work. awful.spawn only starts the command, but does not wait for it to finish. So now you are changing the volume at the same time that your are querying it. If querying finishes faster than changing the volume, then the update does not actually occur.
To only update the widget after changing the volume is done, do something like the following:
awful.spawn.with_line_callback(
"amixer -q set Master 5%-", {
exit = function()
musicbar_timer:emit_signal("timeout")
end
})
I ran into a similar problem with streetturtle's volumearc widget. By default, it runs an update command:
Everytime the volume is modified through the widget.
Every second to catch any external volume changes (like with key bindings defined in rc.lua).
watch(get_volume_cmd, 1, update_graphic, volumearc)
Of course this has the following disadvantages:
A slight delay in the volume update (one second in still very perceptible).
On my machine, a constant 1% CPU load just for this task. A trifle I know, but little things add up.
Using a returned update function
A possible solution is to return an update function, available in the context of rc.lua and call this update along the volume modifications.
In volumearc.lua, in the worker function, we put the widget update into a dedicated function:
local ext_update = function()
spawn.easy_async(get_volume_cmd,
function(stdout, stderr, exitreason, exitcode)
update_graphic(volumearc, stdout, stderr, exitreason, exitcode) end)
end
And we return it both from the worker function:
return volumearc, ext_update
and from the widget itself:
volumearc, ext_update = { __call = function(_, ...) return worker(...) end }
return setmetatable(widget, volumearc), ext_update
Now we can use it in rc.lua:
local volumearc_widget = require("widgets.volume-widget.volumearc")
-- ...
local GET_VOLUME = "amixer sget Master"
local INC_VOLUME = "amixer sset Master 3%+"
local DEC_VOLUME = "amixer sset Master 3%-"
local TOG_VOLUME = "amixer sset Master toggle"
myvolume, volume_update = volumearc_widget({get_volume_cmd=GET_VOLUME,
inc_volume_cmd=INC_VOLUME,
dec_volume_cmd=DEC_VOLUME,
tog_volume_cmd=TOG_VOLUME})
-- `myvolume` is the widget that can be added as usual in the wibox.
-- `volume_update` is the function to call in order to trigger an update.
-- ...
-- In the key bindings:
awful.key({}, "XF86AudioMute",
function () awful.spawn(TOG_VOLUME) volume_update() end,
{description="mute", group = "media"}),
awful.key({}, "XF86AudioRaiseVolume",
function () awful.spawn(INC_VOLUME) volume_update() end,
{description="raise volume", group = "media"}),
awful.key({}, "XF86AudioLowerVolume",
function () awful.spawn(DEC_VOLUME) volume_update() end,
{description="lower volume", group="media"}),
Now the volume will be updated both when changed from the widget or from the media keys, instantly and without the need for polling.
Caveat
This does not catch changes made through other interfaces, such as alsamixer for example. To catch these changes, you might want to run the watch function with a very low time frequency (say once per minute).
The code given here is specific to streetturtle's widget, but the concept of returning an internal update function applies for any widget.
Related
New appmaker user here.
I'm trying to port the work permits approval "app" I made with G Suite form+spreadsheet+GAS; users should enter the day, the start and end time of the permit.
I can see from the Forum Sample that the Date field type is a DateTime field type, so I can use it in my model.
The problem is I cannot find the time picker in the widgets, and the date box has no option to also enter the time.
Am I missing something?
Time Pickers for App Maker
I read your question and thought I'd try to role one of my own and this is what I came up with. I put all of the buttons on a pageFragment and call it with app.showDialog(app.pageFragments.timePicker2);
I only use clientside script.
function updateOutput(){
var h=app.pageFragments.TimePicker2.properties.hour||'00';
var m=app.pageFragments.TimePicker2.properties.minute||'00';
var s=app.pageFragments.TimePicker2.properties.second||'00';
var t=h + ':' + m + ':' + s;
app.pageFragments.TimePicker2.descendants.timeLBL.text=t;
return t;
}
function updateHour(v){
app.pageFragments.TimePicker2.properties.hour=v;
updateOutput();
}
function updateMinute(v){
app.pageFragments.TimePicker2.properties.minute=v;
updateOutput();
}
function updateSecond(v){
app.pageFragments.TimePicker2.properties.second=v;
updateOutput();
}
Here's what my time picker looks like:
Yes. Adding all of the buttons is a nuisance but there are a few features about AppMaker that make it more tolerable.
First you can assign the TimePicker form properties which I use as global properties. I had three hour,minute and second.
Then after you add all of the hour buttons you can grab all of them at one time by clicking each one while holding down control on a windows machine and click on the onClick event and pick custom action and type this in updateHour(widget.text); the code completion won't give you text as an option but type it any way.
I just figured out how to grab the buttons all a one time by pushing shift and selecting with the mouse
Do the same thing with the minute and second buttons using updateMinute(widget.text) and updateSecond(widget.text); This saves you a lot of time typing all of the functions into each widget control panel. Also you don't have to bother giving all of the buttons special names like I did.
But you might like to format them with the following css.
And again you can grab all of the buttons at one time and change the following setting:
That way you can style all of the buttons at one time.
My save button just copies the final string into a label on the main panel.
app.pages.Testing.descendants.timeLBL2.text=app.pageFragments.TimePicker2.descendants.timeLBL.text;
app.closeDialog();
You will probably want to do something more elegant.
Here's a demo: in preview mode. Sorry about the 24 hour clock. I always use this for my own stuff because it's so much easier and I like it. You'll probably want AM & PM. I'll probably go back and do that too.
For an AM/PM Picker I used these functions:
function updateOutputAP(){
var h=app.pageFragments.TimePicker3.properties.hour||'00';
var m=app.pageFragments.TimePicker3.properties.minute||'00';
var s=app.pageFragments.TimePicker3.properties.second||'00';
var ap=app.pageFragments.TimePicker3.properties.ap||' ';
var t=h + ':' + m + ':' + s + ' ' + ap;
app.pageFragments.TimePicker3.descendants.timeLBL.text=t;
return t;
}
function updateHourPM(v){
app.pageFragments.TimePicker3.properties.hour=v;
app.pageFragments.TimePicker3.properties.ap='PM';
updateOutputAP();
}
function updateHourAM(v){
app.pageFragments.TimePicker3.properties.hour=v;
app.pageFragments.TimePicker3.properties.ap='AM';
updateOutputAP();
}
function updateMinuteAP(v){
app.pageFragments.TimePicker3.properties.minute=v;
updateOutputAP();
}
function updateSecondAP(v){
app.pageFragments.TimePicker3.properties.second=v;
updateOutputAP();
}
And this is what my picker looks like:
Now that I know how to pick the components easily with the mouse it was a break to make this change.
Three AppMaker Time Pickers:
At this time App Maker doesn't provide out of the box Time or Date/Time picker widgets, it means that you need to implement one by yourself. There are at least two ways to accomplish this task:
App Maker way
Wait and hope when App Maker will introduce Time or Date/Time picker widget or use existing App Maker widgets to emulate Time Picker. Calendar Sample can be a good starting point:
Hack into DOM/JS
If you have no concerns about cross-browser compatibility and you are OK to get you hands dirty with DOM manipulation by javascript, creating events listeners and other cool stuff, then you can play with HTML widget and native date/time or time input, or even some third party library.
One simple option would be to simply use a textbox and set the validation in the model field.
You can update your Date object on save or just use as is depending on your application. You get the benefit of auto validation errors in the UI to guide your user and it takes only seconds to set up.
Regex:
\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))
Every time you deploy to Firebase hosting a new deploy version is created so you can roll back and see who deployed. This means that each time every file you deploy is stored and occupying more space.
Other than manually deleting each deployed version one by one, is there any automated way to clean those useless files?
You're correct. You'll need to delete the old deployed versions one by one using the Firebase Hosting console.
There's no other way to do this, so I'd suggest you to file a feature request to enable deletion of multiple deployed version in the Firebase Hosting console.
Update:
You can vote here (please avoid +1 spam, use reactions) https://github.com/firebase/firebase-tools/issues/215#issuecomment-314211730 for one of the alternatives proposed by the team (batch delete, keep only X versions, keep versions with published date < Y)
UPDATE Mar/2019
There's now a proper solution: "Version history settings" which allows to keep the last X versions.
https://support.google.com/firebase/answer/9242086?hl=en
UPDATE Feb/2019
Confirmed by Google employee # github.com/firebase/firebase-tools/issues/...
It is actively being worked on. 🙂
🎉🎉🎉
Before continuing reading:
You can vote here (please avoid +1 spamming, use reactions) https://github.com/firebase/firebase-tools/issues/215#issuecomment-314211730 for one of the alternatives proposed by the team
So, by using Chrome Dev tools I found a way to delete multiple versions. Keep in mind it requires a bit for work (proceed with care since deleted versions can't be restored and you won't get any warnings like when using the UI).
Step 1. Retrieving the version list.
Open Chrome Dev Tools (if you don't know how to chances are you should wait for a proper solution by Firebase's team).
Open Firebase's Console and go to the "Hosting" tab.
Go to the "Network" tab on CDT and use the Websockets filter.
Select the request named .ws?v=5&ns=firebase
Open the "Frames" tab
Now comes the tedious part: Select the frames with the highest "length" value. (Depending on your data, it could be 2-n frames. In my case, 3 frames with 14k-16k length)
Paste each of the frame's data in order (which will form a valid JSON object).
Extracting the data: There are several ways to do it. I opted for simple JS on CDT's console.
var jsonString = '...';
var json = JSON.parse(jsonString);
var ids = Object.keys(json.d.b.d);
Step 2. Performing the requests
Almost there :P
Now that you have the IDs, perform the following requests:
DELETE https://firebasehosting.clients6.google.com/v1beta1/sites/PROJECT_NAME/versions/-VERSION_ID?key=KEY
I used Sublime (to create the request strings) + Paw.
The "KEY" can be copied from any of CDT's requests. It doesn't match Firebase's Web API key
=> Before performing the requests: take note of the version you don't want to delete from the table provided by Firebase. (Each version listed on the website has the last 6 digits of it's ID under your email)
(Screenshots weren't provided since all of them would require blurring and a bit of work)
This script is not yet super-solid, so use it at your own risk. I'll try to update it later, but worked for me for now.
Just some javascript to click on buttons to delete deployed items one by one.
var deleteDeployment = function(it) {
it.click()
setTimeout(function() {
$('.md-dialog-container .delete-dialog button.md-raised:contains("Delete")').click()
}, 300)
}
$('.h5g-hist-status-deployed').map((i, a) => $(a).parent()).map((i, a) => $(a).find('md-menu button:contains(Delete)')).each((i, it) => {
setTimeout(function() {
deleteDeployment(it)
}, (i + 1) * 2000)
})
Firebase finally implemented a solution for this.
It is now possible to set a limit of retained versions.
https://firebase.google.com/docs/hosting/deploying#set_limit_for_retained_versions
EDIT: previous link is outdated. Here is a new link that works:
https://firebase.google.com/docs/hosting/usage-quotas-pricing#control-storage-usage
This may be a bit brittle due to the selectors' reliance on current DOM structure and classes on the Hosting Dashboard, but it works for me!
NOTE: This script (if executed from the console) or bookmarklet will click and confirm delete on all of the rows in the current view. I'm fairly certain that even if you click delete on the current deployment it will not delete it.
Function for running in console:
let deleteAllHistory = () => {
let deleteBtns = document.querySelectorAll('.table-row-actions button.md-icon-button');
const deleteBtn = (pointer) => {
deleteBtns[pointer].click();
setTimeout(() => {
document.querySelector('.md-open-menu-container.md-clickable md-menu-item:last-child button').click();
setTimeout(() => {
document.querySelector('.fb-dialog-actions .md-raised').click();
if(pointer < deleteBtns.length - 1) {
deleteBtn(pointer + 1);
}
}, 500);
}, 500);
};
deleteBtn(0);
};
Bookmarklet:
javascript:(function()%7Bvar%20deleteBtns%3Ddocument.querySelectorAll('.table-row-actions%20button.md-icon-button')%2CdeleteBtn%3Dfunction(a)%7BdeleteBtns%5Ba%5D.click()%2CsetTimeout(function()%7Bdocument.querySelector('.md-open-menu-container.md-clickable%20md-menu-item%3Alast-child%20button').click()%2CsetTimeout(function()%7Bdocument.querySelector('.fb-dialog-actions%20.md-raised').click()%2Ca%3CdeleteBtns.length-1%26%26deleteBtn(a%2B1)%7D%2C500)%7D%2C500)%7D%3BdeleteBtn(0)%7D)()
Nathan's option is great, but I have a quick-and-dirty method using AutoHotkey. Takes about a second per version to delete, so you can knock out a page in 10 seconds.
#a::
Click
MouseGetPos, xpos, ypos
MouseMove, xpos, ypos + 30
Sleep 300
Click
Sleep 400
Click 1456, 816
MouseMove, xpos, ypos + 82
return
#s::
Click
MouseGetPos, xpos, ypos
MouseMove, xpos, ypos - 820
return
You'll likely need to modify the exact pixel values for your screen, but this works perfectly on my 1920x1080.
Win + a is delete and move to the next entry, Win + s is move to the next page. Put your mouse on the first 3-dot menu and go for it!
On top of the release history table, click the tool bar and select "Version history settings". Set to desired amount and click save.This will auto delete older deployments.
I don't know it can help you or not but I can delete old deployments from "hosting" menu like this:
Delete or rollback old deployment
I think I should begin with an example of what I want:
User allow the web page to access mic, then start recording.
Every 3 seconds (for example) capture what the user say (maybe into an Blob).
Repeat until user want to stop.
I've found many example that use AudioContext.createScriptProcessor but it work by given a buffer size, I love to have similar thing but given a duration.
you can simply use recorderjs, and use it in the below mentioned fashion:
var rec = new Recorder(source);
rec.record();
var recInterval = setInterval(function(){
rec.exportWAV(function(blob){
rec.clear();
// do something with blob...
});
}, 3000); // 3000 - to get blob for every three seconds.
later on some button click, add rec.stop() to end .
I've got a simple CSV file with 40,000 rows which I'm processing browser-side with papa-parse.
I'm trying to insert them one-by-one into a collection using the techniques in Discover Meteor and other 101 posts I find when Googling.
40000 insert browser-side pretty quickly - but when I check mongo server side it's only got 387 records.
Eventually (usually after 20 seconds or so) it starts to insert server-side.
But if I close or interrupt the browser, the already-inserted records disappear obviously.
How do I force inserts to go server-side, or at least monitor so I know when to notify the user of success?
I tried Tracker.flush() no difference.
I'd go server-side inserts in a Meteor.method, but all the server-side CSV libraries are more complex to operate than client-side (I'm a beginner to pretty much everything programming :)
Thanks!
This is the main part of my code (inside client folder):
Template.hello.events({
"submit form": function (event) {
event.preventDefault();
var reader = new FileReader();
reader.onload = function (event) {
var csv = Papa.parse(this.result, {header: true});
var count = 0;
_.forEach(csv.data, function (csvPerson) {
count++;
Person.insert(csvPerson);
console.log('Inserting: ' + count + ' -> ' + csvPerson.FirstName);
});
};
reader.readAsText(event.target[0].files[0]);
}
});
The last few lines of console output:
Inserting: 39997 -> Joan
Inserting: 39998 -> Sydnee
Inserting: 39999 -> Yael
Inserting: 40000 -> Kirk
The last few lines of CSV (random generated data):
Jescie,Ayala,27/10/82,"P.O. Box 289, 5336 Tristique Road",Dandenong,7903,VI,mus.Proin#gravida.co.uk
Joan,Petersen,01/09/61,299-1763 Aliquam Rd.,Sydney,1637,NS,sollicitudin#Donectempor.ca
Sydnee,Oliver,30/07/13,Ap #648-5619 Aliquam Av.,Albury,1084,NS,Nam#rutrumlorem.ca
Yael,Barton,30/12/66,521 Auctor. Rd.,South Perth,2343,WA,non.cursus.non#etcommodo.co.uk
Kirk,Camacho,25/09/08,"Ap #454-7701 A, Road",Stirling,3121,WA,dictum.eu#morbitristiquesenectus.com
The hello template is a simple form obviously, just file select and submit.
Client code is under client directory.
Person defined in a file in application root.
CSV parsed as strings for now, to avoid complexity.
The records inserted look fine, retrieve by name, whatever.
Person.find().count() browser-side in console results in 40000.
Happy to send the file, which is only 1.5MB and it's random data - not sensitive.
I think call() should work as follows:
On client side
Meteor.call("insertMethod",csvPerson);
And method on server side
insertMethod: function(csvPerson){
Person.insert(csvPerson);
}
In Meteor, on some scenarios, if you don't pass a callback the operation will sync.
If you run the code Person.insert(csvPerson); on the server, the operation will be sync not async. Depending on what you want to do, you might have serious problems in the future. On the client, it won't be sync but async.
Since node.js is an event-based server, a single sync operation can halt the entire system. You've to be really about your sync operations.
For importing data, the best option is to do at server-side inside Meteor.startup(function(){ //import code goes here}).
The solution propose by Sindis works but it slow and if the browser closes (for some reason), you're not keeping a track of the already inserted records. If you use Meteor.call("insertMethod",csvPerson);, this operation will be sync on the client.
The best option on your beginner scenario (not optimal) is to:
1- While (You have record to insert)
2- Call Meteor.call without a callback
3- Count all the inserted fields in the Collection
4- Save this value to localStorage
5- Go back to step 1
This works assuming that the order of insertion is the same on every insert attempt. If you browser fails, you can always get the value from localStorage and skip that number of records.
I've got a ASP.net page that create an excel file using eeplus library (http://epplus.codeplex.com/). my problem is that I create a really big file. It take times to be done and showed to the client. I search a way to show a kind of progressbar of the creation.
for the moment, the client click and wait til the file is created, so I cant really showed something.
What you're trying to do is actually pretty complicated. You might just want to show a spinning wheel gif or something and save yourself the headache.
However, if you're feeling adventurous, read on.
To use a progress bar, you need to create a way to measure how much % complete your task is. This usually involves some kind of incrementer that gets measured against the total number of rows/column/whatever that you're creating. I don't know what language you're using, so here's some pseudo to help you out:
var totalRows = 100;
var processedRows = 0;
var progress = 0;
while( processedRows < totalRows ){
Process_A_Row();
processRows++;
progress = processedRows / totalRows;
StoreProgressSomewhereForPolling();
}
So there's your basic mechanism for tracking progress. On the client, you'll need to set up a way to poll the value of the progress variable. This gets messy because you'll quickly learn that you need a way to isolate the progress variable for every individual request. Its up to you how to implement this -- there are lots of ways to do it. One solution I saw stored the progress value in a static dictionary keyed by username so that it could be easily polled by the client with webmethods.
A quick solution would be to use the Ajax.NET UpdateProgress control. Just display a GIF that spins while the server is processing.