Skip to content

Commit a931e2e

Browse files
authored
Merge pull request #83 from mrgoonie/goon
chore(release): 1.5.0 stable release
2 parents 3c407f3 + 05c0289 commit a931e2e

11 files changed

Lines changed: 238 additions & 20 deletions

File tree

.github/workflows/beta-release.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,21 @@ jobs:
7676
npm install
7777
shell: pwsh
7878

79+
- name: Get version from package.json
80+
if: steps.check.outputs.should_release == 'true'
81+
id: version
82+
run: |
83+
$version = (Get-Content package.json | ConvertFrom-Json).version
84+
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
85+
shell: pwsh
86+
7987
- name: Build portable executable
8088
if: steps.check.outputs.should_release == 'true'
81-
run: wails build -clean -platform windows/amd64
89+
run: wails build -clean -platform windows/amd64 -ldflags "-X main.Version=${{ steps.version.outputs.VERSION }}"
8290

8391
- name: Build NSIS installer
8492
if: steps.check.outputs.should_release == 'true'
85-
run: wails build -platform windows/amd64 -nsis
93+
run: wails build -platform windows/amd64 -nsis -ldflags "-X main.Version=${{ steps.version.outputs.VERSION }}"
8694

8795
- name: Upload build artifacts
8896
if: steps.check.outputs.should_release == 'true'

.github/workflows/release.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,21 @@ jobs:
7676
npm install
7777
shell: pwsh
7878

79+
- name: Get version from package.json
80+
if: steps.check.outputs.should_release == 'true'
81+
id: version
82+
run: |
83+
$version = (Get-Content package.json | ConvertFrom-Json).version
84+
echo "VERSION=$version" >> $env:GITHUB_OUTPUT
85+
shell: pwsh
86+
7987
- name: Build portable executable
8088
if: steps.check.outputs.should_release == 'true'
81-
run: wails build -clean -platform windows/amd64
89+
run: wails build -clean -platform windows/amd64 -ldflags "-X main.Version=${{ steps.version.outputs.VERSION }}"
8290

8391
- name: Build NSIS installer
8492
if: steps.check.outputs.should_release == 'true'
85-
run: wails build -platform windows/amd64 -nsis
93+
run: wails build -platform windows/amd64 -nsis -ldflags "-X main.Version=${{ steps.version.outputs.VERSION }}"
8694

8795
- name: Upload build artifacts
8896
if: steps.check.outputs.should_release == 'true'

app.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ import (
2828
winEnum "winshot/internal/windows"
2929
)
3030

