Skip to content

Commit d2ca58d

Browse files
err solved, windows installer
1 parent 0dcf44a commit d2ca58d

File tree

5 files changed

+231
-14
lines changed

5 files changed

+231
-14
lines changed

Routes/router.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ sessionStore.on('error', (error) => {
3030
});
3131

3232
// Middleware setup
33-
router.use(bodyParser.json()); // Parse JSON bodies
34-
router.use(bodyParser.urlencoded({ extended: true })); // Parse URL-encoded bodies
33+
router.use(bodyParser.json({ limit: '50mb' })); // Parse JSON bodies with 50MB limit
34+
router.use(bodyParser.urlencoded({ extended: true, limit: '50mb' })); // Parse URL-encoded bodies with 50MB limit
3535
router.use(express.static(path.join(__dirname, '../public'))); // Serve static files 📁
3636

3737
// View engine setup

controllers/controller.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ const getCommunityPage = async (req, res) => {
224224
problems: problems,
225225
memberDetails: memberDetails,
226226
isOwner: isOwner,
227-
activeIssues: activeIssues
227+
activeIssues: activeIssues,
228+
success: req.query.success,
229+
error: req.query.error
228230
});
229231

230232
} catch (error) {

views/community.pug

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,29 @@ html(lang="en")
389389
background: rgba(99, 102, 241, 0.1);
390390
}
391391

392+
/* Message Styles */
393+
.message {
394+
padding: 1rem 1.5rem;
395+
border-radius: 8px;
396+
margin-bottom: 1rem;
397+
display: flex;
398+
align-items: center;
399+
gap: 0.75rem;
400+
font-weight: 500;
401+
}
402+
403+
.message.success {
404+
background: rgba(16, 185, 129, 0.1);
405+
border-left: 4px solid var(--secondary);
406+
color: var(--secondary);
407+
}
408+
409+
.message.error {
410+
background: rgba(239, 68, 68, 0.1);
411+
border-left: 4px solid var(--error);
412+
color: var(--error);
413+
}
414+
392415
/* Raise Problem Section */
393416
.raise-problem-section {
394417
background: var(--dark);
@@ -848,6 +871,15 @@ body
848871
// Main Content
849872
main.community-main
850873
.container
874+
// Display success/error messages
875+
if success
876+
.message.success
877+
i.fas.fa-check-circle
878+
= success
879+
if error
880+
.message.error
881+
i.fas.fa-exclamation-triangle
882+
= error
851883
// Community Header
852884
.community-header
853885
h1= community.Name
@@ -1469,14 +1501,17 @@ body
14691501
form(action=`/com/${community.gp_id}/settings`, method="POST")
14701502
.form-group
14711503
label
1504+
input(type="hidden", name="isPublic", value="false")
14721505
input(type="checkbox", name="isPublic", value="true", checked=community.isPublic)
14731506
| Public - Anyone can discover the community
14741507
.form-group
14751508
label
1509+
input(type="hidden", name="allowPublicJoin", value="false")
14761510
input(type="checkbox", name="allowPublicJoin", value="true", checked=(community.settings && community.settings.allowPublicJoin))
14771511
| Allow public to join without invite
14781512
.form-group
14791513
label
1514+
input(type="hidden", name="onlyWorkersCanSolve", value="false")
14801515
input(type="checkbox", name="onlyWorkersCanSolve", value="true", checked=(community.settings && community.settings.onlyWorkersCanSolve))
14811516
| Only workers can mark problems as solved
14821517
button.btn.btn-primary(type="submit")
@@ -2095,6 +2130,25 @@ script.
20952130
return 'audio/webm';
20962131
}
20972132

2133+
// Timer function for recording
2134+
function updateTimer() {
2135+
if (!recordingStartTime) return;
2136+
const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
2137+
const minutes = Math.floor(elapsed / 60);
2138+
const seconds = elapsed % 60;
2139+
recordingTimer.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
2140+
}
2141+
2142+
// Convert blob to base64
2143+
function blobToBase64(blob) {
2144+
return new Promise((resolve, reject) => {
2145+
const reader = new FileReader();
2146+
reader.onload = () => resolve(reader.result);
2147+
reader.onerror = reject;
2148+
reader.readAsDataURL(blob);
2149+
});
2150+
}
2151+
20982152
let mediaRecorder;
20992153
let audioChunks = [];
21002154
let recordingStartTime;
@@ -2123,15 +2177,31 @@ script.
21232177

