#if i find a ts file later i will fix these
Explore tagged Tumblr posts
Photo
TAERAE Not Alone (230420)
#boys planet#zerobaseone#zb1#taerae#kim taerae#m*#useroro#higabi#heyiri#lunanuggets#awful group name but they have him so they won#if i find a ts file later i will fix these#anyways taerae debutation everyone cheered !!!
536 notes
·
View notes
Text
i just learned how to make gifs of live performances from ts files using vapoursynth and i’m a changed person now
#like woah the quality is just incomparable ??#can’t believe I made those shitty stage gifs before I’m sorry y’all#now idk if I fix them or post new ones ?? the thing is that if I fix them they would only be fixed on my blog#so people would have to rb again anyway..#so yeah I think I’m gonna delete those and remake them#gonna try finding those ts files on 4sashi later#my problem with vapoursynth is that the sharpening and denoising aren’t working for me#so I’m only using it to deinterlace ts files#so unless i figure it out my non stage and fancam gifs will stay the same#but idk I’m already happy with making better stage ones#at least for now lol#laura talks
5 notes
·
View notes
Photo
curly haired pure boy on music bank ♡
1K notes
·
View notes
Note
How do you make your gifs? I really like the way you do it and would love to learn how
Hello nony! It really makes my heart warm that you like the way I gif, thank you so much! I'll make a detailed step by step of how I do things and give you the download links and other tutorials because learning it from scratch is usually a long process:
1. Vapoursynth
windows (if you have a x64 windows, I’d advise downloading the VapourSynth64Portable(200616), I just tried that one and the folders look like the tutorial video, I’m not sure about the others.)
macOS
Tutorial:
You have these two icons on your desktop. You drag the file you want to gif on top of the one on the right, the one written vapourscript. It's going to pop up a window like this:
Then you write down when your footage starts, say, 00:00:07. Click enter and then you write down for how long it lasts, say, five seconds = 00:00:05. So when it starts > enter > for how long it lasts > enter. VS will do its own thing and you just have to wait until it pops out this window in your browser:
You can resize the footage however you like.
(Photoshop dimensions:
1 column set: width of 540 px
2 column set: width of 268 px
3 column set: width of 177 px)
For denoise + sharpening I use this:
Just check out both and leave it like that. I hardly ever make either setting stronger than the minimum. If it is a TS file I also check out the qtgmc 30 fast on the Preprocessor option. If it is not a TS file and just a normal video, I don't click on it.
Then you copy everything from here
And you paste it on the other VS window that will pop out here:
I just ignore everything else. I have no idea what the coding actually says.
Now we go to:
2. Photoshop
download here *blows a kiss for @woodzm my fave lil hacker*
download from the torrent one, you’ll need the file that already have all the parts. do not download the parts separately.
Tutorial
After you open photoshop, click ctrl + O to open a file. This is the pathway to the folder where my files from vs are (pay attention to the top in case you need to go through your folders) (vs > vs > gifs > output)
The next steps will depend on what you like / how your original footage looks like.
A) Blur > Gaussian Blur. Either 1,0 or 1,5. Set opacity to 30 - 40%
B) Sharpen > Smart sharpen.
Red is fixed. Green you decide what you think looks better.
You can add A + B together or use one or the other. To know what will help requires practice, so gif a lot!
Now this is a personal preference, I can’t gif in another way:
Convert Frames > Flatten frames into Clips.
Click on those three little squares on the bottom left, and then Make Frames From Layers from the same place you clicked to Convert Frames just a step prior.
Delete the first frame and whatever else doesn’t fit the gif you’re making.
This is when I color, which is personal and it depends a lot on what I’m dealing with. If you want I can make a tutorial of tools I like later, but my advice is to find something that suits you. This is the most fun part!
Now the last touches are:
C) Select all frames and set their timing to 0,07. I always use this one, it’s neither rushed or slowed down.
Save it with ctrl + shift + alt + s (at the same time).
And you’re done! (E tá pronto o sorvetinho :D)
Now for more info:
This post by @chawoongs with everything you need to know regarding TS files and where to get them *blows a kith for my fave boy*
These two alternative tutorials, which I did not read but might just be more coherent than my own: 1 and 2
I’ve done this for a while so things are mechanic for me. If I jumped any step that is not clear to you don’t hesitate to send me a message!
#photoshop tutorial#answered#m: photoshop#anonymous#id never do smth so lenghty but flattery IS the way to my heart#i keep editing this as people point out difficulties w the links lmao
142 notes
·
View notes
Note
hiiii!!! i absolutely love your gifs, they always look so clean (despite tumblrs need to crönch images aha!). can i ask what your process is? :)
hi friend!! ahh thank you so much that means a lot <3
my process depends on what method i'm feeling like using on the day lol i gif using screencaps and i also gif importing my videos on photoshop. also this is the base of what any gif maker will tell you, but use always 1080p or higher res videos, anything below that will look terrible, but if you can't help it for lower res videos always go for small gifs.
i'm putting this under a read more bc i like to use images to explain things bc i'm a visual learner and i feel like i get my points across more if i use images lol anyway *chan voice* leeeeeeets go!
if i use screencaps, i use the program mpv.net to get them! here's a tutorial on how to set it up (this program runs on windows only iirc, for other platforms original mpv works, but i've heard of people liking potplayer or kmplayer for screencaps)
once i have my screencaps in a folder and everything i open photoshop and go to file->open and find the folder with the images and then click on the first image and then at the bottom of the window that opened i click on "Image Sequence". This makes your screencaps look like a "video without sound" when uploaded to photoshop
once you click open it's going to ask you for a time frame, usually i just click okay bc i fix it later but some people just like to pick whatever the video timeframe was, for skz videos their MVs are in 24fps, for other things like talkers or skz code they usually are in 30fps.
when you go OK it'll show u this
here i do the basics, like cropping to whatever size i want my gif to look like, photoshop will ask you if you want to convert what you cropped to a smart object, you click on YES and then for good measure click on the "never ask me again" box so you don't have to be giving it the okay every time you are cropping things.
for the other method what i do is going file->open and look up my clip of what i'm going to make, photoshop can open .mp4 files, .avi files, .mov files & some .ts files depending on how they were encoded
anyway, you open your video and photoshop will start loading it and once it does it will look exactly like the other method, take a look
here i crop it to whatever size i want my gif again, if my clip is waaaay too long i use the timeline to trim it (this also goes the same for the prev method!) for example i wanted my jeonghan gif to be only this part of a longer clip so i moved the red line to look up where it started and where it finished and moved the both grey rectangle thingies that are at the start and end of the timeline to the points i wanted, like this
and to make my gif i use a photoshop action that i recorded myself with my process, if you want it just lmk! but what it does is basically edit some things like taking it out of that video group
duplicate the layer so it doesn't have these annoying white lines on the sides that are from cropping close to the borders of the video, and then sharpen it so it can look more "clean" and then flatten everything so i work in the frames timeline. This is where i fix my gif speed.
my sharpen is very basic lol i use an older photoshop (bc i just like the way it works jhfdjhfd) so my settings look like this
on newer versions you need to have the "Legacy" option enabled to use it like this
sometimes i go for 500, 0.4 if i want it sharper but for 4k mvs with that setting you're more than good. for other content i try not to overdo my sharpen bc it looks like shit so yeah, just mess around with what you find looks best tbh
after that i do my coloring, i make everything from scratch bc i don't like to use psds and i like to make my gifs very.... vibrant lol my go to layers are curves, levels,vibrance & selective color to fix up things! if i want to make edits with a color with more accent i use selective colors or hue/sat
and then to finish up i go to file ->save for web (in newer versions of photoshop this option is in export!) and these are my settings
and thats it! thats whatever i do that gives me okay looking gifs lol
if you want to check out other basics i made a very simple guide on my main blog HERE it's kinda a bit old so maybe a couple links won't work but nothing google can't give you a result for if you search :P
5 notes
·
View notes
Text
Descendants of Madness
Disclaimer: Nobody belongs to me. Which really sucks.
Spoilers: TS - S2 (try to contain your shock); S&H – Bloodbath, Sweet Revenge
Rating: PG-13
Warnings: Don’t run with scissors.
Descendants of Madness
By Gayle Smith
May 30, 1998
Vacaville, CA
“Si-mon.”
“Si-mon.”
“Si-mon.”
“Si-mon.”
Chanting filled the air as a shadowy figure stepped out of the prison transport and raised his shackled hands before him in triumph. “I dreamed this day and it has come to pass. I dreamed that my children would come for me, so that I might walk, unfettered once more, through the unclean cities of the fallen ones and gather the chosen before me.”
His hand drifted down to touch the head of the supplicant kneeling before him, blank eyes reaching up to meet his. “I dreamed you’d come.”
The young man, his guard’s uniform covered in the blood of a man he’d once called friend, dropped down to press a kiss to manacled foot in front of him. “Si-mon. Si-mon. Si-mon. Si-mon.”
* * *
The harsh jangle of the phone disturbed the peaceful silence of the room, bringing a weary sigh from the lone occupant of the bed and seconds later a hand fumbled across the night stand until its fumbling fingers closed around the handset before disappearing beneath the blankets again.
“‘lo?” A sleep-tinged voice answered, “‘s’it? Yeah. What?” The curly-haired figured shot up in bed, throwing back the covers and reaching for the crumbled pair of jeans on the floor. “How the hell did that happen? Damn it. When? Has anyone told Hutch? No, I’ll call him, then I’ll meet you down at the precinct. And I wanna see everything you’ve got on this. Everything.”
* * *
June 3, 1989
Boston, MA
Laughter followed Debbie Foster as she crossed her office and called out to her companion. “Just give me five minutes to print that file and I’ll meet you in the board room.”
Stepping across the room, she leaned over her computer and quickly brought up the document in question, sending it to the printer. Eyes still on the printer, she reached over idly to pick up the ringing phone and bring it to her ear, “Hello?”
“Darkness falls, Simon calls.” A sibilant voice whispered in her ear.
Papers drifted to the floor, falling from fingers gone slack. Every trace of emotion was wiped from her face.
“Si-mon.”
“The time of ascension draws near. Are you ready my child?”
“Yes, father.”
* * *
June 10, 1998
Somewhere in California
“Pick up the phone. Please, Gail, pick up the phone.” She clung desperately to the phone, her gaze moving nervously between it and the door. “Please, please.” A sigh of relief escaped her lips as the connection was made, “Gail, thank God I...”
... awfully busy, but if you leave a number, I’ll be sure to get back to you as soon as I return.” A light, breezy voice informed her.
“Gail, oh please, you have to get this message. It’s very important. Simon is free. He’s coming for the children, you have to warn...”
“I dreamed you’d betray me.”
The soft voice froze the blood in her veins and she turned toward it in mute denial, shaking her head as she stumbled backward.
“Yes, my child, I dreamed of this.” Simon Marcus reached out to touch her face, cupping it lovingly between his palms. “All of this. And then, I dreamed your death.”
“No! Please, Simon, please, no.” She fell to her knees in front of him. “Please, I wasn’t betraying you. I swear. I was just trying to find him for you. I swear.”
“There is no need for untruths between us.” He bent down before her, brushing her hair away from her face. “Simon knows the truth. He dreamed it.” With one swift motion, Simon snapped her neck, watching serenely as she fell bonelessly to the floor. “I dreamed your death.”
* * *
June 13, 1989
Cascade, WA
“Get a move on, Chief.” Detective James Ellison bellowed at his partner as he checked his watch again. “Sandburg, what the hell’s taking so long?”
“I’m coming, I’m coming.” Blair mumbled around the leather tie in his mouth as he smoothed his hair back into a ponytail, “Geez, Jim, what’s the hurry? Simon’s not expecting us to be at the station for another hour.”
“I know, but I want to try and get some of the paperwork cleared off my desk before those bozos from the Federal Task force arrive.” Jim motioned toward the open door. “Which I’m not going to do if you don’t get the lead out.”
“Get the lead out of what?” A cheery voice and the scent of sage brushed past Jim as Naomi Sandburg swept through the open doorway. “Blair, darling.” She enfolded her son in an embrace.
“Mom? What are you doing here?” Blair’s arms tightened around her as he returned the hug. “When did you get here?”
“I came straight from the airport, sweetie.” Naomi held Blair at arms length and ran a mother’s eye over him. “You look tired, are you getting enough rest?”
“I’m fine, Naomi.” Blair smiled indulgently at her, “And you still haven’t told me what you’re doing here.”
“Oh, that’s quite simple, I’ve come to kidnap you,” Naomi responded brightly.
“Kidnap?” Blair backed away warily. “Mom, what are you up to?”
“Just trying to spend some quality time with my favorite son.” Naomi linked her arm though Blair’s and led him to the couch. “I thought it might be nice if we spent a little time together. I know the semester is over and I’m sure that Jim can spare you for a few days. Isn’t that right, Jim?” Naomi turned and fixed her bewitching gaze on Jim. “What do you say?”
“I... ah...” Jim looked between the two figures on the couch, from Naomi’s steady gaze to Blair’s beseeching one. Noticing the slight shake of his partner’s head, a grin crept across his features. “You know, Naomi, I think that sounds like a wonderful idea. Our little Blair’s been burning the candle at both ends trying to get through finals and help me out at the station. I think some time away to relax is just what the doctor ordered.”
“But, Jim, man, don’t you need me down at the station?” Blair’s voice held a barely checked note of desperation. “What about that meeting with the Feds? Simon was expecting both of us for that.”
“I’m sure Simon will understand, Chief,” Jim replied with a good-natured grin. “He knows how much extra time you’ve been putting in. Go on, go with your mom. Commune with nature, eat granola, meditate. I’ll be sure to save you all of the really exciting paperwork.”
“Gee, thanks, man.” Blair frowned across the space at his roommate. “No, really, man, I mean that. Sincerely.”
“Oh, come on, honey, is it really going to be so bad spending a few days alone with your mother?” Naomi reached out a hand to brush a stray lock of hair from his face.
“No, mom, of course not.” Blair reached up to capture her hand and held it. “All right, I surrender. What should I pack?”
“A little of everything,” Naomi replied mysteriously.
Stopped in the doorway to take a final jab at his friend, Jim thought he saw something akin to relief cross Naomi’s face at Blair’s capitulation. He opened his mouth for a moment, intending to ask her if something was wrong just as she turned to him with a graceful smile and quietly waved him out the door.
* * *
“Ellison!”
Jim winced as hot coffee splashed across his hand and turned to face his commanding officer. “Yes, sir?”
“Where’s Sandburg?” Simon’s gaze traveled anxiously over Jim’s shoulder to the space usually occupied by his partner.
“Probably still back at the loft, why?” Jim’s focus shifted to the two men waiting in Simon’s office. Both in their mid-50’s, neither had the look Jim had come to typically expect of Feds. The slight arrogance that seemed to surround most of their brethren was missing and both wore an air of weariness that Jim associated with cops who had spent too many years on the streets.
“What do you mean ‘back at the loft’?” Simon snapped. “I told you both to be in my office at 8:00 to meet with the representatives from the task force.”
“I know, sir, but Naomi turned up just as we were leaving.” Jim’s attention shifted back to Simon, a faint alarm going off in the back of his head over his captain’s reaction. Simon had said nothing to indicate that Blair’s presence was required at the meeting. “She wants spend a few days with Sandburg and with the way the kid’s been running himself ragged between school and working at the station, I figured he could use the down time. Why? Simon, what’s going on?”
“Jim, you’d better come in here.” Simon motioned for Jim to join him in his office, closing his door on the curious glances of their co-workers.
* * *
“Blair, sweetie, you don’t have to pack everything you own,” Naomi chastised lovingly from the doorway of his room.
“I’m not packing everything, I’m just... You know, this would be a lot easier if you told me where we were going.”
“And ruin the surprise?” Naomi’s bright laughter filled the room. “Where’s that sense of adventure I always loved about you, my darling?”
“Probably somewhere in this bag.” Blair gestured to the large duffel covering half his futon. “But if you tell me what the emergency is and why you’re in such a hurry to get me out of here, maybe I’ll be able to drag it out.” Shoving the duffel aside and plopping down on the bed, Blair reached a hand out to his mother and pulled her closer. “Naomi, what’s wrong? What are you running from?”
“Nothing, sweetie, nothing at all.” Naomi drew him into her arms. “I’ve got everything I could ever need right here.”
“Mom, please, I can tell you’re upset about something.” Blair rubbed soothing circles across his mother’s back as she clung to him. “What is it? Please, tell me. You’re not...” Naomi saw the sudden fear shining in Blair’s eyes. “Are you sick?”
“No. No, Blair, love, I’m not sick. It’s nothing, nothing important.” Naomi stood and brushed away the tears that had suddenly sprung to her eyes. “Let’s just take what you have and go, Blair. We can get whatever we need when we get there. Please.”
“Okay, mom,” Blair hefted the bag over his shoulder, “but when we get there I expect you tell me what’s going on. Everything.”
“Anything you want, Blair, just hurry.” Naomi started for the door, her eyes widening fearfully as the phone began to ring. “NO! Blair, leave it.”
“Mom, it could be Jim.” Blair dropped the bag and reached for the phone.
“Sweetie, no!” Naomi’s hand closed over Blair’s. “Jim knows we’re leaving, and I know you, if that’s one of your friends from the University we could be here all day. Just let the machine get it. Please. For me.”
“All right,” Blair shouldered the bag again and followed Naomi out the door. “But you’re going to tell me what’s going on, mom.”
“Of course, sweetie. Of course.”
* * *
Jim paid little attention to the phone ringing in his ear, instead fixing his attention on the files scattered across Simon's desk and the worried frowns surrounding him. The topmost folder was open, revealing bloody crime scene photos of a man in prison guard's uniform with half his face blown away and the inside of a prison transport drenched in blood.
Fixated on the dark red patterns and the tension radiating off the other men in the room, Jim nearly jumped out of his skin as his own voice greeted him from inside the phone. 'Shit. Answering machine.' Jim silently berated himself and took a deep breath, trying to push aside his feeling of impending doom. "Sandburg, are you there? Chief, if you're there pick up the phone. Okay, listen, I want you to call me as soon as you get this. If you come back to the loft don't leave before you call me, got that?"
Hanging up the phone, he looked up to find three pairs of eyes watching him. "He must've left with Naomi already."
"Who's Naomi?" One of the Feds, the angry looking blond one Simon had called Hutchinson, barked at Jim.
"His mother. What the hell is it to you?" Jim returned the man's icy glare. "And what do you want with my partner?"
"Hutch, calm down." The second man, his short, dark curls just beginning to turn gray, reached out and placed a calming hand on his partner's arm before fixing his gazing on Jim. "Do you have any idea where they went?"
"No. Naomi just that said she wanted Blair to come away with her for a few days." Anxiety crept through Jim's gut again. There was something seriously wrong with this situation. Why the hell were these people so damn anxious to find his partner?
"Simon, what the hell's going on here? Who are they?" Jim jabbed an angry finger in the direction of the visiting Feds. "And what the hell do they want with Sandburg? Is Blair in some kind of trouble?"
"Jim, I think you'd better have a seat." Simon motioned to the table behind them and started gathering the files off his desk.
"I don't want to have a seat, Sir." Jim replied coldly, his jaw tensing. "I want to know what the hell is going on."
"Jim, please..."
"Captain Banks," the dark-haired one cleared his throat uncomfortably, "I think you'd better get an APB out on Sandburg."
"You think I don't know that?" Simon snapped at the man before taking a deep breath and continuing. "Captain Starsky, please, just take the files and wait with your partner at the table. Give me a minute to make that call and have a word with my detective."
"We're wasting time here." Hutch hissed toward his partner. "I told you that we should've just headed straight to the apartment and picked up the kid."
"Hutch, drop it." Starsky curled a hand around Hutch’s forearm and directed him to the table. “These people know what they’re doing, let’s give them a chance to do it.”
“All right, I just...” a weary sigh escaped him and Hutch closed his eyes.
“I know, babe, I know.” Starsky turned back and scooped the files off of Simon’s desk, meeting Jim’s hard stare. “While your captain’s taking care of that, there’s somethin’ I think you should see.”
Jim studied the man closely, once again gaining the impression that he and his partner were something more or less than the harried Federal agents he’d been expecting. “Unless it’s going to tell me why you’re so interested in my partner, I don’t care.”
“It is. Please.” He gestured toward the table. “All I’m askin’ for is five minutes, detective. Just listen to what we’ve gotta tell you and then decide from there.”
Jim sat stiffly across from them, suddenly not sure if he wanted to know what those bloody pictures had to do with his friend. As he watched, Starsky reached to pull the second file from the pile and flipped it open in front of him. Inside, a man of approximately 30 stared up at Jim, his guard’s uniform neatly pressed, his warm brown eyes holding a glint of humor.
“Joseph Spinelli, a prison guard for the last 8 years. He and his partner and another guard were transferring a felon from Folsom prison to San Quentin thirteen days ago.” Jim tried to place the note in Starsky’s voice as he recited this information. Sadness? Regret? “Their prison transport was found abandoned at the side of the road, their prisoner gone and both Spinelli’s partner and the other guard murdered. No sign of Spinelli.”
“What do a dirty guard and a prison break in California have to do with my...”
“Debbie Foster,” Hutchinson intoned as he tossed the next folder down in front of Jim, “twenty-eight year-old investment banker in New York. Walked into her office ten days ago to get a file and hasn’t been seen since.” He sorted through the files, “Jennifer Santo, disappeared from her home, leaving her five month old daughter behind, that same day. George Murphy, disappeared off a construction site. Tyler Parker. David Rhodes. Kathy Wilder.” His voice rising as he slapped each file down, until a hand land softly on top of his, stilling it.
“What the hell have any of these people have to do with Sandburg?” Jim snapped, what little patience he had wearing thin. “Damn it, Simon, what’s going on?”
“Twenty-two years ago,” Starsky leaned back in his chair, his voice so low that Jim had nudge his hearing up to catch it. “Hutch and I were part of the investigation that brought down Simon Marcus...”
“Simon Marcus? The cult leader?” Jim found himself straining forward, vague memories of news stories flashing through his memory.
The knot in Starsky’s stomach tightened as he remembered the horrors they’d found. “We were the arresting officers...” his voice faltered and he exchanged a glance with his friend that bespoke shared pain. “God, I can’t even begin to tell you what we found there. After all this time, everything we’ve seen as cops, none of it, not one other unholy terror compares to what they were doing. We managed to arrest Marcus and most of the cult’s hierarchy, or so we thought. It took eight months to bring Marcus to trial and convict him of the nine murders we could pin on him.”
A sudden spike in Hutchinson’s pulse drew Jim’s attention. The man was reaching for a glass of water with a hand that trembled so slightly that even with his senses Jim could almost believe it was his imagination.
“Hutch?” Somehow, Starsky had picked up on his partner’s discomfort as well, reaching out to gently touch Hutch’s back. The move was so intimate and familiar that for a moment Jim found himself leaning into the comforting hand that was always there when he needed it, turning to catch the smile that always offered support and encouragement, only to find it missing. A chill worked its way down his spine as he turned back to the two men.
“Yeah, yeah, I’m fine.” The blond man waved off his friend’s concern and turned to Jim. “At the sentencing hearing, they got into the court house somehow and...” there it was again, that telltale spike in his pulse, a slight increase in respiration, “they kidnapped Starsky. Right from under our noses, right from under my nose.”
“Hutch.” This time the warm voice held a note that was at once both supportive and concerned. “Don’t.”
“Yeah, I know,” in a tone that clearly said he didn’t agree, “it wasn’t my fault.” Brushing a hand across his face, he looked up at Jim and continued, “When we found Starsky, the remaining cult leaders were holding him at an old, closed down city zoo. But that wasn’t all we found. God, if we’d had any idea...” his voice cracked, “they were holding children there. Thirteen children. Jesus Christ, you’d think that someone, some parent, grandparent, teacher, someone would’ve reported at least one of those children missing. The things they did to those children...”
Starsky reached to pull Joseph Spinelli’s folder out and dropped it on top again. When he finished flipping through the first few pages, Jim found himself looking down at a picture a thin, young boy. Hunched in the corner of what appeared to be a cave, he was covered in filth, welts and bruises showing through the torn clothes he wore.
“Damn,” Jim swore softly, his heart breaking for the scared little boy in the pictures, “Spinelli was one of the kids?” The implication suddenly hitting him as he looked sharply at the two shrewd pairs of eyes studying him. “All of them?”
“Yes.” Hutchinson turned the pages in the next file. “It took over a week for anyone to make the connection between the disappearances and Marcus’ escape.”
Feeling the cold dread that had started in the pit of his stomach spreading outward, Jim looked to Simon for support, afraid to ask the next question, not wanting to hear the answer he already knew. The brief flash of pain in Simon’s eyes before he turned away only confirmed Jim’s fear. Taking a deep breath, he asked. “Blair?”
No words were spoken as another file was pushed in front of him. Worn, the edges tattered with age and use, the precise letters, written in neat block, everything about the file seemed to mock Jim, daring him to open it. In that moment, he found he couldn’t. Couldn’t open it. Couldn’t look at the words, the pictures. Couldn’t know that once, somewhere, someone had hurt Blair and that he’d been unable to stop it, hadn’t even known about it.
But he had to look, didn’t he? Had to know if he was going to help, going to stop this from happening again. And so, slowly, so slowly that no one could tell it contained the same slight tremble that Hutchinson’s had just moments before, Jim’s hand reached out and touched the file. He ran his finger across the faded letters: Sandburg, Blair, as though it that could somehow change them. Then, with an almost too casual flick of his wrist, he opened it.
Jim slammed his eyes shut at the sight that greeted him, as if those thin tissues of flesh could block out the image that was now seared into his brain. The sight of that achingly tiny boy, curled into a corner, his small body covered in bruises, painfully thin ribs straining against flesh that seemed stretched to the breaking point. And then there were the eyes, eyes that Jim recognized despite the intervening years and the changes they’d brought, eyes that should be full of joy and wonder, but only held fear.
“Simon, I...” Jim closed his eyes against a flood of emotion, anger and fear warring with pain and regret. “I can’t look at this.”
“Jim, I’m sorry.” The compassion in Simon’s eyes almost undid him. “I should’ve warned you. But...” he shook his head and turned away, whispering softly. “But how do you prepare someone for that.”
A hand gripped his arm, squeezing briefly before pulling away. “Me too. I should’ve taken the pictures out. You didn’t need to see that, no one does.” Starsky shuffled through the file, removing a handful of photographs before handing it back to Jim. “Just the reports, if you think...” He left the thought unfinished, shrugging apologetically.
Trying hard to check his emotions, Jim opened the file again, running a professional eye over the reports, ignoring the pang in his heart at what each methodically catalogued injury had meant to the little boy who was now his best friend. Closing it again, Jim pushed aside the nagging voice in the back of his head proclaimed there was something missing and focused his attention on Starsky and Hutchinson.
“Why?”
The question, spoken coldly, startled the two visitors and they exchanged a quick glance before Hutch asked. “Why what?”
“Why help him? After everything Marcus and his followers did to these kids, why would Spinelli help him escape?” Catching the silent, almost imperceptible signals that flew between them, Jim knew there was still more they hadn’t told him. “And why kidnap the other children? What could they possibly want from them after all this time”
Hutchinson’s pulse spike again and Jim turned a laser fine glare on him. “What aren’t you telling me?”
“They weren’t kidnapped, at least not that we can prove.” Hutch slumped back in his chair, rubbing his face. “At least one we know wasn’t. Kristine Anderson was in protective custody in Portland, three days ago she answered the phone and then hit the officer guarding her over the head with a lamp and disappeared.”
“So, she was in on it, went voluntarily?”
“No, she was... From every report, the girl was terrified when the Portland PD picked her up and told her what was going on. She didn’t remember a lot of what happened, but enough to be scared. She went into protective custody willingly, gladly.” Hutch shifted uncomfortably, making eye contact once more. “Until she got that phone call, a phone call that shouldn’t have gotten through to her at the safe house, she was fine. Afterward... the officer said that she just went blank, like there was no one there.”
“What the hell does that mean?” Jim’s heart raced at the implications. “Are you saying that they got to her? With just a phone call?”
“We’re not sure what happened, but...” Starsky heaved a sigh, once again breaking eye contact. “Given the sudden circumstances behind each disappearance, the out-of-character nature of them... We know that the cult was usin’ brainwashing techniques back in the 70s and the docs figure that maybe the kids are reacting to some kind of post-hypnotic suggestion that was planted in ‘em before they were rescued. Now all they gotta do is call the kids and activate it.”
“Naomi.” Jim suddenly sat upright in his chair. “I knew something was wrong with her. She was too desperate to get Blair out there this morning. She has to know that something’s wrong.”
“You think she’s trying to protect Sandburg?” Simon asked dubiously.
“If she is, it’s about time.” Jim’s eyes narrowed angrily as they drifted over the folder bearing Blair’s name. “How the hell could she let this happen, Simon? I know she wasn’t the most attentive mother, but for Christ’s sake, how the hell could she let something like this happen to her own child?”
“I don’t know, Jim.” Pain shone in Simon’s eyes as he thought of his own son. “I can’t even begin to imagine.”
“Wait a second.” Hutch snapped his fingers and reached across the table for the file. “Who did you say this Naomi person was?”
“Blair’s mother.” Jim replied, scorn coloring his tone.
“Not according to this.” Hutch flipped quickly through the pages, pointing insistently at the relevant line.
“What are you talking about?” Jim snatched the file back, quickly scanning the page.
Name: Blair Alonso Sandburg
DOB: 12/27/69 Age: 7
Father: Unknown
Mother: Deceased
* * *
“Blair, honey, you’re not eating.” Naomi reproved gently, cupping a hand over her son’s. “What’s wrong?”
“Besides the fact that you won’t tell me what’s going on?” Blair turned his hand over to grasp hers. “Mom, I know there’s something more going on here than just your wanting to spend time with me. What is it?”
“Yes, sweetie, you’re right, there is.” Extracting her hand, she reached up and tucked a strand of hair behind his ear before patting his cheek. “But I’ve already promised to tell you all about it once we get to the cabin. I’m talking about whatever else is bothering you. Blair, sweetie, I can tell just by looking at you that something is wrong. You’re too pale, too thin. What’s wrong?”
Blair looked at his mother for a long moment, as if weighing his choices, before shrugging and pulling away. “There was an accident, a few weeks ago. I guess I’m still recovering.”
“Accident?” Naomi’s breath caught in her throat as she looked at her son’s pallid features. “What kind of accident? Does this have something to do with your work with Jim?”
“Yes, no... Not exactly. It’s a long story, Mom. And I don’t think I’m really ready to talk about it yet.” He shifted uncomfortably and recaptured her hand. “The short version is Jim and I kind of had a falling out and we both said and did somethings we shouldn’t have and we’re still trying to put the friendship back together.”
“Oh, sweetie, I know how much Jim means to you,” Naomi squeezed the hand holding hers and sighed. “My timing couldn’t have been worse, could it?”
“Actually, Mom,” Blair laughed gently. “It could’ve been a lot worse considering. Besides, maybe some time apart will do us good. It seems like lately we’re either trying too hard to act normal or sniping at each other.” He shrugged, suddenly uncomfortable with the turn their conversation had taken, and pushed his plate away. “What do you say we get this circus on the road again?”
“You’re right,” she sighed, gathering her belongs. “We still have a long drive in front of us. I’m just going to freshen up.”
“Okay, I’ll meet you at the car.” Blair dropped some money on the table and started to slip his wallet in his back pocket before catching sight of a payphone across the restaurant. Pulling out his calling card, he walked over and quickly dialed.
* * *
“Major Crime.”
“Hey, Henri, is Jim around?”
“Hairboy, is that you?” Henri swiveled around in his chair, grinning gleefully at the sight of Jim shut away in Captain Banks’ office with the two federal task force agents. “Man, Simon was not happy to find out you’d skipped on this morning’s meeting.”
“Damn. I was afraid that would happen. How loud was he?”
“Loud,” Henri confirmed. “Just about took Ellison’s head off.”
“Maybe I should head back...”
“Where are you?”
“A restaurant outside of Yelm.”
“What’re you doing all the way out there?” Henri asked, rifling around in his top desk drawer.
“That’s a good question.”
“What?”
“Going... somewhere with my mom. She showed up on our doorstep this morning to whisk me away for a long weekend of family bonding.”
Henri burst into laughter. “I’ve seen your mother, Hairboy, I’d take a weekend in the mountains with her over a meeting between Jim and the Feds any day.”
“Henri, man, she’s my mom. Cut that out. Listen, can you tell Jim I called? I’ll let him know where we’re headed as soon as I know.”
“You got it Sandburg, later man.”
* * *
Jim stepped out of Simon’s office, pale and grim, making his way, woodenly, over to his desk and sat down heavily.
God damn, he needed a drink. Jim couldn’t remember the last time he’d needed a drink to numb the horrors his job brought to him everyday, probably the night Lila died, but he’d just about sell his soul for one right now. For anything to help dim the memory of that thin, battered little boy and eyes that should only know joy filled with terror.
“Jim? Man, are you all right?” Henri stood next to his desk, brow furled in concern. “I called you a couple times.”
“Sorry, H. Yeah, I’m fine, just...” He waved a hand in the direction of Simon’s office. “This case is...” He trailed off, staring back through the blinds at Starsky and Hutchinson sorting through the files.
“Ugly, huh?” When Jim nodded, Henri just shook his head. “Sorry, man. Listen, I just wanted to pass on a message from Sandburg. He wanted you to know—”
“Sandburg?” Jim’s head whipped around audibly and he glared up at Brown. “When did you talk to Sandburg?”
“About twenty minutes ago,” Henri replied, puzzled by the sudden change in Ellison’s attitude. “You were in with Captain Banks and the Feds.”
“And you didn’t call me?” Jim snapped, rising angrily to his feet.
“I told you, you were in with Simon and—”
“I don’t give a damn who the hell I was in with, you should’ve called me.” Jim’s voice rose with every word.
“Ellison.” Simon stood in the doorway of his office, glaring at his detective. “What’s the problem?”
“Brown talked to Sandburg,” Jim ground out, pointing an accusatory finger at the other detective.
“You what? When?” Simon demanded, the two Feds crowding in behind him.
“I talked to Hairboy on the phone. What’s going on, Captain?” Henri asked, concerned. “Why the big deal about my talking to Sandburg?”
“Did he say where he was?” Starsky pushed in between Jim and Simon.
“Yeah, he said he was in a restaurant outside of Yelm.” Henri answered. “Simon, is Sandburg in some kind of trouble?”
“We think he might be. Did he give you any idea where he was headed?”
“No.” Henri shook his head in disgust. “He only said to let Jim know he called and that he’d try and call back when he knew where they were going. Jim, man, I’m sorry. If I’d had any idea that Sandburg was in some kind of trouble, I’d’ve got you right away.”
“I know. I shouldn’t have come down on you like that,” Jim admitted apologetically. “I’m just worried.”
“Yeah, I hear you, man.”
“Okay, listen up people, I don’t want to have to repeat myself.” Simon stood in the middle of the bullpen, making sure he had everyone’s attention before continuing. “If anyone here takes a call from Sandburg, you find Detective Ellison or myself right away. Do not let him off the line. Is everyone clear on that?” A round of affirmations followed.
“Captain Banks, this Yelm, where is it?” Hutch asked, following his partner and Jim back into the captain’s office.
“It’s a small town about thirty, thirty-five miles South of here.” Simon pulled a map out of his filing cabinet and spread it out on the conference table. “Here.” He pointed to the spot on the map.
“Is there anywhere around there that you think they could be going?” Starsky traced the line of freeways leading to the small dot on the map.
“Nothing I can remember Sandburg bringing up,” Jim replied thoughtfully. “And from there they could be headed to any number of out of the way places. Up into the mountains to one of the National Parks or Forests, back over to I-5 and down into Oregon. Damn it, I should’ve stuck around and found out where Naomi was taking him.”
“She probably wouldn’t have told you the truth anyway,” Hutch pointed out, turning to his partner. “What now?”
“That’s up to Captain Banks.” Starsky watched him expectantly. “We’d like to stick around here, with your permission. Sandburg’s the last kid on the list, and the hardest to track down. I think if we’re going to have any hope of findin’ the others or Marcus it’s gonna be through him.”
“We’d appreciate any support you’d be willing to lend us.” Simon replied, glancing significantly at his detective. “Have you checked into your hotel yet?”
“No, we were kind of hoping we wouldn’t be staying that long,” Hutch sighed, rubbing long fingers across his forehead. “We should probably check in with the task force, too, let Franks know what’s going down.”
“Ellison, forward your calls to your cell and tell Brown I want to see him in here. I want him to get started calling all of the contacts listed for Naomi in Sandburg’s personnel file. Then make sure Captains Starsky and Hutchinson get settled in their hotels,” Simon directed. “When you get back we’ll see what Brown’s come up with and see if you can come up with anyone else Sandburg’s mentioned.”
“Contact list?” Hutchinson questioned. “You don’t just have a number you can call?”
“For Naomi?” Simon snorted softly. “Not hardly. Ms. Sandburg travels quite frequently, usually to some of the more exotic locations. It makes it hard to find her when she isn’t trying to get lost.”
“Makes her hard to find when her son needs her,” Jim muttered angrily.
“Jim,” Simon warned softly.
“Whatever,” Jim replied curtly. “I’m going to go transfer my calls, let me know when you’re ready.”
“Mind if I ask what that was about?” Starsky motioned toward the closed door.
“We’ve had a hard time getting a hold of Ms. Sandburg after Blair was injured on a couple of cases,” Simon explained. “Detective Ellison is a bit... protective of his partner, he tends to take things like that a little more personally than Sandburg does.”
“I can understand that,” Hutch said softly, exchanging a knowing glance with his partner.
* * *
Jim leaned against the wall outside of the hotel room Starsky and Hutchinson had just checked into, carefully extending his hearing to pick up on their conversation.
“What do you think of Ellison?”
“Seems like a good cop to me. Really seems to care about the kid.”
“You don’t think it’s a little strange that a former army ranger just happened to lose his partner at a time like this?”
“Hey, you lost me once in a crowded court house.”
“That’s not funny, Starsk!”
“I know, Babe, but it’s true. I think Ellison’s a good cop who got caught in somethin’ none of us are prepared to deal with and I think you’re takin’ too much responsibility for this on yourself.”
* * *
Hutch jumped from the car, not even taking time to turn off the ignition, and scrambled around the front, his long legs eating up the distance between him and the horrifying tableau in front of him. With each hurried, pounding step, he could hear the sounds in front of him, the low fervent chanting of the cult members, grow louder, “Si-mon. Si-mon. Si-mon,” filling his ears.
But it wasn’t the black robed figured that transfixed him, driving a cold spike of terror through his chest. It was a tiny slip of girl, red hair flowing across her simple white gown, and the knife she held above her head, poised to strike. It was the sight of his partner, his best friend, helpless before her, his hands tethered over his head.
Even as he ran, legs pumping, lungs straining, he knew he was going to be too late. Too late. Too late. And then, there it was, the flash of morning sun against the blade as it moved in an inexorable arc, down, down, until the only sounds he heard were the soft sickening squelch of the blade sinking into vulnerable flesh and Starsky’s cry of pain.
“NO!” The cry was torn from his throat as Starsky lifted eyes already fogged with pain to meet his. A brief second of recognition passed between them before those eyes slid silently, permanently, shut. “NO! STARSKY! NOOOOOOOOO!”
"Hutch! Hutch!" Strong arms wrapped around the sobbing man, holding him close, "Hutch, come on, babe, wake up. It's just a nightmare, that's all," Starsky soothed, rocking his partner gently as he held him tight, "Just a nightmare. I'm right here."
“Starsk?” Hutch clutched weakly
Sentinel, Too – part 1: May 20 1998
New Moon – May 25th, 1998/June 24th, 1998/July 23rd,1998
Full Moon – June 10th, 1998/July 9th, 1998
Bloodbath - Judge Arlen B. Yager
2 notes
·
View notes
Text
My Gaslighting Manager told me I did NOT have a degree
I've been lurking here for a while and thought I'd share.
This will probably be a long one so my TL; DR: My gaslighting manager told my I didn't have a degree, was a terrible programmer and that HE could write a script I was assigned better and more efficiently so I quit the day he went on an international month and a half long vacation so he'd have to do it himself from paradise.
So some background, I'm an African American woman who's been working as a programmer for about a good 4 - 5 years now. This situation happened in my first job out of college about 5 years ago and I was employed there for six months. I got a degree in Computer Science and I had tried to work at a larger company but decided to work somewhere smaller and then work my way up to a big company.
I stumbled upon a job for a programmer at......let's call them Gaslight Business Solutions (GBS), and decided to apply. At the time I was living in the Bay Area and the job was in SoCal so after successful phone interviews I flew down for an in person interview. The interview went fine with the CEO and he mentioned that as a small company (8 people at the time) I'd have to wear multiple hats and at the time I was okay with that. The CEO then proceeds to tell me that I'd spend maybe about 25% of my time being a support rep for a software, let's call it TerribleSuite (TS) (a CRM and ERP software), and the other 75% of my time programming for it. Once again I was fine with that and so the CEO offered me employment and I accepted. He did warn me that I'd start off making something pretty low but my pay would become exponentially better after three months.
So a few days later I receive an email about the terms of my employment, my initial pay for three months was going to be 12.00 an hour and my overtime rate was 18.00 an hour. I was pissed, programmers make significantly more than that! But I justified it that they were a small company, and my pay was going to be exponentially better three months from my start date so I could hold off. I also noticed that my title wasn't "Programmer" but "Software Analyst" which I thought was strange but once again wrote it off.
On my very first day, THE VERY FIRST DAY AFTER AN HOUR OF BEING THERE, several employees asked me "Do you still like it here?" and "You're not going to quit right?" I thought it was strange but I explained that I liked working there and asked why they were asking that. They explained that another African American (I'm not sure why that was important to explain to me) programmer had quit on them after only a couple of days of working there so they just wanted to make sure I was happy. I assured them that I was happy and had no plans of leaving.
These questions proceeded for the next six weeks, every. single. day. MULTIPLE times a day. I thought it was strange behavior but at the time believed that it was because of what happened with the other employee so I didn't think much of it. The next red flag was when I had to go to a customer and my coworker told me within five minutes of driving to the customer he told me "I needed to get my experience and get out." I was flabbergasted, I asked him about it and he explained that I had better opportunities and I shouldn't waste my time at GBS. I was shaken by his words and I told him a bland okay.
For pretty much 90% of my time at GBS I spent most of my time was spent learning TerribleSuite and being a support rep, I did pretty much ZERO programming. I was pretty annoyed, I was promised I'd be doing programming and I began to lightly voice my displeasure about a month in, since that's what they wanted a programmer. My manager, "Jav", decided he'd "test" my programming skills so he gave me a script to rewrite. I was just to rewrite the code and not look into the different functions I could use or to research the functions that were used. As my first programming assignment I was on it and I was excited to finally be writing some code, even if it was just a rewrite. I rewrote the 200 lines of code down to under a hundred, sectioning off repeated code into separate functions so it made the code more legible.
I returned it to my manager within under a day and he called me into a code review. He proceeded to scold me about how I didn't really enhance the code and that I didn't really understand the methods being used. I told him that he told me not to and he just looked at me strangely but didn't say anything. Jav then told me that he could have written it better but he told me I did a good job. I took at a small win but I felt a little strange.
Now Jav knows how to program, he was self taught but made a LOT of mistakes. Some of them were very blatant and others were just subtle rushing mistakes. He'd sometimes forget a closing bracket or he'd forget to null check variables. Just mistakes every where, but typically the code would function, but it was pretty much always volatile.
So after that point my manager proceeded to make my life a living hell. He'd find any moment to critique my work. On multiple occasions he'd tell me I'd do something wrong and after I had apologized then tell me I had actually done it right. I was confused and I asked my fellow coworkers and they laughed it off as a quirk of Jav. He would assign me scripts and when I had questions he'd say he was too busy and schedule time to talk about it AFTER the script's due date. I'd see him goofing off in the conference room and if I'd ask him to help me he'd tell me he was going to be in call in a few minutes and was just letting off steam. I'd leave come back after about 10 - 20 minutes, and when I'd walk by the conference room he'd still be goofing off, or listening to music. If I did catch him at a time where he wasn't busy he'd dramatically sigh and reluctantly "help" out. His help sessions would go like this:
Me: Hi I need to access this sub object in TS but the help doc doesn't say what the api name is. Jav: You need to use a for loop to go through the object and then use a nested for to access the sub object.
Me: Yes, I'm aware of that but I don't have the name of the sub object's api name. Jav: You need to use a nested for. Me: Yes, I...
[He'd respond with one of the following] Jav: Well then you know what you're doing, I have a call/I'm busy so go upstairs and finish the script. Jav: You're just confused, go upstairs and think about it more. Jav: You don't understand the business process, go back upstairs and ask [coworker] about the business process.
For those of you non-programmers out there I'm basically asking for a key to access a room that's in another room and Jav is just confirming that the key exists but he's not telling me where the key is so I can access that room.
I always left these meetings extremely frustrated because he'd always tell me extremely basic computer science knowledge instead of what I really wanted to know. Jav also loved to spend time trying to tell me the "proper" ways to program. The longer I was there the more convinced he was that I actually faked my degree.
For those of you who don't know for loops are used to do an operation multiple times. Nested for loops with do multiple operations multiple times depending upon the number of nested loops. I write my nested loops like this, like a lot of programmers.
int n = 5; int m = 10; for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { // Do something } }
He would BADGER me on this saying that it was the incorrect way of doing a nested for loop and that I was an idiot for doing my nested loops this way. If I tried to submit any scripts with the code above he'd throw a fit and told me to rewrite it the "proper" way. This is the way he wanted me to write nested loops.
int i = 0; int j = 0; int n = 5; int m = 10; for(i =0; i < n; i++) { for(j = 0; j < m; j++) { // Do something } j = 0; }
While this isn't incorrect, when n and/or m is large, like in the millions, resetting j to zero that second time after the inner loop wastes a computers time and it's unnecessary because the first "j = 0" will reset it to zero. I had tried to explain that to him, but he'd throw a fit insisting that ALL CS degree programs write nested loops his way AND that my way did NOT reset the inner loop counter. He say I was confused and I didn't know what I was talking about and that I learned how to do nested loops WRONG.
After this he really began to up his game on criticizing my programming skills, began to lightly suggest that I actually didn't have a computer science degree and made me his personal punching bag when his code broke.
On several occasions, scripts would break on the production accounts of GBS's customers mostly due to tri-yearly updates to TS. When that would occur he'd waste no time lambasting me about how I can't just go into accounts, change code and that I'd need to go back and "put the code back to the way it was". TS kept a history of when files were changed so I'd look it up and shockingly the last time the file was touched was before my hire date. I'd tell him this and he'd respond with the following: "I KNOW you touched the file. My code is PERFECT. You must have hacked into TS and changed the code!"
(First off WTF, if I had those skills I wouldn't be working at their tiny company and secondly ???? who thinks that?!)
I'd have to fix his code, test it in the sandbox account (if a customer had one) and then redeploy it to get the customer up and working again. After I fixed it, Jav would continue to reiterate that I hacked TS and that if I had been good and left things alone the script would have worked just fine. Needless to say I was pissed and I had to hold my tongue because I wanted to tell him how much of a moron he was.
Jav had once given me a script for a customer where given a particular day in the month it would renew a contract for the next year. If the day was in the first half of the month, the contract renewal would be for that month for the next year, it was in the last half of the month, the contract renewal would be for the next month for the next year. For example a March 3rd 2019 the contract renewal would be for March 3rd 2020 and for the second option March 20th 2019 the contract renewal would be for April 20th 2020.
So I wrote the script but forgot that for the last half of December I needed to add an additional year because if I didn't the contract would only be good for a month.
I didn't discover this issue until I was demoing it for the customer. It was going to be an easy fix, three lines of code max and the customer was very forgiving, they were just happy that the script was done and how easy it was going to make their lives. I apologized profusely to the customer about the mistake, but they assured me it was okay because they were just so happy about the script in general. They still had to go through a couple more stages with their bosses so they didn't mind that I needed to add in the quick change. Jav. Was. PISSED. He goes off on me on the call with the customer telling me that I needed to double check my code to make sure that it was working properly and that I always present customers with working code instead of broken code that clearly doesn't work. (If I remember correctly I had demoed it for Jav and another coworker and Jav was very pleased with it, albeit him saying he could do it better). The customer was awkwardly silent on the call as Jav spent a good five minutes berating me and my work. He then addresses the customer with multiple apologies before we hang up the call. I get a call to come downstairs to talk to Jav and when I get down there he continues his tirade and ends the discussion with, "You're NOT a programmer and I don't think you have a degree in computer science and, to be honest, I'm not even sure why we even hired you."
I was furious and on that day I started to seriously look for a new job (I had been casually looking since around the four month mark). I went back upstairs and started a journal of everything he had ever said to me. I also began to have a little "fun", I'd ask "why?" like a toddler when he assigned me new scripts just to see his eyes get as large as saucer and see the little hamster wheel in his head race when he couldn't come up with an answer.
The straw that broke the camel's back was when Jav asked me to write a Julian Date converter for a customer. The customer needed unique numbers every time they created a new item order, so they needed it to be a Julian Date rather than a Gregorian Date. So I researched different algorithms wrote the code and with two other coworkers showed Jav the script after a few days. Like normal Jav was pissed. He looks at me and says I'm confused because I CLEARLY don't know what a Julian Date was. I blinked a little confused, and waited for him to continue. Jav tells me that he wants just month, day, year and that was it. He looks at my other two coworkers and expresses disappointment that they would leave me to this task given my current skills and that they needed to look over and oversee all of my work or do it themselves because they couldn't trust me with the work. (They had some programming knowledge but it wasn't very extensive. They actually passed along all their script writing to me). He orders us to go back upstairs and for me to rewrite the code to use a "proper" Julian Date.
My coworkers and I go back upstairs and a little angered I proclaim that Jav clearly doesn't know we are Gregorian calendar. My coworkers agree and said they were sorry that I had to rewrite the code. Suddenly Jav calls one of my coworkers and asks my coworker to put him on speaker so the whole office could hear (remember we have like 8 coworkers). She does as she's told and Jav proceeds to say that I'm a confused idiot who has no idea what she's doing and that I can't be left unsupervised because I go off and do what I want to do instead of the writing code that I should be writing. Jav says that he's a much better programmer and that his code is faster and more efficient than mine and that if he wasn't so busy he'd be writing all the scripts. He tells my coworkers that they need to keep a close eye on me and hangs up the phone. The office is awkwardly quiet and I'm so upset that I'm seeing red. I was so upset that if people asked me questions in person I'd IM them because I was going to explode at any minute. I immediately stopped working on the Julian Date script switching over to another script that was due much later.
I confided in a coworker who missed out on what happened above, and I told her that I was going to quit that Friday. She tells me to wait a few weeks because Jav always takes a month and a half long vacation. I thought that this was gold. In the weeks up to his vacation he kept parading around about how happy he was about not having to work on his vacation and that he was NOT taking a computer with him because he wasn't going to do anything but relax. He told me he was worried about the Julian Date script and that he was hoping it would be done soon because he didn't want to work on it during his vacation. On the day his vacation starts I go to the CEO and hand in my resignation letter saying that today was my very last day. The CEO is stunned and says "You don't want to give us two weeks" and I reply "No, I'm unhappy here". The CEO is taken aback and asks why, I pull out my journal and began to detail all the things that Jav has done. The CEO is dumbfounded and doesn't believe me since he and Jav had been working together and no one had ever said anything like this about Jav. I didn't really care because I wasn't going to be working there anymore. The CEO calls in my coworkers and announces that I have quit and the coworkers are also STUNNED. I go back to my desk which I had already packed up and put all my personal items in my car before everyone had arrived, and open up the Julian Date Script and write in the comments at the top of the file: "Hi Jav just to let you know, we're currently in the Gregorian Calendar and not the Julian Calendar. I'm so sorry that you're confused and don't understand the business process of this script. I hope you have fun on your vacation!"
I couldn't find a good place to add this end but around fifth month I learned that GBS had hired several people and all of them subsequently quit because of Jav's behavior and that the CEO had been told on multiple occasions that the reason why the person had quit was because of Jav I also learned that someone had went online and wrote negative reviews about Jav on any review site they could find. I tried to find those reviews but unfortunately they have been taking down. I'm upset that the CEO lied to my face stating that no one had ever told him about Jav's behavior but hey I'm not longer working there so I don't really care anymore.
I know that was incredible nasty and it's definitely a low point in my professional career but I don't regret it one bit. My only regret is not deleting all the code in the file so he had to start from scratch.
Sorry for the incredibly long post but I just thought I needed to tell the whole story. Hope you enjoyed it.
(source) story by (/u/CurlyCoderGirl)
567 notes
·
View notes
Text
download mafia cheats free W6SC+
💾 ►►► DOWNLOAD FILE 🔥🔥🔥🔥🔥 Press SHIFT + TAB and enter the cheat codes. Health trick, weapons and ammunition tricks, immortality mode, no damage, big hands, etc. This page contains a list of cheats, codes, Easter eggs, tips, and other secrets for Mafia for PC. If you've discovered a cheat you'd like to add to. - Idle Tycoon Game download free. Our Mafia trainer has 8 cheats and supports Steam. Cheat in this game and more with the WeMod app! Mafia cheats. Trainers and cheats for Steam. Download. click "Downloads," and put "Mafia Swap Program" in the search engine. You want to use--I enter the number that means I don't have a mod. Official Game Link. Today's Wackiest Video. All Rights Reserved. Privacy Policy Disclaimer. Latest Forum Discussions:. Rise from the lowly but well-dressed Foot soldier to the envied and feared Made Man in an era of big bands, zoot suits and Model Ts. Take on the role of a hit man, enforcer, getaway driver and more in your struggle for respect, money and power with the Salieri Family. There are 20 action-packed missions each of which have their own sub-quests. Luxury and riches are yours for the taking, if you do as Don Salieri has requested. From mob hits, car chases, shoot-outs and more, complete the unsavoury tasks the Family needs done and you will be handsomely rewarded. As well as the missions, there are over 12 square miles of the s American city, with simulated traffic, landmarks and surrounding landscapes to explore, it's a sprawling city where opportunity is around every corner. The environments pull you into the s with their attention to detail and style. From seedy bars and hotel rooms to train stations and airports--every location is rendered in fantastic detail by the 3-D "LS3D" engine, such as the Lost Heaven International Airport and Chinatown. In addition, drive over 60 different vehicles including the Model T, Roadsters and delivery trucks with some of the most realistic car physics ever made in a game. Info With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. Info none. Mafia: City of Lost Heaven v1. Info MafiaHack is a development and cheating tool for Mafia. It works for all versions of Mafia 1. It provides a position manager that allows you to read and modify the player's position in the memory. Additionally, it offers a save function that allows you to store read positions in a position file that you can use later. The cheating functionality helps mod development. It simplifies missions without actually requiring any changes to the mod itself e. This comes handy for test-runs or to find bugs more easily. Furthermore MafiaHack offers a fly mode. This allows you to override collisions and to get to parts of a map that are inaccessible by default. Again, everything without requiring any changes to the mod itself. Finally MafiaHack comes with an actor modificator that allows you to modify an actor properties in memory such as speed, strength, mass, etc Mafia: The City of Lost Heaven. Game Info. Game Cheat Codes. Game Demos. Game Patch Updates. Game Trainers. With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. MafiaHack is a development and cheating tool for Mafia. Game Patch Fixes.
1 note
·
View note
Text
download mafia cheats new UOA?
💾 ►►► DOWNLOAD FILE 🔥🔥🔥🔥🔥 Press SHIFT + TAB and enter the cheat codes. Health trick, weapons and ammunition tricks, immortality mode, no damage, big hands, etc. This page contains a list of cheats, codes, Easter eggs, tips, and other secrets for Mafia for PC. If you've discovered a cheat you'd like to add to. - Idle Tycoon Game download free. Our Mafia trainer has 8 cheats and supports Steam. Cheat in this game and more with the WeMod app! Mafia cheats. Trainers and cheats for Steam. Download. click "Downloads," and put "Mafia Swap Program" in the search engine. You want to use--I enter the number that means I don't have a mod. Official Game Link. Today's Wackiest Video. All Rights Reserved. Privacy Policy Disclaimer. Latest Forum Discussions:. Rise from the lowly but well-dressed Foot soldier to the envied and feared Made Man in an era of big bands, zoot suits and Model Ts. Take on the role of a hit man, enforcer, getaway driver and more in your struggle for respect, money and power with the Salieri Family. There are 20 action-packed missions each of which have their own sub-quests. Luxury and riches are yours for the taking, if you do as Don Salieri has requested. From mob hits, car chases, shoot-outs and more, complete the unsavoury tasks the Family needs done and you will be handsomely rewarded. As well as the missions, there are over 12 square miles of the s American city, with simulated traffic, landmarks and surrounding landscapes to explore, it's a sprawling city where opportunity is around every corner. The environments pull you into the s with their attention to detail and style. From seedy bars and hotel rooms to train stations and airports--every location is rendered in fantastic detail by the 3-D "LS3D" engine, such as the Lost Heaven International Airport and Chinatown. In addition, drive over 60 different vehicles including the Model T, Roadsters and delivery trucks with some of the most realistic car physics ever made in a game. Info With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. Info none. Mafia: City of Lost Heaven v1. Info MafiaHack is a development and cheating tool for Mafia. It works for all versions of Mafia 1. It provides a position manager that allows you to read and modify the player's position in the memory. Additionally, it offers a save function that allows you to store read positions in a position file that you can use later. The cheating functionality helps mod development. It simplifies missions without actually requiring any changes to the mod itself e. This comes handy for test-runs or to find bugs more easily. Furthermore MafiaHack offers a fly mode. This allows you to override collisions and to get to parts of a map that are inaccessible by default. Again, everything without requiring any changes to the mod itself. Finally MafiaHack comes with an actor modificator that allows you to modify an actor properties in memory such as speed, strength, mass, etc Mafia: The City of Lost Heaven. Game Info. Game Cheat Codes. Game Demos. Game Patch Updates. Game Trainers. With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. MafiaHack is a development and cheating tool for Mafia. Game Patch Fixes.
1 note
·
View note
Text
download mafia cheats working 2EQ%
💾 ►►► DOWNLOAD FILE 🔥🔥🔥🔥🔥 Press SHIFT + TAB and enter the cheat codes. Health trick, weapons and ammunition tricks, immortality mode, no damage, big hands, etc. This page contains a list of cheats, codes, Easter eggs, tips, and other secrets for Mafia for PC. If you've discovered a cheat you'd like to add to. - Idle Tycoon Game download free. Our Mafia trainer has 8 cheats and supports Steam. Cheat in this game and more with the WeMod app! Mafia cheats. Trainers and cheats for Steam. Download. click "Downloads," and put "Mafia Swap Program" in the search engine. You want to use--I enter the number that means I don't have a mod. Official Game Link. Today's Wackiest Video. All Rights Reserved. Privacy Policy Disclaimer. Latest Forum Discussions:. Rise from the lowly but well-dressed Foot soldier to the envied and feared Made Man in an era of big bands, zoot suits and Model Ts. Take on the role of a hit man, enforcer, getaway driver and more in your struggle for respect, money and power with the Salieri Family. There are 20 action-packed missions each of which have their own sub-quests. Luxury and riches are yours for the taking, if you do as Don Salieri has requested. From mob hits, car chases, shoot-outs and more, complete the unsavoury tasks the Family needs done and you will be handsomely rewarded. As well as the missions, there are over 12 square miles of the s American city, with simulated traffic, landmarks and surrounding landscapes to explore, it's a sprawling city where opportunity is around every corner. The environments pull you into the s with their attention to detail and style. From seedy bars and hotel rooms to train stations and airports--every location is rendered in fantastic detail by the 3-D "LS3D" engine, such as the Lost Heaven International Airport and Chinatown. In addition, drive over 60 different vehicles including the Model T, Roadsters and delivery trucks with some of the most realistic car physics ever made in a game. Info With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. Info none. Mafia: City of Lost Heaven v1. Info MafiaHack is a development and cheating tool for Mafia. It works for all versions of Mafia 1. It provides a position manager that allows you to read and modify the player's position in the memory. Additionally, it offers a save function that allows you to store read positions in a position file that you can use later. The cheating functionality helps mod development. It simplifies missions without actually requiring any changes to the mod itself e. This comes handy for test-runs or to find bugs more easily. Furthermore MafiaHack offers a fly mode. This allows you to override collisions and to get to parts of a map that are inaccessible by default. Again, everything without requiring any changes to the mod itself. Finally MafiaHack comes with an actor modificator that allows you to modify an actor properties in memory such as speed, strength, mass, etc Mafia: The City of Lost Heaven. Game Info. Game Cheat Codes. Game Demos. Game Patch Updates. Game Trainers. With this savegame you get all cars in freeridemode, all tracks and freeridemode extreme is unlocked. MafiaHack is a development and cheating tool for Mafia. Game Patch Fixes.
1 note
·
View note
Text
Gif tutorial (Avisynth, Photoshop and Topaz)
okay so I’ve gotten a lot of questions asking me how I get my gifs so nice and smooth and HD and normally I don’t really go into depth because it’s kind of a combination of stuff I’ve learnt over time. My old gif tutorial only outlines using photoshop and is like over a year old so here’s an updated version.
Everything below the cut
So I’m going to break this tutorial down into 3 key parts, avisynth, photoshop and topaz. So you can pick which section that applies for you and each tutorial is basically independent.
Avisynth
disclaimer: not recommended to start out using avisynth, learn and familiarise using photoshop to make gifs first.
some background - why I use avisynth is because it’s a way to downscale the video without/next to no loss at all, so the quality is retained. Definitely check out brandinator’s tutorial and explanations here as he’s basically the innovator for avisynth gifs. He’s also super friendly so if you have any issues definitely hit him up. His tutorial basically explains everything and I guess I’ll just outline what it’s telling you to do.
Drag your video file onto normalwebmrange.bat and a command prompt will pop up
if your file does not work with normalwebmrange, then useless lossless intead
Type in your start time and hit enter, do the same for the end time (must be in hh:mm:ss format, i.e. 00:00:03 for it to start at 3 seconds)
Then after all the frames are done processing resizer.html will automatically open up in your browser
From here you figure out the sizing of your video (please refer to brandinator’s video here because it explain it really well)
For the following steps just refer to the rest of the video because its visually easier to learn off
When you copy your code across, dont include the crop line of code just in case you want to move things around in photoshop later
What do all the options in the resizer.html mean?
GIF size - Here you just plug in the value that you intend for your gif. Refer here for the tumblr post width guides. I personally use:
540 x [anything] for single gifs
268 x 350 for double gifs
177/178 x 250 for triple gifs
Opacity - This slider is just so you can see which part of the video will be shown in your given dimensions and is really useful as a preview to see if everything you want in your gif is visible for that dimension so you can go back and tweak your size if necessary
Resizing - In the bottom right hand corner of the video itself you can click and drag to resize the video so that you can find the desired size/zoom for your given dimensions
Pre-processor - Okay this is the important one and where all the smooth and HD quality comes from. In the first box you are given these options
Use for standard video formats (mp4, avi, mkv etc)
None (choose none for every video that is not a .ts file)
IVTC (tbh idk what this does, but you don’t need it)
1 in 5 Dupe (some videos have a duplicate frame for every 5th frame and this fixes it, kind of rare to use)
Only use for .ts files Basically the 30 and 60 is talking about 30fps or 60fps, recommend that you choose 30fps for single gifs because you’re going to find it hard to fit anything of substance under 3mb for single gifs. Double gifs can be either 30/60 at your own discretion. Triple gifs can definitely be 60fps without worries about cutting out frames. The fast and slow options are the encoding speed once you’ve saved the avs, slow renders at a better quality but its barely noticeable so you can use fast every time
qtgmc 30 fast
qtgmc 30 slow
qtgmc 60 fast
qtgmc 60 slow
In the second box you can pick between resamplehq, debilinear and dithertools. All you need to know is that you choose debilinear every time and for every video format
Didn’t want to make another comparison gif so just reusing this one again, it’s slowed down so you can really see the different between 30fps and 60fps which you don’t normally feel too much if the gif is faster but definitely makes a difference. Notice the quality differences also between the first two, thats what avisynth helps with the most; making sure the quality loss is minimal
Photoshop
if you haven’t used avisynth to cut/resize your video then you can do that in photoshop, although I recommend that you cut the video first in another editing program so there’s less loading time and strain on photoshop itself (I use to use Adobe Premiere for this)
Refer here for the sites that I use to download my videos from.
Importing/Resizing
Import it into Photoshop (File > Import > Video Frames to Layers)
Match the settings to these
Select the part you want with the slider on the right and hit okay
Once it’s all into Photoshop, make sure you have the timeline/animation window open (Window > Timeline) and you should be able to see all the frames of your gif
If you’ve used avisynth you only need to crop it (Image > Canvas Size; and input in the desired size of your gif) otherwise you’ll need to crop it and then down size it to the desired gif size (Image > Image Size; setting width to either 540, 268, 177/178). Remember to set it to Bicubic and locking the width when you resize it
delete all the unwanted frames from your gifs
Sharpening
At this point I normally apply topaz (so refer below) otherwise you can do standard sharpening now
Select all your frames and layers and then convert it to a video timeline (bottom left of the timeline window)
Convert your layers to a smart object (Filter > Convert to Smart Object)
Then use Smart Sharpening (Filter > Sharpen > Smart Sharpen)
These are the settings that I used before, feel free to tweak them to whatever you like. (Note that .ts files resized with avisynth rarely need to be sharpened at all because sometimes they already look over sharp, and thus we use topaz to clean them up)
After you’re happy with your sharpening, flatten the layers back to frames, make sure your gif is on the first frame (top right hand corner of the timeline (Convert Frames > Flatten Frames to Clips) and then create the frames again (Convert Frames > Make Frames from Clips)
Click the bottom left again to change your video timeline back to frame view and delete the first frame (it should be blank) and your first layer (it should be the one thats still a smart object)
Now everything should look like it did before you did your sharpening except now your gif will be sharpened
Colouring
I’m not really going to tell you how to colour your gifs because everyone does them differently and it’s also kind of each individual’s signature thing, like often I can tell who’s gif it is just by the way its coloured. Things to note here that too many colours and over saturation will lead to your gif file being extremely large. If you want to lower the file size of your gif it’s helpful to note that darkening the blacks and/or brightening the whites helps a lot. Colouring overall can drastically change your gifs so much. Here’s an example
of course you don’t want to change the colours so heavily every time but its just an example of what’s possible
Timing
To change timing (speed) for your gif you just need to select all your frames and then click on the arrow at the bottom of any of the frames and someone will pop up for you to input the time. Generally this is what I use for my gifs
0.02/0.03/0.04 for 60fps
0.04/0.05/0.06 for 30fps
Exporting
After all your colouring and timing is done you just need to export your gif (File > Export > Save for Web (Legacy)), double check that your gif is less than 3mb (in the bottom left) and also here are my export settings. Never lower the colours unless your gif is black and white or you’ll probably lose quality
200212 update: gif size limits have been increased and can reach up to 8mb but have exponential loss in quality the larger the file size. The optimal size is to remain under 3.5mb-4mb, another larger and you’ll start to notice a loss in quality.
Topaz
I’m not going to tell you where and/or how to download topaz because you can probably look that up yourself, there are a bunch of tutorials how on tumblr/youtube etc so you can sort that out. I use Topaz Denoise and Topaz Clean. Topaz Denoise does exactly what you think it does, it removes the noise from your gifs and it helps clean everything up. Topaz Clean is basically the same principle of sharpening but it’s a lot nicer and doesn’t over sharpen like smart sharpening does most of the time. Topaz takes a lot longer than regular sharpening because it processes it frame by frame so if you don’t want to bother and wait for it that’s alright.
Following the same two steps as the sharpening section of the photoshop tutorial, you’ll have a video timeline and a smart object
Then apply Denoise first (Filter > Topaz Labs > Topaz Denoise)
Play around with the settings as you see fit
Then do the same for Clean (Filter > Topaz Labs > Topaz Clean)
After you’re happy with the settings you’ll need to flatten the frames back (refer above to the sharpening section), and then just put on your psd like normal
Sometimes topaz isn’t as noticeable but when compared to original and smart sharpening it’s more noticeable
Smart Sharpen, while it does sharpen the gif also increases how grainy it is so for gifs which are originally grainy it’s not a good solution. Meanwhile topaz sharpens it without increasing the grain and can also reduce it. (The gifs above aren’t that grainy so smart sharpening doesn’t look that bad)
Here’s another example of how topaz removes the noise from gifs
Wrap-up
Okay so this is a pretty comprehensive post about everything and anything I know about photoshop and gif-making in general, it outlines all the little steps I take to make my gifs look really nice and just for an overall comparison to show you that the little things really add up
and slower for you to notice the framerate
Additional Video Tutorial Resources:
Basic gif tutorial (photoshop only)
Gif Tutorial
How I adjust for coloured lighting
Colouring tutorial - levels + exposure
Blurring text/captions
If you have any other questions on this or felt like I’ve missed something feel free to send me an ask. Happy Gif-ing!
#text#tutorials#okay this is was longer than i thought it would be#i really hope it covered everything#hmu if you think i missed anything#i proof read it but i probably missed smth so cut me some slack#ive been @ this for like 1387201 hours#long post#this is the most comprehensive thing i've written in my life
1K notes
·
View notes
Text
Create A Bookmarking Application With FaunaDB, Netlify And 11ty
Create A Bookmarking Application With FaunaDB, Netlify And 11ty
Bryan Robinson
2019-10-24T13:30:59+02:002019-10-24T13:03:40+00:00
The JAMstack (JavaScript, APIs and Markup) revolution is in full swing. Static sites are secure, fast, reliable and fun to work on. At the heart of the JAMstack are static site generators (SSGs) that store your data as flat files: Markdown, YAML, JSON, HTML, and so on. Sometimes, managing data this way can be overly complicated. Sometimes, we still need a database.
With that in mind, Netlify — a static site host and FaunaDB — a serverless cloud database — collaborated to make combining both systems easier.
Why A Bookmarking Site?
The JAMstack is great for many professional uses, but one of my favorite aspects of this set of technology is its low barrier to entry for personal tools and projects.
There are plenty of good products on the market for most applications I could come up with, but none would be exactly set up for me. None would give me full control over my content. None would come without a cost (monetary or informational).
With that in mind, we can create our own mini-services using JAMstack methods. In this case, we’ll be creating a site to store and publish any interesting articles I come across in my daily technology reading.
I spend a lot of time reading articles that have been shared on Twitter. When I like one, I hit the “heart” icon. Then, within a few days, it’s nearly impossible to find with the influx of new favorites. I want to build something as close to the ease of the “heart,” but that I own and control.
How are we going to do that? I’m glad you asked.
Interested in getting the code? You can grab it on Github or just deploy straight to Netlify from that repository! Take a look at the finished product here.
Our Technologies
Hosting And Serverless Functions: Netlify
For hosting and serverless functions, we’ll be utilizing Netlify. As an added bonus, with the new collaboration mentioned above, Netlify’s CLI — “Netlify Dev” — will automatically connect to FaunaDB and store our API keys as environment variables.
Database: FaunaDB
FaunaDB is a “serverless” NoSQL database. We’ll be using it to store our bookmarks data.
Static Site Generator: 11ty
I’m a big believer in HTML. Because of this, the tutorial won’t be using front-end JavaScript to render our bookmarks. Instead, we’ll utilize 11ty as a static site generator. 11ty has built-in data functionality that makes fetching data from an API as easy as writing a couple of short JavaScript functions.
iOS Shortcuts
We’ll need an easy way to post data to our database. In this case, we’ll use iOS’s Shortcuts app. This could be converted to an Android or desktop JavaScript bookmarklet, as well.
Setting Up FaunaDB Via Netlify Dev
Whether you have already signed up for FaunaDB or you need to create a new account, the easiest way to set up a link between FaunaDB and Netlify is via Netlify’s CLI: Netlify Dev. You can find full instructions from FaunaDB here or follow along below.
If you don’t already have this installed, you can run the following command in Terminal:
npm install netlify-cli -g
From within your project directory, run through the following commands:
netlify init // This will connect your project to a Netlify project netlify addons:create fauna // This will install the FaunaDB "addon" netlify addons:auth fauna // This command will run you through connecting your account or setting up an account
Once this is all connected, you can run netlify dev in your project. This will run any build scripts we set up, but also connect to the Netlify and FaunaDB services and grab any necessary environment variables. Handy!
Creating Our First Data
From here, we’ll log into FaunaDB and create our first data set.
We’ll start by creating a new Database called “bookmarks.” Inside a Database, we have Collections, Documents and Indexes.
A Collection is a categorized group of data. Each piece of data takes the form of a Document. A Document is a “single, changeable record within a FaunaDB database,” according to Fauna’s documentation. You can think of Collections as a traditional database table and a Document as a row.
For our application, we need one Collection, which we’ll call “links.” Each document within the “links” Collection will be a simple JSON object with three properties. To start, we’ll add a new Document that we’ll use to build our first data fetch.
{ "url": "https://css-irl.info/debugging-css-grid-part-2-what-the-fraction/", "pageTitle": "CSS { In Real Life } | Debugging CSS Grid – Part 2: What the Fr(action)?", "description": "CSS In Real Life is a blog covering CSS topics and useful snippets on the web’s most beautiful language. Published by Michelle Barker, front end developer at Ordoo and CSS superfan." }
This creates the basis for the information we’ll need to pull from our bookmarks as well as provides us with our first set of data to pull into our template.
If you’re like me, you want to see the fruits of your labor right away. Let’s get something on the page!
Installing 11ty And Pulling Data Into A Template
Since we want the bookmarks to be rendered in HTML and not fetched by the browser, we’ll need something to do the rendering. There are many great ways of doing it, but for ease and power, I love using the 11ty static site generator.
Since 11ty is a JavaScript static site generator, we can install it via NPM.
npm install --save @11ty/eleventy
From that installation, we can run eleventy or eleventy --serve in our project to get up and running.
Netlify Dev will often detect 11ty as a requirement and run the command for us. To have this work - and make sure we’re ready to deploy, we can also create “serve” and “build” commands in our package.json.
"scripts": { "build": "npx eleventy", "serve": "npx eleventy --serve" }
11ty’s Data Files
Most static site generators have an idea of a “data file” built-in. Usually, these files will be JSON or YAML files that allow you to add extra information to your site.
In 11ty, you can use JSON data files or JavaScript data files. By utilizing a JavaScript file, we can actually make our API calls and return the data directly into a template.
The file will be a JavaScript module. So in order to have anything work, we need to export either our data or a function. In our case, we’ll export a function.
module.exports = async function() { const data = mapBookmarks(await getBookmarks()); return data.reverse() }
Let’s break that down. We have two functions doing our main work here: mapBookmarks() and getBookmarks().
The getBookmarks() function will go fetch our data from our FaunaDB database and mapBookmarks() will take an array of bookmarks and restructure it to work better for our template.
Let’s dig deeper into getBookmarks().
getBookmarks()
First, we’ll need to install and initialize an instance of the FaunaDB JavaScript driver.
npm install --save faunadb
Now that we’ve installed it, let’s add it to the top of our data file. This code is straight from Fauna’s docs.
// Requires the Fauna module and sets up the query module, which we can use to create custom queries. const faunadb = require('faunadb'), q = faunadb.query; // Once required, we need a new instance with our secret var adminClient = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET });
After that, we can create our function. We’ll start by building our first query using built-in methods on the driver. This first bit of code will return the database references we can use to get full data for all of our bookmarked links. We use the Paginate method, as a helper to manage cursor state should we decide to paginate the data before handing it to 11ty. In our case, we’ll just return all the references.
In this example, I’m assuming you installed and connected FaunaDB via the Netlify Dev CLI. Using this process, you get local environment variables of the FaunaDB secrets. If you didn’t install it this way or aren’t running netlify dev in your project, you’ll need a package like dotenv to create the environment variables. You’ll also need to add your environment variables to your Netlify site configuration to make deploys work later.
adminClient.query(q.Paginate( q.Match( // Match the reference below q.Ref("indexes/all_links") // Reference to match, in this case, our all_links index ) )) .then( response => { ... })
This code will return an array of all of our links in reference form. We can now build a query list to send to our database.
adminClient.query(...) .then((response) => { const linkRefs = response.data; // Get just the references for the links from the response const getAllLinksDataQuery = linkRefs.map((ref) => { return q.Get(ref) // Return a Get query based on the reference passed in }) return adminClient.query(getAllLinksDataQuery).then(ret => { return ret // Return an array of all the links with full data }) }).catch(...)
From here, we just need to clean up the data returned. That’s where mapBookmarks() comes in!
mapBookmarks()
In this function, we deal with two aspects of the data.
First, we get a free dateTime in FaunaDB. For any data created, there’s a timestamp (ts) property. It’s not formatted in a way that makes Liquid’s default date filter happy, so let’s fix that.
function mapBookmarks(data) { return data.map(bookmark => { const dateTime = new Date(bookmark.ts / 1000); ... }) }
With that out of the way, we can build a new object for our data. In this case, it will have a time property, and we’ll use the Spread operator to destructure our data object to make them all live at one level.
function mapBookmarks(data) { return data.map(bookmark => { const dateTime = new Date(bookmark.ts / 1000); return { time: dateTime, ...bookmark.data } }) }
Here’s our data before our function:
{ ref: Ref(Collection("links"), "244778237839802888"), ts: 1569697568650000, data: { url: 'https://sample.com', pageTitle: 'Sample title', description: 'An escaped description goes here' } }
Here’s our data after our function:
{ time: 1569697568650, url: 'https://sample.com', pageTitle: 'Sample title' description: 'An escaped description goes here' }
Now, we’ve got well-formatted data that’s ready for our template!
Let’s write a simple template. We’ll loop through our bookmarks and validate that each has a pageTitle and a url so we don’t look silly.
<div class="bookmarks"> </div>
We’re now ingesting and displaying data from FaunaDB. Let’s take a moment and think about how nice it is that this renders out pure HTML and there’s no need to fetch data on the client side!
But that’s not really enough to make this a useful app for us. Let’s figure out a better way than adding a bookmark in the FaunaDB console.
Enter Netlify Functions
Netlify’s Functions add-on is one of the easier ways to deploy AWS lambda functions. Since there’s no configuration step, it’s perfect for DIY projects where you just want to write the code.
This function will live at a URL in your project that looks like this: https://myproject.com/.netlify/functions/bookmarks assuming the file we create in our functions folder is bookmarks.js.
Basic Flow
Pass a URL as a query parameter to our function URL.
Use the function to load the URL and scrape the page’s title and description if available.
Format the details for FaunaDB.
Push the details to our FaunaDB Collection.
Rebuild the site.
Requirements
We’ve got a few packages we’ll need as we build this out. We’ll use the netlify-lambda CLI to build our functions locally. request-promise is the package we’ll use for making requests. Cheerio.js is the package we’ll use to scrape specific items from our requested page (think jQuery for Node). And finally, we’ll need FaunaDb (which should already be installed.
npm install --save netlify-lambda request-promise cheerio
Once that’s installed, let’s configure our project to build and serve the functions locally.
We’ll modify our “build” and “serve” scripts in our package.json to look like this:
"scripts": { "build": "npx netlify-lambda build lambda --config ./webpack.functions.js && npx eleventy", "serve": "npx netlify-lambda build lambda --config ./webpack.functions.js && npx eleventy --serve" }
Warning: There’s an error with Fauna’s NodeJS driver when compiling with Webpack, which Netlify’s Functions use to build. To get around this, we need to define a configuration file for Webpack. You can save the following code to a new — or existing — webpack.config.js.
const webpack = require('webpack'); module.exports = { plugins: [ new webpack.DefinePlugin({ "global.GENTLY": false }) ] };
Once this file exists, when we use the netlify-lambda command, we’ll need to tell it to run from this configuration. This is why our “serve” and “build scripts use the --config value for that command.
Function Housekeeping
In order to keep our main Function file as clean as possible, we’ll create our functions in a separate bookmarks directory and import them into our main Function file.
import { getDetails, saveBookmark } from "./bookmarks/create";
getDetails(url)
The getDetails() function will take a URL, passed in from our exported handler. From there, we’ll reach out to the site at that URL and grab relevant parts of the page to store as data for our bookmark.
We start by requiring the NPM packages we need:
const rp = require('request-promise'); const cheerio = require('cheerio');
Then, we’ll use the request-promise module to return an HTML string for the requested page and pass that into cheerio to give us a very jQuery-esque interface.
const getDetails = async function(url) { const data = rp(url).then(function(htmlString) { const $ = cheerio.load(htmlString); ... }
From here, we need to get the page title and a meta description. To do that, we’ll use selectors like you would in jQuery.
Note: In this code, we use 'head > title' as the selector to get the title of the page. If you don’t specify this, you may end up getting <title> tags inside of all SVGs on the page, which is less than ideal.
const getDetails = async function(url) { const data = rp(url).then(function(htmlString) { const $ = cheerio.load(htmlString); const title = $('head > title').text(); // Get the text inside the tag const description = $('meta[name="description"]').attr('content'); // Get the text of the content attribute // Return out the data in the structure we expect return { pageTitle: title, description: description }; }); return data //return to our main function }
With data in hand, it’s time to send our bookmark off to our Collection in FaunaDB!
saveBookmark(details)
For our save function, we’ll want to pass the details we acquired from getDetails as well as the URL as a singular object. The Spread operator strikes again!
const savedResponse = await saveBookmark({url, ...details});
In our create.js file, we also need to require and setup our FaunaDB driver. This should look very familiar from our 11ty data file.
const faunadb = require('faunadb'), q = faunadb.query; const adminClient = new faunadb.Client({ secret: process.env.FAUNADB_SERVER_SECRET });
Once we’ve got that out of the way, we can code.
First, we need to format our details into a data structure that Fauna is expecting for our query. Fauna expects an object with a data property containing the data we wish to store.
const saveBookmark = async function(details) { const data = { data: details }; ... }
Then we’ll open a new query to add to our Collection. In this case, we’ll use our query helper and use the Create method. Create() takes two arguments. First is the Collection in which we want to store our data and the second is the data itself.
After we save, we return either success or failure to our handler.
const saveBookmark = async function(details) { const data = { data: details }; return adminClient.query(q.Create(q.Collection("links"), data)) .then((response) => { /* Success! return the response with statusCode 200 */ return { statusCode: 200, body: JSON.stringify(response) } }).catch((error) => { /* Error! return the error with statusCode 400 */ return { statusCode: 400, body: JSON.stringify(error) } }) }
Let’s take a look at the full Function file.
import { getDetails, saveBookmark } from "./bookmarks/create"; import { rebuildSite } from "./utilities/rebuild"; // For rebuilding the site (more on that in a minute) exports.handler = async function(event, context) { try { const url = event.queryStringParameters.url; // Grab the URL const details = await getDetails(url); // Get the details of the page const savedResponse = await saveBookmark({url, ...details}); //Save the URL and the details to Fauna if (savedResponse.statusCode === 200) { // If successful, return success and trigger a Netlify build await rebuildSite(); return { statusCode: 200, body: savedResponse.body } } else { return savedResponse //or else return the error } } catch (err) { return { statusCode: 500, body: `Error: ${err}` }; } };
rebuildSite()
The discerning eye will notice that we have one more function imported into our handler: rebuildSite(). This function will use Netlify’s Deploy Hook functionality to rebuild our site from the new data every time we submit a new — successful — bookmark save.
In your site’s settings in Netlify, you can access your Build & Deploy settings and create a new “Build Hook.” Hooks have a name that appears in the Deploy section and an option for a non-master branch to deploy if you so wish. In our case, we’ll name it “new_link” and deploy our master branch.
A visual reference for the Netlify Admin’s build hook setup (Large preview)
From there, we just need to send a POST request to the URL provided.
We need a way of making requests and since we’ve already installed request-promise, we’ll continue to use that package by requiring it at the top of our file.
const rp = require('request-promise'); const rebuildSite = async function() { var options = { method: 'POST', uri: 'https://api.netlify.com/build_hooks/5d7fa6175504dfd43377688c', body: {}, json: true }; const returned = await rp(options).then(function(res) { console.log('Successfully hit webhook', res); }).catch(function(err) { console.log('Error:', err); }); return returned }
A demo of the Netlify Function setup and the iOS Shortcut setup combined
Setting Up An iOS Shortcut
So, we have a database, a way to display data and a function to add data, but we’re still not very user-friendly.
Netlify provides URLs for our Lambda functions, but they’re not fun to type into a mobile device. We’d also have to pass a URL as a query parameter into it. That’s a LOT of effort. How can we make this as little effort as possible?
A visual reference for the setup for our Shortcut functionality (Large preview)
Apple’s Shortcuts app allows the building of custom items to go into your share sheet. Inside these shortcuts, we can send various types of requests of data collected in the share process.
Here’s the step-by-step Shortcut:
Accept any items and store that item in a “text” block.
Pass that text into a “Scripting” block to URL encode (just in case).
Pass that string into a URL block with our Netlify Function’s URL and a query parameter of url.
From “Network” use a “Get contents” block to POST to JSON to our URL.
Optional: From “Scripting” “Show” the contents of the last step (to confirm the data we’re sending).
To access this from the sharing menu, we open up the settings for this Shortcut and toggle on the “Show in Share Sheet” option.
As of iOS13, these share “Actions” are able to be favorited and moved to a high position in the dialog.
We now have a working “app” for sharing bookmarks across multiple platforms!
Go The Extra Mile!
If you’re inspired to try this yourself, there are a lot of other possibilities to add functionality. The joy of the DIY web is that you can make these sorts of applications work for you. Here are a few ideas:
Use a faux “API key” for quick authentication, so other users don’t post to your site (mine uses an API key, so don’t try to post to it!).
Add tag functionality to organize bookmarks.
Add an RSS feed for your site so that others can subscribe.
Send out a weekly roundup email programmatically for links that you’ve added.
Really, the sky is the limit, so start experimenting!
(dm, yk)
0 notes
Link
React TypeScript: Basics and Best PracticesAn updated handbook/cheat sheet for working with React.js with TypeScript.There is no single “right” way of writing React code using TypeScript. As with other technologies, if your code compiles and works, you probably did something right. That being said, there are “best practices” that you’d want to consider following, especially when writing code others will have to either read or re-use for their own purposes. So, here I’m going to list some useful code-snippets that follow said “best practices”. There are a lot of them, some that you might’ve used already in the past and some that might be new. Just go through the list and make mental notes. Bookmarking this article for future reference might be a good idea as well. Making your components ready for sharing, with TypeScriptExample: browsing through shared React components in bit.devBit.dev has become a very popular alternative to traditional component libraries as it offers a way to “harvest” and share individual components from any codebase (to a single component hub). By building projects using React with TS, you make sure your components are easily comprehensible to other developers (as well as to your future self). That is absolutely crucial for making them ready for sharing. It’s a great way to write maintainable code and optimize your team collaboration. Learn more about sharing and reusing React TS components across repos here: Getting startedcreate-react-app with TypeScript$ npx create-react-app your-app-name --template typescriptIf you’re more of a fan of Yarn, you can use the following command: $ yarn create react-app your-app-name --template typescriptIn either case, notice how we’re not directly using the app, rather, we’re using other tools that will download the latest version of the app whenever it’s required. This helps ensure you’re not using an outdated version. BasicsSome of the very interesting tidbits added by TS to the language are: InterfacesOne of the many benefits TypeScript brings to the table, is access to constructs such as this, which allows you to define the interface of your components or even any other complex objects you might want to use with them, such as the shape your Props object will have (i.e how many properties and their types). The above code ensures that whoever uses your components needs to add exactly 3 properties: text: which needs to be a Stringtype: which needs to be a ButtonType option (I’ll cover Enums in a second)action: which is a simple functionNote that we “extended” the FC (Functional Component) type with our own custom interface. That gives our function all the generic functional component definitions such as the ‘children’ prop and a return type that must be assignable to JSX.Element. If you ignore one of them or send something that’s not compatible, both the TypeScript compiler and your IDE (assuming you’re using a JavaScript specific IDE, such as Code) will notify you and won’t allow you to continue until you fix it. A better way to define our ExtendedButton element would be to extend a native HTML button element type like so: But more on that topic later in this post… Also, note that when working with Bit.dev or react-docgen, the following syntax is required to auto-generate docs: (The props are defined directly and explicitly using :IButtonProps in addition to defining the component with ) EnumsJust like with Interfaces, Enums allow you to define a set of related constants as part of a single entity. Importing and using Enums: Please note that unlike Interfaces or Types, Enums will get translated into plain JavaScript. So, for example, this: enum SelectableButtonTypes {Important = "important",Optional = "optional",Irrelevant = "irrelevant"}will transform into this: "use strict";var SelectableButtonTypes;(function (SelectableButtonTypes) {SelectableButtonTypes["Important"] = "important";SelectableButtonTypes["Optional"] = "optional";SelectableButtonTypes["Irrelevant"] = "irrelevant";})(SelectableButtonTypes || (SelectableButtonTypes = {}));Interfaces vs Types aliasA common question that newcomers to TypeScript have is whether they should be using Interfaces or Type Aliases for different parts of their code — after all, the official documentation is a bit unclear regarding that topic. Truth is, although these entities are conceptually different, in practice, they are quite similar: They can both be extended.2. They can both be used to define the shape of objects. 3. They both can be implemented in the same way. The only extra feature Interfaces bring to the table (that Type aliases don’t), is “declaration merging” which means you can define the same interface several times and with each definition, the properties get merged: Optional types for your propsPart of the benefits of using Interfaces is that you’re able to enforce the properties of your props for your components. However, thanks to the optional syntax available through TypeScript, you can also define optional props, like this: HooksHooks are the new mechanics React provides to interact with several of its features (such as the state) without the need to define a class. Adding type check to hooksHooks such as useState receive a parameter and correctly return the state (again, that’s for this case) and a function to set it. Thanks to TypeScript’s type validation, you can enforce the type (or interface) of the initial value of the state, like this: Nullable values to hooksHowever, if the initial value for your hook can potentially be a null, then the above example will fail. For these cases, TypeScript allows you to set an optional type as well, making sure you’re covered from all sides. That way you’re ensuring you keep type checks, but allow for those scenarios where the initial value can come as null. Generic ComponentsMuch like the way you define generic functions and interfaces in TypeScript, you can define generic components, allowing you to re-use them for different data types. You can do this for props and states as well. You can then use the component either by taking advantage of type inference or directly specifying the data types, likes so: Type inference exampleDirectly declared typesFor the latter, note that if your list contains strings instead of numbers, TypeScript will throw an error during the transpilation process. Extending HTML ElementsSometimes, your components function and behave like native HTML elements (on steroids). For example, a “borederd box” (which is simply a component that always renders a div with a default border) or a “big submit” (which again, is nothing but your good old submit button with a default size and maybe some custom behavior). For these scenarios, it’s best to define your component type as a native HTML element or an extension of it. As you can see, I’ve extended HTML’s default props and added a new one: “title” for my specific needs. Event TypesAs you probably know, React provides its own set of events, which is why you can’t directly use the good old HTML Events. That being said, you do have access to all the useful UI events you need, so much so in fact, that they have the same names as well, so make sure you reference them directly like React.MouseEvent or just remember to import them from React like so: import React, { Component, MouseEvent } from 'react';The benefits of using TypeScript here, is that we can also use Generics (like in the previous example) to restrict the elements a particular event handler can be used on. For example, the following code will not work: And you’ll see an error message similar to the following: You can, however, use unions to allow a single handler to be re-used by multiple components: Integrated type definitionFinally, for the last tip, I wanted to mention the index.d.ts and the global.d.ts files. They’re both installed when you add React to your project (if you used npm, you’ll find them inside the npm_modules/@types folder. These files contain type and interface definitions used by React, so if you need to understand the props of one particular type, you can simply open these files and review their content. For example: There you can see a small section of the index.d.ts file, showing the different signatures for the createElement function. ConclusionThere is a lot more you can achieve by using TypeScript as part of your React toolchain, so if you saw at least one snippet that you liked here, consider reading up on how to gradually migrate your React projects to TypeScript or even learn how to design your own React TypeScript libraries here. Either way, I hope you got something out of this article, and feel free to leave any other tips or tricks you’ve picked up over the years of using TypeScript for your React projects! See you on the next one! Learn More
0 notes
Text
Books2Read | Amazon | Nook | Kobo | iBooks
They say the college years are what really shape a person for adulthood, and although rare, unbreakable bonds of friendship can be made.
Gianna Moretti, Alexis Cole and Shelby Lansing are about to discover just how true that is.
When the three of them are thrust together in a new city and on their own for the first time at Boston College, none of them expected to click so suddenly despite their many differences.
They also didn’t anticipate how much their lives would be changed.
Forgotten dreams, family secrets and heartbreaking fears are revealed as they grow, learn and laugh together. What started as a bond of circumstance quickly becomes something they never realized they’d all been missing –true friendship.
However, when devastation strikes, will their friendship survive?
This slideshow requires JavaScript.
About Elle Vanzandt
Elle Vanzandt grew up a Navy brat who has had the privilege of living in many places but she will always call Illinois her home. A stay-at-home mom to two amazing children, a blogger, and a writer, she wears many hats. With determination and strength taught to her by her mother, she has made it through many obstacles, always sticking with the family motto ‘pull up your bootstraps and get it done’. Writing is an adventure she could have never predicted but is enjoying the ride all the same. In her free time you can find Elle with a book in one hand and a cup of coffee in the other. Background noise is a must, usually in the form of her kids, but Netflix and country music on Spotify come in a close second and third.
Facebook | Twitter | Goodreads
About P. Marie
P. Marie lives in a small town outside of Boston where she was born and raised. She resides in the house she grew up in with her husband of 33 years and her two fur babies, Tek and Tessie. She is the proud mother of two adult children as well. While she is a corporate girl during the day, she has become both a writer and blogger by night. P. Marie says she owes her success to her mother who taught her that if you believe in yourself, you can achieve anything.
Facebook | Goodreads
a Rafflecopter giveaway
Grab this fun coming of age novel by @AuthorEVanzandt and P Marie! #WelcomeToBeantown Books2Read | Amazon | Nook | Kobo | iBooks They say the college years are what really shape a person for adulthood, and although rare, unbreakable bonds of friendship can be made.
#Beantown#ComingofAge#ElleVanZandt#HEANovelThoughts#NewRelease#PMarie#ReleaseBlitz#WelcomeToBeantown
0 notes
Text
Designing And Building A Progressive Web Application Without A Framework (Part 2)
Designing And Building A Progressive Web Application Without A Framework (Part 2)
Ben Frain
2019-07-25T14:00:59+02:002019-07-25T12:06:45+00:00
The raison d’être of this adventure was to push your humble author a little in the disciplines of visual design and JavaScript coding. The functionality of the application I’d decided to build was not dissimilar to a ‘to do’ application. It is important to stress that this wasn’t an exercise in original thinking. The destination was far less important than the journey.
Want to find out how the application ended up? Point your phone browser at https://io.benfrain.com.
Read Part One of Designing And Building A Progessive Web Application Without A Framework.
Here is a summary of what we will cover in this article:
The project set-up and why I opted for Gulp as a build tool;
Application design patterns and what they mean in practice;
How to store and visualize application state;
how CSS was scoped to components;
what UI/UX niceties were employed to make the things more ‘app-like’;
How the remit changed through iteration.
Let’s start with the build tools.
Build Tools
In order to get my basic tooling of TypeScipt and PostCSS up and running and create a decent development experience, I would need a build system.
In my day job, for the last five years or so, I have been building interface prototypes in HTML/CSS and to a lesser extent, JavaScript. Until recently, I have used Gulp with any number of plugins almost exclusively to achieve my fairly humble build needs.
Typically I need to process CSS, convert JavaScript or TypeScript to more widely supported JavaScript, and occasionally, carry out related tasks like minifying code output and optimizing assets. Using Gulp has always allowed me to solve those issues with aplomb.
For those unfamiliar, Gulp lets you write JavaScript to do ‘something’ to files on your local file system. To use Gulp, you typically have a single file (called gulpfile.js) in the root of your project. This JavaScript file allows you to define tasks as functions. You can add third-party ‘Plugins’, which are essentially further JavaScript functions, that deal with specific tasks.
An Example Gulp Task
An example Gulp task might be using a plugin to harness PostCSS to process to CSS when you change an authoring style sheet (gulp-postcss). Or compiling TypeScript files to vanilla JavaScript (gulp-typescript) as you save them. Here is a simple example of how you write a task in Gulp. This task uses the ‘del’ gulp plugin to delete all the files in a folder called ‘build’:
var del = require("del"); gulp.task("clean", function() { return del(["build/**/*"]); });
The require assigns the del plugin to a variable. Then the gulp.task method is called. We name the task with a string as the first argument (“clean”) and then run a function, which in this case uses the ‘del’ method to delete the folder passed to it as an argument. The asterisk symbols there are ‘glob’ patterns which essentially say ‘any file in any folder’ of the build folder.
Gulp tasks can get heaps more complicated but in essence, that is the mechanics of how things are handled. The truth is, with Gulp, you don’t need to be a JavaScript wizard to get by; grade 3 copy and paste skills are all you need.
I’d stuck with Gulp as my default build tool/task runner for all these years with a policy of ‘if it ain’t broke; don’t try and fix it’.
However, I was worried I was getting stuck in my ways. It’s an easy trap to fall into. First, you start holidaying the same place every year, then refusing to adopt any new fashion trends before eventually and steadfastly refusing to try out any new build tools.
I’d heard plenty of chatter on the Internets about ‘Webpack’ and thought it was my duty to try a project using the new-fangled toast of the front-end developer cool-kids.
Webpack
I distinctly remember skipping over to the webpack.js.org site with keen interest. The first explanation of what Webpack is and does started like this:
import bar from './bar';
Say what? In the words of Dr. Evil, “Throw me a frickin’ bone here, Scott”.
I know it’s my own hang-up to deal with but I’ve developed a revulsion to any coding explanations that mention ‘foo’, ‘bar’ or ‘baz’. That plus the complete lack of succinctly describing what Webpack was actually for had me suspecting it perhaps wasn’t for me.
Digging a little further into the Webpack documentation, a slightly less opaque explanation was offered, “At its core, webpack is a static module bundler for modern JavaScript applications”.
Hmmm. Static module bundler. Was that what I wanted? I wasn’t convinced. I read on but the more I read, the less clear I was. Back then, concepts like dependency graphs, hot module reloading, and entry points were essentially lost on me.
A couple of evenings of researching Webpack later, I abandoned any notion of using it.
I’m sure in the right situation and more experienced hands, Webpack is immensely powerful and appropriate but it seemed like complete overkill for my humble needs. Module bundling, tree-shaking, and hot-module reloading sounded great; I just wasn’t convinced I needed them for my little ‘app’.
So, back to Gulp then.
On the theme of not changing things for change sake, another piece of technology I wanted to evaluate was Yarn over NPM for managing project dependencies. Until that point, I had always used NPM and Yarn was getting touted as a better, faster alternative. I don’t have much to say about Yarn other than if you are currently using NPM and everything is OK, you don’t need to bother trying Yarn.
One tool that arrived too late for me to appraise for this application is Parceljs. With zero configuration and a BrowserSync like browser reloading backed in, I’ve since found great utility in it! In addition, in Webpack’s defense, I'm told that v4 onwards of Webpack doesn’t require a configuration file. Anecdotally, in a more recent poll I ran on Twitter, of the 87 respondents, over half chose Webpack over Gulp, Parcel or Grunt.
I started my Gulp file with basic functionality to get up and running.
A ‘default’ task would watch the ‘source’ folders of style sheets and TypeScript files and compile them out to a build folder along with the basic HTML and associated source maps.
I got BrowserSync working with Gulp too. I might not know what to do with a Webpack configuration file but that didn’t mean I was some kind of animal. Having to manually refresh the browser while iterating with HTML/CSS is soooo 2010 and BrowserSync gives you that short feedback and iteration loop that is so useful for front-end coding.
Here is the basic gulp file as of 11.6.2017
You can see how I tweaked the Gulpfile nearer to the end of shipping, adding minification with ugilify:
Project Structure
By consequence of my technology choices, some elements of code organization for the application were defining themselves. A gulpfile.js in the root of the project, a node_modules folder (where Gulp stores plugin code) a preCSS folder for the authoring style sheets, a ts folder for the TypeScript files, and a build folder for the compiled code to live.
The idea was to have an index.html that contained the ‘shell’ of the application, including any non-dynamic HTML structure and then links to the styles and the JavaScript file that would make the application work. On disk, it would look something like this:
build/ node_modules/ preCSS/ img/ partials/ styles.css ts/ .gitignore gulpfile.js index.html package.json tsconfig.json
Configuring BrowserSync to look at that build folder meant I could point my browser at localhost:3000 and all was good.
With a basic build system in place, files organization settled and some basic designs to make a start with, I had run-out of procrastination fodder I could legitimately use to prevent me from actually building the thing!
Writing An Application
The principle of how the application would work was this. There would be a store of data. When the JavaScript loaded it would load that data, loop through each player in the data, creating the HTML needed to represent each player as a row in the layout and placing them in the appropriate in/out section. Then interactions from the user would move a player from one state to another. Simple.
When it came to actually writing the application, the two big conceptual challenges that needed to be understood were:
How to represent the data for an application in a manner that could be easily extended and manipulated;
How to make the UI react when data was changed from user input.
One of the simplest ways to represent a data structure in JavaScript is with object notation. That sentence reads a little computer science-y. More simply, an ‘object’ in JavaScript lingo is a handy way of storing data.
Consider this JavaScript object assigned to a variable called ioState (for In/Out State):
var ioState = { Count: 0, // Running total of how many players RosterCount: 0; // Total number of possible players ToolsExposed: false, // Whether the UI for the tools is showing Players: [], // A holder for the players }
If you don’t really know JavaScript that well, you can probably at least grasp what’s going on: each line inside the curly braces is a property (or ‘key’ in JavaScript parlance) and value pair. You can set all sorts of things to a JavaScript key. For example, functions, arrays of other data or nested objects. Here’s an example:
var testObject = { testFunction: function() { return "sausages"; }, testArray: [3,7,9], nestedtObject { key1: "value1", key2: 2, } }
The net result is that using that kind of data structure you can get, and set, any of the keys of the object. For example, if we want to set the count of the ioState object to 7:
ioState.Count = 7;
If we want to set a piece of text to that value, the notation works like this:
aTextNode.textContent = ioState.Count;
You can see that getting values and setting values to that state object is simple in the JavaScript side of things. However, reflecting those changes in the User Interface is less so. This is the main area where frameworks and libraries seek to abstract away the pain.
In general terms, when it comes to dealing with updating the user interface based upon state, it’s preferable to avoid querying the DOM, as this is generally considered a sub-optimal approach.
Consider the In/Out interface. It’s typically showing a list of potential players for a game. They are vertically listed, one under the other, down the page.
Perhaps each player is represented in the DOM with a label wrapping a checkbox input. This way, clicking a player would toggle the player to ‘In’ by virtue of the label making the input ‘checked’.
To update our interface, we might have a ‘listener’ on each input element in the JavaScript. On a click or change, the function queries the DOM and counts how many of our player inputs are checked. On the basis of that count, we would then update something else in the DOM to show the user how many players are checked.
Let’s consider the cost of that basic operation. We are listening on multiple DOM nodes for the click/check of an input, then querying the DOM to see how many of a particular DOM type are checked, then writing something into the DOM to show the user, UI wise, the number of players we just counted.
The alternative would be to hold the application state as a JavaScript object in memory. A button/input click in the DOM could merely update the JavaScript object and then, based on that change in the JavaScript object, do a single-pass update of the all interface changes that are needed. We could skip querying the DOM to count the players as the JavaScript object would already hold that information.
So. Using a JavaScript object structure for the state seemed simple but flexible enough to encapsulate the application state at any given time. The theory of how this could be managed seemed sound enough too – this must be what phrases like ‘one-way data flow’ were all about? However, the first real trick would be in creating some code that would automatically update the UI based on any changes to that data.
The good news is that smarter people than I have already figured this stuff out (thank goodness!). People have been perfecting approaches to this kind of challenge since the dawn of applications. This category of problems is the bread and butter of ‘design patterns’. The moniker ‘design pattern’ sounded esoteric to me at first but after digging just a little it all started to sound less computer science and more common sense.
Design Patterns
A design pattern, in computer science lexicon, is a pre-defined and proven way of solving a common technical challenge. Think of design patterns as the coding equivalent of a cooking recipe.
Perhaps the most famous literature on design patterns is "Design Patterns: Elements of Reusable Object-Oriented Software" from back in 1994. Although that deals with C++ and smalltalk the concepts are transferable. For JavaScript, Addy Osmani’s "Learning JavaScript Design Patterns" covers similar ground. You can also read it online for free here.
Observer Pattern
Typically design patterns are split into three groups: Creational, Structural and Behavioural. I was looking for something Behavioural that helped to deal with communicating changes around the different parts of the application.
More recently, I have seen and read a really great deep-dive on implementing reactivity inside an app by Gregg Pollack. There is both a blog post and video for your enjoyment here.
When reading the opening description of the ‘Observer’ pattern in Learning JavaScript Design Patterns I was pretty sure it was the pattern for me. It is described thus:
The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state. When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic of the notification).
The key to my excitement was that this seemed to offer some way of things updating themselves when needed.
Suppose the user clicked a player named “Betty” to select that she was ‘In’ for the game. A few things might need to happen in the UI:
Add 1 to the playing count
Remove Betty from the ‘Out’ pool of players
Add Betty to the ‘In’ pool of players
The app would also need to update the data that represented the UI. What I was very keen to avoid was this:
playerName.addEventListener("click", playerToggle); function playerToggle() { if (inPlayers.includes(e.target.textContent)) { setPlayerOut(e.target.textContent); decrementPlayerCount(); } else { setPlayerIn(e.target.textContent); incrementPlayerCount(); } }
The aim was to have an elegant data flow that updated what was needed in the DOM when and if the central data was changed.
With an Observer pattern, it was possible to send out updates to the state and therefore the user interface quite succinctly. Here is an example, the actual function used to add a new player to the list:
function itemAdd(itemString: string) { let currentDataSet = getCurrentDataSet(); var newPerson = new makePerson(itemString); io.items[currentDataSet].EventData.splice(0, 0, newPerson); io.notify({ items: io.items }); }
The part relevant to the Observer pattern there being the io.notify method. As that shows us modifying the items part of the application state, let me show you the observer that listened for changes to ‘items’:
io.addObserver({ props: ["items"], callback: function renderItems() { // Code that updates anything to do with items... } });
We have a notify method that makes changes to the data and then Observers to that data that respond when properties they are interested in are updated.
With this approach, the app could have observables watching for changes in any property of the data and run a function whenever a change occurred.
If you are interested in the Observer pattern I opted for, I describe it more fully here.
There was now an approach for updating the UI effectively based on state. Peachy. However, this still left me with two glaring issues.
One was how to store the state across page reloads/sessions and the fact that despite the UI working, visually, it just wasn’t very ‘app like’. For example, if a button was pressed the UI instantly changed on screen. It just wasn’t particularly compelling.
Let’s deal with the storage side of things first.
Saving State
My primary interest from a development side entering into this centered on understanding how app interfaces could be built and made interactive with JavaScript. How to store and retrieve data from a server or tackle user-authentication and logins was ‘out of scope’.
Therefore, instead of hooking up to a web service for the data storage needs, I opted to keep all data on the client. There are a number of web platform methods of storing data on a client. I opted for localStorage.
The API for localStorage is incredibly simple. You set and get data like this:
// Set something localStorage.setItem("yourKey", "yourValue"); // Get something localStorage.getItem("yourKey");
LocalStorage has a setItem method that you pass two strings to. The first is the name of the key you want to store the data with and the second string is the actual string you want to store. The getItem method takes a string as an argument that returns to you whatever is stored under that key in localStorage. Nice and simple.
However, amongst the reasons to not use localStorage is the fact that everything has to be saved as a ‘string’. This means you can’t directly store something like an array or object. For example, try running these commands in your browser console:
// Set something localStorage.setItem("myArray", [1, 2, 3, 4]); // Get something localStorage.getItem("myArray"); // Logs "1,2,3,4"
Even though we tried to set the value of ‘myArray’ as an array; when we retrieved it, it had been stored as a string (note the quote marks around ‘1,2,3,4’).
You can certainly store objects and arrays with localStorage but you need to be mindful that they need converting back and forth from strings.
So, in order to write state data into localStorage it was written to a string with the JSON.stringify() method like this:
const storage = window.localStorage; storage.setItem("players", JSON.stringify(io.items));
When the data needed retrieving from localStorage, the string was turned back into usable data with the JSON.parse() method like this:
const players = JSON.parse(storage.getItem("players"));
Using localStorage meant everything was on the client and that meant no 3rd party services or data storage concerns.
Data was now persisting refreshes and sessions — Yay! The bad news was that localStorage does not survive a user emptying their browser data. When someone did that, all their In/Out data would be lost. That’s a serious shortcoming.
It’s not hard to appreciate that `localStorage` probably isn’t the best solution for 'proper' applications. Besides the aforementioned string issue, it is also slow for serious work as it blocks the 'main thread'. Alternatives are coming, like KV Storage but for now, make a mental note to caveat its use based on suitability.
Despite the fragility of saving data locally on a users device, hooking up to a service or database was resisted. Instead, the issue was side-stepped by offering a ‘load/save’ option. This would allow any user of In/Out to save their data as a JSON file which could be loaded back into the app if needed.
This worked well on Android but far less elegantly for iOS. On an iPhone, it resulted in a splurge of text on screen like this:
(Large preview)
As you can imagine, I was far from alone in berating Apple via WebKit about this shortcoming. The relevant bug was here.
At the time of writing this bug has a solution and patch but has yet to make its way into iOS Safari. Allegedly, iOS13 fixes it but it’s that’s in Beta as I write.
So, for my minimum viable product, that was storage addressed. Now it was time to attempt to make things more ‘app-like’!
App-I-Ness
Turns out after many discussions with many people, defining exactly what ‘app like’ means is quite difficult.
Ultimately, I settled on ‘app-like’ being synonymous with a visual slickness usually missing from the web. When I think of the apps that feel good to use they all feature motion. Not gratuitous, but motion that adds to the story of your actions. It might be the page transitions between screens, the manner in which menus pop into existence. It’s hard to describe in words but most of us know it when we see it.
The first piece of visual flair needed was shifting player names up or down from ‘In’ to ‘Out’ and vice-versa when selected. Making a player instantly move from one section to the other was straightforward but certainly not ‘app-like’. An animation as a player name was clicked would hopefully emphasize the result of that interaction – the player moving from one category to another.
Like many of these kinds of visual interactions, their apparent simplicity belies the complexity involved in actually getting it working well.
It took a few iterations to get the movement right but the basic logic was this:
Once a ‘player’ is clicked, capture where that player is, geometrically, on the page;
Measure how far away the top of the area is the player needs to move to if going up (‘In’) and how far away the bottom is, if going down (‘Out’);
If going up, a space equal to the height of the player row needs to be left as the player moves up and the players above should collapse downwards at the same rate as the time it takes for the player to travel up to land in the space vacated by the existing ‘In’ players (if any exist) coming down;
If a player is going ‘Out’ and moving down, everything else needs to move up to the space left and the player needs to end up below any current ‘Out’ players.
Phew! It was trickier than I thought in English — never mind JavaScript!
There were additional complexities to consider and trial such as transition speeds. At the outset, it wasn’t obvious whether a constant speed of movement (e.g. 20px per 20ms), or a constant duration for the movement (e.g. 0.2s) would look better. The former was slightly more complicated as the speed needed to be computed ‘on the fly’ based upon how far the player needed to travel — greater distance requiring a longer transition duration.
However, it turned out that a constant transition duration was not just simpler in code; it actually produced a more favorable effect. The difference was subtle but these are the kind of choices you can only determine once you have seen both options.
Every so often whilst trying to nail this effect, a visual glitch would catch the eye but it was impossible to deconstruct in real time. I found the best debugging process was creating a QuickTime recording of the animation and then going through it a frame at a time. Invariably this revealed the problem quicker than any code based debugging.
Looking at the code now, I can appreciate that on something beyond my humble app, this functionality could almost certainly be written more effectively. Given that the app would know the number of players and know the fixed height of the slats, it should be entirely possible to make all distance calculations in the JavaScript alone, without any DOM reading.
It’s not that what was shipped doesn’t work, it’s just that it isn’t the kind of code solution you would showcase on the Internet. Oh, wait.
Other ‘app like’ interactions were much easier to pull off. Instead of menus simply snapping in and out with something as simple as toggling a display property, a lot of mileage was gained by simply exposing them with a little more finesse. It was still triggered simply but CSS was doing all the heavy lifting:
.io-EventLoader { position: absolute; top: 100%; margin-top: 5px; z-index: 100; width: 100%; opacity: 0; transition: all 0.2s; pointer-events: none; transform: translateY(-10px); [data-evswitcher-showing="true"] & { opacity: 1; pointer-events: auto; transform: none; } }
There when the data-evswitcher-showing="true" attribute was toggled on a parent element, the menu would fade in, transform back into its default position and pointer events would be re-enabled so the menu could receive clicks.
ECSS Style Sheet Methodology
You’ll notice in that prior code that from an authoring point of view, CSS overrides are being nested within a parent selector. That’s the way I always favor writing UI style sheets; a single source of truth for each selector and any overrides for that selector encapsulated within a single set of braces. It’s a pattern that requires the use of a CSS processor (Sass, PostCSS, LESS, Stylus, et al) but I feel is the only positive way to make use of nesting functionality.
I’d cemented this approach in my book, Enduring CSS and despite there being a plethora of more involved methods available to write CSS for interface elements, ECSS has served me and the large development teams I work with well since the approach was first documented way back in 2014! It proved just as effective in this instance.
Partialling The TypeScript
Even without a CSS processor or superset language like Sass, CSS has had the ability to import one or more CSS files into another with the import directive:
@import "other-file.css";
When beginning with JavaScript I was surprised there was no equivalent. Whenever code files get longer than a screen or so high, it always feels like splitting it into smaller pieces would be beneficial.
Another bonus to using TypeScript was that it has a beautifully simple way of splitting code into files and importing them when needed.
This capability pre-dated native JavaScript modules and was a great convenience feature. When TypeScript was compiled it stitched it all back to a single JavaScript file. It meant it was possible to easily break up the application code into manageable partial files for authoring and import then into the main file easily. The top of the main inout.ts looked like this:
/// <reference path="defaultData.ts" /> /// <reference path="splitTeams.ts" /> /// <reference path="deleteOrPaidClickMask.ts" /> /// <reference path="repositionSlat.ts" /> /// <reference path="createSlats.ts" /> /// <reference path="utils.ts" /> /// <reference path="countIn.ts" /> /// <reference path="loadFile.ts" /> /// <reference path="saveText.ts" /> /// <reference path="observerPattern.ts" /> /// <reference path="onBoard.ts" />
This simple house-keeping and organization task helped enormously.
Multiple Events
At the outset, I felt that from a functionality point of view, a single event, like “Tuesday Night Football” would suffice. In that scenario, if you loaded In/Out up you just added/removed or moved players in or out and that was that. There was no notion of multiple events.
I quickly decided that (even going for a minimum viable product) this would make for a pretty limited experience. What if somebody organized two games on different days, with a different roster of players? Surely In/Out could/should accommodate that need? It didn’t take too long to re-shape the data to make this possible and amend the methods needed to load in a different set.
At the outset, the default data set looked something like this:
var defaultData = [ { name: "Daz", paid: false, marked: false, team: "", in: false }, { name: "Carl", paid: false, marked: false, team: "", in: false }, { name: "Big Dave", paid: false, marked: false, team: "", in: false }, { name: "Nick", paid: false, marked: false, team: "", in: false } ];
An array containing an object for each player.
After factoring in multiple events it was amended to look like this:
var defaultDataV2 = [ { EventName: "Tuesday Night Footy", Selected: true, EventData: [ { name: "Jack", marked: false, team: "", in: false }, { name: "Carl", marked: false, team: "", in: false }, { name: "Big Dave", marked: false, team: "", in: false }, { name: "Nick", marked: false, team: "", in: false }, { name: "Red Boots", marked: false, team: "", in: false }, { name: "Gaz", marked: false, team: "", in: false }, { name: "Angry Martin", marked: false, team: "", in: false } ] }, { EventName: "Friday PM Bank Job", Selected: false, EventData: [ { name: "Mr Pink", marked: false, team: "", in: false }, { name: "Mr Blonde", marked: false, team: "", in: false }, { name: "Mr White", marked: false, team: "", in: false }, { name: "Mr Brown", marked: false, team: "", in: false } ] }, { EventName: "WWII Ladies Baseball", Selected: false, EventData: [ { name: "C Dottie Hinson", marked: false, team: "", in: false }, { name: "P Kit Keller", marked: false, team: "", in: false }, { name: "Mae Mordabito", marked: false, team: "", in: false } ] } ];
The new data was an array with an object for each event. Then in each event was an EventData property that was an array with player objects in as before.
It took much longer to re-consider how the interface could best deal with this new capability.
From the outset, the design had always been very sterile. Considering this was also supposed to be an exercise in design, I didn’t feel I was being brave enough. So a little more visual flair was added, starting with the header. This is what I mocked up in Sketch:
Revised design mockup. (Large preview)
It wasn’t going to win awards but it was certainly more arresting than where it started.
Aesthetics aside, it wasn’t until somebody else pointed it out, that I appreciated the big plus icon in the header was very confusing. Most people thought it was a way to add another event. In reality, it switched to an ‘Add Player’ mode with a fancy transition that let you type in the name of the player in the same place the event name was currently.
This was another instance where fresh eyes were invaluable. It was also an important lesson in letting go. The honest truth was I had held on to the input mode transition in the header because I felt it was cool and clever. However, the fact was it was not serving the design and therefore the application as a whole.
This was changed in the live version. Instead, the header just deals with events — a more common scenario. Meanwhile, adding players is done from a sub-menu. This gives the app a much more understandable hierarchy.
The other lesson learned here was that whenever possible, it’s hugely beneficial to get candid feedback from peers. If they are good and honest people, they won’t let you give yourself a pass!
Summary: My Code Stinks
Right. So far, so normal tech-adventure retrospective piece; these things are ten a penny on Medium! The formula goes something like this: the dev details how they smashed down all obstacles to release a finely tuned piece of software into the Internets and then pick up an interview at Google or got acqui-hired somewhere. However, the truth of the matter is that I was a first-timer at this app-building malarkey so the code ultimately shipped as the ‘finished’ application stunk to high heaven!
For example, the Observer pattern implementation used worked very well. I was organized and methodical at the outset but that approach ‘went south’ as I became more desperate to finish things off. Like a serial dieter, old familiar habits crept back in and the code quality subsequently dropped.
Looking now at the code shipped, it is a less than ideal hodge-bodge of clean observer pattern and bog-standard event listeners calling functions. In the main inout.ts file there are over 20 querySelector method calls; hardly a poster child for modern application development!
I was pretty sore about this at the time, especially as at the outset I was aware this was a trap I didn’t want to fall into. However, in the months that have since passed, I’ve become more philosophical about it.
The final post in this series reflects on finding the balance between silvery-towered code idealism and getting things shipped. It also covers the most important lessons learned during this process and my future aspirations for application development.
(dm, yk, il, ra)
0 notes