31+
// Version is set at build time via ldflags
32+
var Version = "dev"
33+
3134
// App struct
3235
type App struct {
3336
ctx context.Context
@@ -81,8 +84,8 @@ func (a *App) startup(ctx context.Context) {
8184
println("Warning: failed to start overlay manager:", err.Error())
8285
}
8386

84-
// Initialize system tray
85-
a.trayIcon = tray.NewTrayIcon("WinShot - Screenshot Tool")
87+
// Initialize system tray with version in tooltip
88+
a.trayIcon = tray.NewTrayIcon(fmt.Sprintf("WinShot v%s", Version))
8689
a.trayIcon.SetCallback(a.onTrayMenu)
8790
a.trayIcon.SetOnShow(func() {
8891
runtime.WindowShow(a.ctx)

frontend/src/App.tsx

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { StatusBar } from './components/status-bar';
1313
import { AnnotationToolbar } from './components/annotation-toolbar';
1414
import { ExportToolbar } from './components/export-toolbar';
1515
import { CropToolbar } from './components/crop-toolbar';
16-
import { CaptureResult, CaptureMode, WindowInfo, Annotation, EditorTool, OutputRatio, CropArea, CropAspectRatio, CropState, LibraryImage } from './types';
16+
import { CaptureResult, CaptureMode, WindowInfo, Annotation, EditorTool, OutputRatio, CropArea, CropAspectRatio, CropState, BorderType, LibraryImage } from './types';
1717
import {
1818
CaptureFullscreen,
1919
CaptureWindow,
@@ -50,6 +50,11 @@ const DEFAULT_EDITOR_SETTINGS = {
5050
showBackground: true,
5151
inset: 0,
5252
autoBackground: true,
53+
borderEnabled: false,
54+
borderWeight: 2,
55+
borderColor: '#000000',
56+
borderOpacity: 100,
57+
borderType: 'center' as BorderType,
5358
};
5459

5560
// Helper to parse ratio string into numeric ratio
@@ -145,6 +150,11 @@ function App() {
145150
const [autoBackground, setAutoBackground] = useState(DEFAULT_EDITOR_SETTINGS.autoBackground);
146151
const [extractedColor, setExtractedColor] = useState<string | null>(null);
147152
const [insetBackgroundColor, setInsetBackgroundColor] = useState<string | null>(null);
153+
const [borderEnabled, setBorderEnabled] = useState(DEFAULT_EDITOR_SETTINGS.borderEnabled);
154+
const [borderWeight, setBorderWeight] = useState(DEFAULT_EDITOR_SETTINGS.borderWeight);
155+
const [borderColor, setBorderColor] = useState(DEFAULT_EDITOR_SETTINGS.borderColor);
156+
const [borderOpacity, setBorderOpacity] = useState(DEFAULT_EDITOR_SETTINGS.borderOpacity);
157+
const [borderType, setBorderType] = useState<BorderType>(DEFAULT_EDITOR_SETTINGS.borderType);
148158
const [editorSettingsLoaded, setEditorSettingsLoaded] = useState(false);
149159
// Stores settings when background is hidden, so they can be restored
150160
const [savedBackgroundSettings, setSavedBackgroundSettings] = useState<{
@@ -238,6 +248,11 @@ function App() {
238248
if (cfg.autoBackground !== undefined) setAutoBackground(cfg.autoBackground);
239249
if (cfg.insetBackgroundColor) setInsetBackgroundColor(cfg.insetBackgroundColor);
240250
if (cfg.shapeCornerRadius !== undefined) setShapeCornerRadius(cfg.shapeCornerRadius);
251+
if (cfg.borderEnabled !== undefined) setBorderEnabled(cfg.borderEnabled);
252+
if (cfg.borderWeight !== undefined) setBorderWeight(cfg.borderWeight);
253+
if (cfg.borderColor) setBorderColor(cfg.borderColor);
254+
if (cfg.borderOpacity !== undefined) setBorderOpacity(cfg.borderOpacity);
255+
if (cfg.borderType) setBorderType(cfg.borderType as BorderType);
241256
}
242257
} catch (err) {
243258
console.error('Failed to load editor settings:', err);
@@ -261,10 +276,15 @@ function App() {
261276
autoBackground,
262277
insetBackgroundColor: insetBackgroundColor || undefined,
263278
shapeCornerRadius,
279+
borderEnabled,
280+
borderWeight,
281+
borderColor,
282+
borderOpacity,
283+
borderType,
264284
}).catch(err => {
265285
console.error('Failed to save editor settings:', err);
266286
});
267-
}, [padding, cornerRadius, shadowSize, backgroundColor, outputRatio, showBackground, inset, autoBackground, insetBackgroundColor, shapeCornerRadius, editorSettingsLoaded]);
287+
}, [padding, cornerRadius, shadowSize, backgroundColor, outputRatio, showBackground, inset, autoBackground, insetBackgroundColor, shapeCornerRadius, borderEnabled, borderWeight, borderColor, borderOpacity, borderType, editorSettingsLoaded]);
268288

269289
// Load export settings from config on startup
270290
useEffect(() => {
@@ -1617,6 +1637,11 @@ function App() {
16171637
outputRatio={outputRatio}
16181638
inset={inset}
16191639
insetBackgroundColor={insetBackgroundColor ?? extractedColor ?? undefined}
1640+
borderEnabled={borderEnabled}
1641+
borderWeight={borderWeight}
1642+
borderColor={borderColor}
1643+
borderOpacity={borderOpacity}
1644+
borderType={borderType}
16201645
stageRef={stageRef}
16211646
activeTool={activeTool}
16221647
annotations={annotations}
@@ -1656,6 +1681,11 @@ function App() {
16561681
autoBackground={autoBackground}
16571682
extractedColor={extractedColor}
16581683
insetBackgroundColor={insetBackgroundColor}
1684+
borderEnabled={borderEnabled}
1685+
borderWeight={borderWeight}
1686+
borderColor={borderColor}
1687+
borderOpacity={borderOpacity}
1688+
borderType={borderType}
16591689
onPaddingChange={setPadding}
16601690
onCornerRadiusChange={setCornerRadius}
16611691
onShadowSizeChange={setShadowSize}
@@ -1665,6 +1695,11 @@ function App() {
16651695
onInsetChange={setInset}
16661696
onAutoBackgroundChange={handleAutoBackgroundChange}
16671697
onInsetBackgroundColorChange={setInsetBackgroundColor}
1698+
onBorderEnabledChange={setBorderEnabled}
1699+
onBorderWeightChange={setBorderWeight}
1700+
onBorderColorChange={setBorderColor}
1701+
onBorderOpacityChange={setBorderOpacity}
1702+
onBorderTypeChange={setBorderType}
16681703
/>
16691704
)}
16701705
</div>

frontend/src/components/editor-canvas.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useRef, useState, useEffect, useCallback } from 'react';
22
import { Stage, Layer, Image as KonvaImage, Rect, Group } from 'react-konva';
33
import useImage from 'use-image';
44
import Konva from 'konva';
5-
import { CaptureResult, Annotation, AnnotationType, EditorTool, OutputRatio, CropArea, CropAspectRatio } from '../types';
5+
import { CaptureResult, Annotation, AnnotationType, EditorTool, OutputRatio, CropArea, CropAspectRatio, BorderType } from '../types';
66
import { AnnotationShapes } from './annotation-shapes';
77
import { SpotlightOverlay } from './spotlight-overlay';
88
import { CropOverlay } from './crop-overlay';
@@ -17,6 +17,11 @@ interface EditorCanvasProps {
1717
outputRatio: OutputRatio;
1818
inset: number; // 0-50 percentage for screenshot scaling
1919
insetBackgroundColor?: string; // Background color revealed when inset > 0 (extracted from screenshot)
20+
borderEnabled: boolean;
21+
borderWeight: number;
22+
borderColor: string;
23+
borderOpacity: number;
24+
borderType: BorderType;
2025
stageRef?: React.RefObject<Konva.Stage>;
2126
// Annotation props
2227
activeTool: EditorTool;
@@ -178,6 +183,11 @@ export function EditorCanvas({
178183
outputRatio,
179184
inset,
180185
insetBackgroundColor,
186+
borderEnabled,
187+
borderWeight,
188+
borderColor,
189+
borderOpacity,
190+
borderType,
181191
stageRef,
182192
activeTool,
183193
annotations,
@@ -789,6 +799,28 @@ export function EditorCanvas({
789799
displayWidth={innerWidth}
790800
displayHeight={innerHeight}
791801
/>
802+
803+
{/* Border rect - rendered above image */}
804+
{borderEnabled && borderWeight > 0 && (() => {
805+
// Calculate border offset based on type: outside (-0.5), center (0), inside (+0.5)
806+
const offsetMultiplier = borderType === 'outside' ? -1 : borderType === 'inside' ? 1 : 0;
807+
const offset = (borderWeight / 2) * offsetMultiplier;
808+
// Adjust corner radius to match image's curve when border is offset
809+
const borderCornerRadius = Math.max(0, cornerRadius - offset);
810+
return (
811+
<Rect
812+
x={offset}
813+
y={offset}
814+
width={innerWidth - offset * 2}
815+
height={innerHeight - offset * 2}
816+
stroke={borderColor}
817+
strokeWidth={borderWeight}
818+
cornerRadius={borderCornerRadius}
819+
opacity={borderOpacity / 100}
820+
listening={false}
821+
/>
822+
);
823+
})()}
792824
</Group>
793825

794826
{/* Annotations */}

frontend/src/components/settings-panel.tsx

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useRef, useState, useEffect } from 'react';
2-
import { OutputRatio } from '../types';
2+
import { OutputRatio, BorderType } from '../types';
33
import { GetBackgroundImages, SaveBackgroundImages } from '../../wailsjs/go/main/App';
44
import { X, ImagePlus, Eye, EyeOff } from 'lucide-react';
55

@@ -70,6 +70,11 @@ interface SettingsPanelProps {
7070
autoBackground: boolean;
7171
extractedColor: string | null;
7272
insetBackgroundColor: string | null;
73+
borderEnabled: boolean;
74+
borderWeight: number;
75+
borderColor: string;
76+
borderOpacity: number;
77+
borderType: BorderType;
7378
onPaddingChange: (value: number) => void;
7479
onCornerRadiusChange: (value: number) => void;
7580
onShadowSizeChange: (value: number) => void;
@@ -79,6 +84,11 @@ interface SettingsPanelProps {
7984
onInsetChange: (value: number) => void;
8085
onAutoBackgroundChange: (value: boolean) => void;
8186
onInsetBackgroundColorChange: (value: string) => void;
87+
onBorderEnabledChange: (value: boolean) => void;
88+
onBorderWeightChange: (value: number) => void;
89+
onBorderColorChange: (value: string) => void;
90+
onBorderOpacityChange: (value: number) => void;
91+
onBorderTypeChange: (value: BorderType) => void;
8292
}
8393

8494
const GRADIENT_PRESETS = [
@@ -127,6 +137,11 @@ export function SettingsPanel({
127137
autoBackground,
128138
extractedColor,
129139
insetBackgroundColor,
140+
borderEnabled,
141+
borderWeight,
142+
borderColor,
143+
borderOpacity,
144+
borderType,
130145
onPaddingChange,
131146
onCornerRadiusChange,
132147
onShadowSizeChange,
@@ -136,6 +151,11 @@ export function SettingsPanel({
136151
onInsetChange,
137152
onAutoBackgroundChange,
138153
onInsetBackgroundColorChange,
154+
onBorderEnabledChange,
155+
onBorderWeightChange,
156+
onBorderColorChange,
157+
onBorderOpacityChange,
158+
onBorderTypeChange,
139159
}: SettingsPanelProps) {
140160
const fileInputRef = useRef<HTMLInputElement>(null);
141161
const [uploadedImages, setUploadedImages] = useState<string[]>([]);
@@ -298,6 +318,93 @@ export function SettingsPanel({
298318
/>
299319
</div>
300320

321+
{/* Border Section */}
322+
<div className="mb-6">
323+
<div className="flex items-center justify-between mb-3">
324+
<label className="text-sm text-slate-300 font-medium">Border</label>
325+
<button
326+
onClick={() => onBorderEnabledChange(!borderEnabled)}
327+
className={`flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium transition-all duration-200
328+
${borderEnabled
329+
? 'bg-violet-500/20 text-violet-300 hover:bg-violet-500/30'
330+
: 'bg-slate-500/20 text-slate-400 hover:bg-slate-500/30'
331+
}`}
332+
>
333+
{borderEnabled ? <Eye className="w-3.5 h-3.5" /> : <EyeOff className="w-3.5 h-3.5" />}
334+
<span>{borderEnabled ? 'Enabled' : 'Disabled'}</span>
335+
</button>
336+
</div>
337+
338+
{/* Border controls - disabled when border is off */}
339+
<div className={`space-y-4 transition-opacity duration-200 ${!borderEnabled ? 'opacity-50 pointer-events-none' : ''}`}>
340+
{/* Weight */}
341+
<div>
342+
<div className="flex justify-between items-center mb-2">
343+
<label className="text-sm text-slate-300 font-medium">Weight</label>
344+
<span className="text-xs text-amber-400 font-semibold bg-amber-500/10 px-2 py-0.5 rounded-full">{borderWeight}px</span>
345+
</div>
346+
<input
347+
type="range"
348+
min="1"
349+
max="50"
350+
value={borderWeight}
351+
onChange={(e) => onBorderWeightChange(Number(e.target.value))}
352+
className="w-full"
353+
/>
354+
</div>
355+
356+
{/* Color */}
357+
<div className="flex items-center gap-3">
358+
<input
359+
type="color"
360+
value={borderColor}
361+
onChange={(e) => onBorderColorChange(e.target.value)}
362+
className="w-10 h-10 rounded-lg cursor-pointer bg-white/5 border border-white/10 hover:border-white/20 transition-colors"
363+
/>
364+
<div className="text-xs">
365+
<div className="text-slate-400">Color</div>
366+
<div className="text-slate-300 font-mono">{borderColor}</div>
367+
</div>
368+
</div>
369+
370+
{/* Opacity */}
371+
<div>
372+
<div className="flex justify-between items-center mb-2">
373+
<label className="text-sm text-slate-300 font-medium">Opacity</label>
374+
<span className="text-xs text-blue-400 font-semibold bg-blue-500/10 px-2 py-0.5 rounded-full">{borderOpacity}%</span>
375+
</div>
376+
<input
377+
type="range"
378+
min="0"
379+
max="100"
380+
value={borderOpacity}
381+
onChange={(e) => onBorderOpacityChange(Number(e.target.value))}
382+
className="w-full"
383+
/>
384+
</div>
385+
386+
{/* Type */}
387+
<div>
388+
<label className="block text-sm text-slate-300 font-medium mb-2">Type</label>
389+
<div className="grid grid-cols-3 gap-1.5">
390+
{(['outside', 'center', 'inside'] as BorderType[]).map((type) => (
391+
<button
392+
key={type}
393+
onClick={() => onBorderTypeChange(type)}
394+
className={`px-2 py-1.5 text-xs rounded-lg transition-all duration-200 font-medium capitalize
395+
${borderType === type
396+
? 'bg-gradient-to-r from-violet-500 to-purple-600 text-white shadow-lg shadow-violet-500/30'
397+
: 'bg-white/5 text-slate-300 hover:bg-white/10 border border-white/5'
398+
}`}
399+
>
400+
{type}
401+
</button>
402+
))}
403+
</div>
404+
</div>
405+
</div>
406+
</div>
407+
301408
{/* Shadow/Blur */}
302409
<div className="mb-6">
303410
<div className="flex justify-between items-center mb-2">

frontend/src/types/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ export interface CropState {
8787
// Output canvas ratio - determines the final export dimensions
8888
export type OutputRatio = 'auto' | '1:1' | '4:3' | '3:2' | '16:9' | '5:3' | '9:16' | '3:4' | '2:3';
8989

90+
// Border position type - determines where border stroke is rendered relative to edge
91+
export type BorderType = 'outside' | 'center' | 'inside';
92+
9093
// App configuration types
9194
export interface HotkeyConfig {
9295
fullscreen: string;

0 commit comments

Comments
 (0)