21242178
// Start Recording with supported format
21252179
startBtn.addEventListener('click', async () => {
2180+
let stream;
21262181
try {
2127-
const stream = await navigator.mediaDevices.getUserMedia({
2182+
stream = await navigator.mediaDevices.getUserMedia({
21282183
audio: {
21292184
echoCancellation: true,
21302185
noiseSuppression: true,
21312186
sampleRate: 44100 // Higher quality for MP3
21322187
}
21332188
});
2189+
} catch (error) {
2190+
console.error('Error accessing microphone with advanced constraints:', error);
21342191

2192+
// Try with simpler constraints
2193+
try {
2194+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2195+
console.log('Microphone access granted with basic constraints');
2196+
} catch (simpleError) {
2197+
console.error('Error accessing microphone with basic constraints:', simpleError);
2198+
alert('Could not access microphone. Please check browser permissions and try again.');
2199+
return;
2200+
}
2201+
}
2202+
2203+
// If we got here, we have a stream - proceed with recording
2204+
try {
21352205
// Get supported MIME type
21362206
currentMimeType = getSupportedMimeType();
21372207
console.log('Using MIME type:', currentMimeType);
@@ -2200,18 +2270,64 @@ script.
22002270
recordingActions.style.display = 'none';
22012271
audioPreview.style.display = 'none';
22022272

2203-
} catch (error) {
2204-
console.error('Error accessing microphone:', error);
2205-
2206-
// Try with simpler constraints
2207-
try {
2208-
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
2209-
alert('Microphone access granted. Please try recording again.');
2210-
stream.getTracks().forEach(track => track.stop());
2211-
} catch (simpleError) {
2212-
alert('Could not access microphone. Please check browser permissions and try again.');
2273+
} catch (recordError) {
2274+
console.error('Error setting up recording:', recordError);
2275+
alert('Error setting up audio recording. Please try again.');
2276+
// Stop the stream if recording setup failed
2277+
stream.getTracks().forEach(track => track.stop());
2278+
}
2279+
});
2280+
2281+
// Stop Recording
2282+
stopBtn.addEventListener('click', () => {
2283+
if (mediaRecorder && isRecording) {
2284+
mediaRecorder.stop();
2285+
isRecording = false;
2286+
if (timerInterval) {
2287+
clearInterval(timerInterval);
2288+
timerInterval = null;
2289+
}
2290+
}
2291+
});
2292+
2293+
// Cancel Recording
2294+
cancelBtn.addEventListener('click', () => {
2295+
if (mediaRecorder && isRecording) {
2296+
mediaRecorder.stop();
2297+
isRecording = false;
2298+
if (timerInterval) {
2299+
clearInterval(timerInterval);
2300+
timerInterval = null;
22132301
}
22142302
}
2303+
2304+
// Reset UI
2305+
startBtn.style.display = 'block';
2306+
stopBtn.style.display = 'none';
2307+
cancelBtn.style.display = 'none';
2308+
recordingStatus.style.display = 'none';
2309+
recordingActions.style.display = 'none';
2310+
audioPreview.style.display = 'none';
2311+
2312+
// Clear any saved audio
2313+
savedAudioBlob = null;
2314+
audioChunks = [];
2315+
});
2316+
2317+
// Discard Recording
2318+
discardBtn.addEventListener('click', () => {
2319+
savedAudioBlob = null;
2320+
audioDataInput.value = '';
2321+
audioMimeTypeInput.value = '';
2322+
2323+
// Reset UI
2324+
recordingActions.style.display = 'none';
2325+
audioPreview.style.display = 'none';
2326+
saveBtn.innerHTML = '<i class="fas fa-save"></i> Save';
2327+
saveBtn.classList.remove('btn-secondary');
2328+
saveBtn.classList.add('btn-success');
2329+
saveBtn.disabled = false;
2330+
discardBtn.innerHTML = '<i class="fas fa-trash"></i> Discard';
22152331
});
22162332

22172333
// Save Recording - Convert to Base64

win/ProblemPad.exe

14.6 MB
Binary file not shown.

win/desktop_app.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import webview
2+
import tkinter as tk
3+
from tkinter import messagebox, simpledialog
4+
import threading
5+
import sys
6+
7+
URL = "https://problempad.onrender.com/"
8+
9+
class Api:
10+
def alert(self, message):
11+
# show an info dialog and return None (alert doesn't need a return)
12+
root = tk.Tk()
13+
root.withdraw()
14+
messagebox.showinfo("Alert", str(message), parent=root)
15+
root.destroy()
16+
17+
def confirm(self, message):
18+
root = tk.Tk()
19+
root.withdraw()
20+
result = messagebox.askokcancel("Confirm", str(message), parent=root)
21+
root.destroy()
22+
return result # True/False
23+
24+
def prompt(self, message, default=""):
25+
root = tk.Tk()
26+
root.withdraw()
27+
response = simpledialog.askstring("Prompt", str(message), initialvalue=default, parent=root)
28+
root.destroy()
29+
return response # string or None
30+
31+
def _inject_protection_js():
32+
"""
33+
JS that:
34+
- replaces alert/confirm/prompt to call python API
35+
- disables right-click context menu
36+
- blocks ctrl+wheel zoom, ctrl+/- and F12 and Ctrl+Shift+I
37+
- sets viewport to prevent pinch-zoom on touch devices
38+
"""
39+
return r"""
40+
(function(){
41+
// route built-in dialogs to Python
42+
if (window.pywebview && window.pywebview.api) {
43+
window.alert = function(msg){ try { window.pywebview.api.alert(String(msg)); } catch(e){} };
44+
window.confirm = function(msg){ try { return window.pywebview.api.confirm(String(msg)); } catch(e){ return false; } };
45+
window.prompt = function(msg, def){ try { return window.pywebview.api.prompt(String(msg), def || ''); } catch(e){ return null; } };
46+
}
47+
48+
// disable right-click
49+
document.addEventListener('contextmenu', function(e){ e.preventDefault(); });
50+
51+
// prevent zoom via Ctrl + wheel / Ctrl + +/- and pinch zoom
52+
window.addEventListener('wheel', function(e){
53+
if (e.ctrlKey) e.preventDefault();
54+
}, {passive:false});
55+
56+
window.addEventListener('keydown', function(e){
57+
// block Ctrl+ / Ctrl- / Ctrl= , F12, Ctrl+Shift+I
58+
if ((e.ctrlKey && (e.key === '+' || e.key === '-' || e.key === '=')) ||
59+
(e.key === 'F12') ||
60+
(e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'i'))) {
61+
e.preventDefault();
62+
e.stopPropagation();
63+
}
64+
}, true);
65+
66+
// block pinch-zoom by setting viewport
67+
(function(){
68+
var meta = document.querySelector('meta[name=viewport]');
69+
if (!meta){
70+
meta = document.createElement('meta');
71+
meta.name = 'viewport';
72+
document.head.appendChild(meta);
73+
}
74+
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0';
75+
})();
76+
})();
77+
"""
78+
79+
def on_loaded():
80+
"""called when GUI starts — inject JS into the first window"""
81+
try:
82+
# evaluate JS in the window (apply protections)
83+
if webview.windows:
84+
w = webview.windows[0]
85+
w.evaluate_js(_inject_protection_js())
86+
except Exception as e:
87+
# if evaluate_js fails silently, print for debug
88+
print("JS injection failed:", e, file=sys.stderr)
89+
90+
def main():
91+
api = Api()
92+
# Create the window. Set a descriptive title and comfortable default size.
93+
window = webview.create_window("ProblemPad (Desktop)", URL, width=1100, height=700, js_api=api)
94+
95+
# Start the GUI. on_loaded will run on GUI thread and inject JS after window exists.
96+
webview.start(on_loaded, window)
97+
98+
if __name__ == "__main__":
99+
main()

0 commit comments

Comments
 (0)