diazla wersja
This commit is contained in:
parent
7530c2aa45
commit
b025000018
6
.env
6
.env
@ -1,6 +0,0 @@
|
|||||||
SECRET_KEY=MOKO00koko
|
|
||||||
PASSWORD_KEY=tZnq1IyFSKeBwaLa0Bx5Ge722GgrJztHHNv3jqXMswo=
|
|
||||||
ADMIN_PASSWORD=admin
|
|
||||||
MODEL_REPO_URL=https:/git.pathl.pl
|
|
||||||
UPLOAD_LIMIT_MB=500
|
|
||||||
DEBUG=True
|
|
||||||
@ -2,18 +2,33 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="12dec3ca-9052-46eb-9015-fb58a43ed712" name="Changes" comment="">
|
<list default="true" id="12dec3ca-9052-46eb-9015-fb58a43ed712" name="Changes" comment="">
|
||||||
<change afterPath="$PROJECT_DIR$/.idea/Pathl.Stona.iml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/libs/string.py" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/apks/mk.py" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/models.json" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/interpreter.py" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/models/ai.py" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/templates/editor.html" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/ql_interpreter.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
|
<change afterPath="$PROJECT_DIR$/ql_stdlib.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/Strona AI.iml" beforeDir="false" />
|
<change afterPath="$PROJECT_DIR$/static/css/console.css" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/misc.xml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/static/css/editor.css" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/modules.xml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/static/js/editor.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/templates/dashboard.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/login.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/models.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/register.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/templates/user_projects.html" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.env" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/apks/mk.py" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/app.py" beforeDir="false" afterPath="$PROJECT_DIR$/app.py" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/style.css" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/interpreter.py" beforeDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/user.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/user.html" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/model.py" beforeDir="false" afterPath="$PROJECT_DIR$/libs/lib.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/models/Power/Readme.md" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/models/Power/description.md" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/models/Power/meta.json" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/models/Power/model.py" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/templates/editor.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/editor.html" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/templates/user.html" beforeDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -23,6 +38,8 @@
|
|||||||
<component name="FileTemplateManagerImpl">
|
<component name="FileTemplateManagerImpl">
|
||||||
<option name="RECENT_TEMPLATES">
|
<option name="RECENT_TEMPLATES">
|
||||||
<list>
|
<list>
|
||||||
|
<option value="HTML File" />
|
||||||
|
<option value="JavaScript File" />
|
||||||
<option value="Python Script" />
|
<option value="Python Script" />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
@ -40,6 +57,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
|
"DefaultHtmlFileTemplate": "HTML File",
|
||||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||||
"Python.Pathl.Stona.executor": "Run",
|
"Python.Pathl.Stona.executor": "Run",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
@ -53,6 +71,12 @@
|
|||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}]]></component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/static/js" />
|
||||||
|
<recent name="$PROJECT_DIR$" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
<component name="RunManager">
|
<component name="RunManager">
|
||||||
<configuration name="Pathl.Stona" type="PythonConfigurationType" factoryName="Python">
|
<configuration name="Pathl.Stona" type="PythonConfigurationType" factoryName="Python">
|
||||||
<module name="Pathl.Stona" />
|
<module name="Pathl.Stona" />
|
||||||
@ -94,7 +118,7 @@
|
|||||||
<option name="number" value="Default" />
|
<option name="number" value="Default" />
|
||||||
<option name="presentableId" value="Default" />
|
<option name="presentableId" value="Default" />
|
||||||
<updated>1769791641577</updated>
|
<updated>1769791641577</updated>
|
||||||
<workItem from="1769791642745" duration="1260000" />
|
<workItem from="1769791642745" duration="13856000" />
|
||||||
</task>
|
</task>
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
@ -102,6 +126,6 @@
|
|||||||
<option name="version" value="3" />
|
<option name="version" value="3" />
|
||||||
</component>
|
</component>
|
||||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||||
<SUITE FILE_PATH="coverage/Pathl_Stona$Pathl_Stona.coverage" NAME="Pathl.Stona Coverage Results" MODIFIED="1769792843549" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
|
<SUITE FILE_PATH="coverage/Pathl_Stona$Pathl_Stona.coverage" NAME="Pathl.Stona Coverage Results" MODIFIED="1769805559894" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
Binary file not shown.
BIN
__pycache__/ql_interpreter.cpython-313.pyc
Normal file
BIN
__pycache__/ql_interpreter.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
@ -1,5 +0,0 @@
|
|||||||
def add(a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def sub(a, b):
|
|
||||||
return a - b
|
|
||||||
803
app.py
803
app.py
@ -1,182 +1,657 @@
|
|||||||
from flask import Flask, request, render_template, jsonify, send_file
|
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
|
||||||
import os, json, importlib, importlib.util, sys, re
|
from markupsafe import Markup
|
||||||
from markdown import markdown
|
import os, json, time, re
|
||||||
|
import markdown
|
||||||
|
from datetime import datetime
|
||||||
|
from ql_interpreter import run_ql_code # Upewnij się że to jest
|
||||||
|
app = Flask(__name__, static_folder='static')
|
||||||
|
app.secret_key = "super_secret_key_12345"
|
||||||
|
|
||||||
app = Flask(__name__)
|
USERS_FILE = "users.json"
|
||||||
|
BASE_DIR = "projects"
|
||||||
|
os.makedirs(BASE_DIR, exist_ok=True)
|
||||||
|
|
||||||
# FOLDERS
|
|
||||||
UPLOAD_FOLDER = "scripts"
|
|
||||||
MODELS_FOLDER = "models"
|
|
||||||
APKS_FOLDER = "apks"
|
|
||||||
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
|
||||||
os.makedirs(MODELS_FOLDER, exist_ok=True)
|
|
||||||
sys.path.insert(0, os.path.join(os.getcwd(), APKS_FOLDER))
|
|
||||||
|
|
||||||
# GLOBAL MODELS
|
# ------------------------------
|
||||||
MODELS = {}
|
# Funkcje pomocnicze dla templatki
|
||||||
|
# ------------------------------
|
||||||
|
@app.context_processor
|
||||||
|
def utility_processor():
|
||||||
|
def file_size(filename):
|
||||||
|
"""Zwraca rozmiar pliku w czytelnej formie"""
|
||||||
|
try:
|
||||||
|
username = session.get('username', '')
|
||||||
|
project = 'Projekt1' # Domyślny projekt
|
||||||
|
if 'project' in request.view_args:
|
||||||
|
project = request.view_args['project']
|
||||||
|
|
||||||
# ---- Load models at startup ----
|
file_path = os.path.join(BASE_DIR, username, project, filename)
|
||||||
def load_models():
|
if os.path.exists(file_path):
|
||||||
global MODELS
|
size = os.path.getsize(file_path)
|
||||||
MODELS = {}
|
if size < 1024:
|
||||||
|
return f"{size} B"
|
||||||
|
elif size < 1024 * 1024:
|
||||||
|
return f"{size / 1024:.1f} KB"
|
||||||
|
else:
|
||||||
|
return f"{size / (1024 * 1024):.1f} MB"
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return "0 B"
|
||||||
|
|
||||||
for folder in os.listdir(MODELS_FOLDER):
|
def get_file_icon(filename):
|
||||||
path = os.path.join(MODELS_FOLDER, folder)
|
"""Zwraca ikonę dla typu pliku"""
|
||||||
if not os.path.isdir(path):
|
if filename.endswith('.ql'):
|
||||||
continue
|
return 'bi-file-earmark-code'
|
||||||
meta_path = os.path.join(path, "meta.json")
|
elif filename.endswith('.md'):
|
||||||
if not os.path.exists(meta_path):
|
return 'bi-file-earmark-text'
|
||||||
continue
|
elif filename.endswith('.py'):
|
||||||
with open(meta_path) as f:
|
return 'bi-file-earmark-code'
|
||||||
meta = json.load(f)
|
elif filename.endswith('.js'):
|
||||||
func_name = meta.get("function_name")
|
return 'bi-file-earmark-code'
|
||||||
if not func_name:
|
elif filename.endswith('.html'):
|
||||||
continue
|
return 'bi-file-earmark-code'
|
||||||
|
elif filename.endswith('.css'):
|
||||||
|
return 'bi-file-earmark-code'
|
||||||
|
elif filename.endswith('.json'):
|
||||||
|
return 'bi-file-earmark-data'
|
||||||
|
else:
|
||||||
|
return 'bi-file-earmark'
|
||||||
|
|
||||||
py_file = os.path.join(path, "model.py")
|
def format_date(timestamp):
|
||||||
spec = importlib.util.spec_from_file_location("model_module", py_file)
|
"""Formatuje datę"""
|
||||||
module = importlib.util.module_from_spec(spec)
|
return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M')
|
||||||
spec.loader.exec_module(module)
|
|
||||||
|
|
||||||
func = getattr(module, func_name)
|
return dict(
|
||||||
MODELS[folder] = {
|
file_size=file_size,
|
||||||
"func": func,
|
get_file_icon=get_file_icon,
|
||||||
"inputs": meta.get("inputs", []),
|
format_date=format_date
|
||||||
"meta": meta,
|
)
|
||||||
"path": path
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Pomocnicze funkcje
|
||||||
|
# ------------------------------
|
||||||
|
def load_users():
|
||||||
|
if not os.path.exists(USERS_FILE):
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (json.JSONDecodeError, FileNotFoundError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def save_users(users):
|
||||||
|
with open(USERS_FILE, "w", encoding='utf-8') as f:
|
||||||
|
json.dump(users, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def create_user_dir(username):
|
||||||
|
user_path = os.path.join(BASE_DIR, username)
|
||||||
|
os.makedirs(user_path, exist_ok=True)
|
||||||
|
return user_path
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Routes
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
|
def login():
|
||||||
|
if 'username' in session:
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.form.get('username', '').strip()
|
||||||
|
password = request.form.get('password', '').strip()
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
flash("Wypełnij wszystkie pola!")
|
||||||
|
return render_template("login.html")
|
||||||
|
|
||||||
|
users = load_users()
|
||||||
|
if username in users and users[username]['password'] == password:
|
||||||
|
session['username'] = username
|
||||||
|
session['cwd'] = os.path.join(BASE_DIR, username)
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
flash("Nieprawidłowy login lub hasło")
|
||||||
|
|
||||||
|
return render_template("login.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/register", methods=["GET", "POST"])
|
||||||
|
def register():
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.form.get('username', '').strip()
|
||||||
|
password = request.form.get('password', '').strip()
|
||||||
|
|
||||||
|
if not username or not password:
|
||||||
|
flash("Wypełnij wszystkie pola!")
|
||||||
|
return render_template("register.html")
|
||||||
|
|
||||||
|
if len(username) < 3:
|
||||||
|
flash("Nazwa użytkownika musi mieć co najmniej 3 znaki")
|
||||||
|
return render_template("register.html")
|
||||||
|
|
||||||
|
if len(password) < 4:
|
||||||
|
flash("Hasło musi mieć co najmniej 4 znaki")
|
||||||
|
return render_template("register.html")
|
||||||
|
|
||||||
|
users = load_users()
|
||||||
|
if username in users:
|
||||||
|
flash("Użytkownik już istnieje!")
|
||||||
|
else:
|
||||||
|
users[username] = {"password": password}
|
||||||
|
save_users(users)
|
||||||
|
|
||||||
|
user_path = create_user_dir(username)
|
||||||
|
default_project = os.path.join(user_path, "Projekt1")
|
||||||
|
os.makedirs(default_project, exist_ok=True)
|
||||||
|
|
||||||
|
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
|
||||||
|
|
||||||
|
with open(os.path.join(default_project, "README.md"), "w", encoding='utf-8') as f:
|
||||||
|
f.write(
|
||||||
|
"# Projekt1\n\nTo jest domyślny projekt.\n\n## Funkcje:\n- Edycja kodu QL\n- Konsola systemowa\n- Zarządzanie plikami")
|
||||||
|
|
||||||
|
flash("Rejestracja udana! Możesz się teraz zalogować.")
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
return render_template("register.html")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
session.clear()
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/dashboard")
|
||||||
|
def dashboard():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
username = session['username']
|
||||||
|
user_dir = os.path.join(BASE_DIR, username)
|
||||||
|
|
||||||
|
if not os.path.exists(user_dir):
|
||||||
|
os.makedirs(user_dir, exist_ok=True)
|
||||||
|
|
||||||
|
projects = []
|
||||||
|
if os.path.exists(user_dir):
|
||||||
|
for item in os.listdir(user_dir):
|
||||||
|
item_path = os.path.join(user_dir, item)
|
||||||
|
if os.path.isdir(item_path):
|
||||||
|
projects.append(item)
|
||||||
|
|
||||||
|
if not projects:
|
||||||
|
default_project = os.path.join(user_dir, "Projekt1")
|
||||||
|
os.makedirs(default_project, exist_ok=True)
|
||||||
|
with open(os.path.join(default_project, "main.ql"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("# Mój pierwszy program QL\nprint('Witaj w QL!');")
|
||||||
|
projects.append("Projekt1")
|
||||||
|
|
||||||
|
last_project = projects[-1] if projects else "Projekt1"
|
||||||
|
|
||||||
|
# Zaktualizowane kafelki z kolorami i ikonami
|
||||||
|
tiles = [
|
||||||
|
{
|
||||||
|
"title": "🔄 Modele",
|
||||||
|
"desc": "Wybierz model i funkcję",
|
||||||
|
"link": url_for('models_page'),
|
||||||
|
"icon": "bi-diagram-3",
|
||||||
|
"bg_color": "#007bff",
|
||||||
|
"bg_color2": "#6610f2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "📁 Projekty",
|
||||||
|
"desc": "Zarządzaj swoimi projektami",
|
||||||
|
"link": url_for('user_projects', username=username),
|
||||||
|
"icon": "bi-folder",
|
||||||
|
"bg_color": "#28a745",
|
||||||
|
"bg_color2": "#20c997"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "✏️ Edytor",
|
||||||
|
"desc": "Otwórz edytor kodu",
|
||||||
|
"link": url_for('ql_editor', username=username, project=last_project),
|
||||||
|
"icon": "bi-code-slash",
|
||||||
|
"bg_color": "#343a40",
|
||||||
|
"bg_color2": "#6c757d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "🛒 Marketplace",
|
||||||
|
"desc": "Przeglądaj moduły",
|
||||||
|
"link": "#",
|
||||||
|
"icon": "bi-shop",
|
||||||
|
"bg_color": "#ffc107",
|
||||||
|
"bg_color2": "#fd7e14"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "💬 Chat",
|
||||||
|
"desc": "Czat z użytkownikami",
|
||||||
|
"link": "#",
|
||||||
|
"icon": "bi-chat",
|
||||||
|
"bg_color": "#dc3545",
|
||||||
|
"bg_color2": "#e83e8c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "⚙️ Ustawienia",
|
||||||
|
"desc": "Konfiguracja platformy",
|
||||||
|
"link": "#",
|
||||||
|
"icon": "bi-gear",
|
||||||
|
"bg_color": "#17a2b8",
|
||||||
|
"bg_color2": "#20c997"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "📚 Dokumentacja",
|
||||||
|
"desc": "Przeczytaj dokumentację",
|
||||||
|
"link": "#",
|
||||||
|
"icon": "bi-book",
|
||||||
|
"bg_color": "#6f42c1",
|
||||||
|
"bg_color2": "#e83e8c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "🚪 Wyloguj",
|
||||||
|
"desc": "Zakończ sesję",
|
||||||
|
"link": url_for('logout'),
|
||||||
|
"icon": "bi-box-arrow-right",
|
||||||
|
"bg_color": "#6c757d",
|
||||||
|
"bg_color2": "#495057"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return render_template("dashboard.html",
|
||||||
|
username=username,
|
||||||
|
tiles=tiles,
|
||||||
|
projects=projects)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/models")
|
||||||
|
def models_page():
|
||||||
|
if 'username' not in session:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
return render_template("models.html", username=session['username'])
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Edytor QL
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/editor/<username>/<project>", methods=["GET"])
|
||||||
|
def ql_editor(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
project_path = os.path.join(BASE_DIR, username, project)
|
||||||
|
if not os.path.exists(project_path):
|
||||||
|
os.makedirs(project_path, exist_ok=True)
|
||||||
|
with open(os.path.join(project_path, "main.ql"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("# Mój program QL\nprint('Witaj!');")
|
||||||
|
|
||||||
|
# Pobierz wszystkie pliki (nie tylko .ql)
|
||||||
|
files = []
|
||||||
|
if os.path.exists(project_path):
|
||||||
|
for f in os.listdir(project_path):
|
||||||
|
if os.path.isfile(os.path.join(project_path, f)):
|
||||||
|
files.append(f)
|
||||||
|
|
||||||
|
selected_file = request.args.get("file")
|
||||||
|
if not selected_file:
|
||||||
|
if files:
|
||||||
|
selected_file = files[0]
|
||||||
|
else:
|
||||||
|
selected_file = "main.ql"
|
||||||
|
with open(os.path.join(project_path, selected_file), "w", encoding='utf-8') as f:
|
||||||
|
f.write("# Nowy program QL\nprint('Hello World!');")
|
||||||
|
files.append(selected_file)
|
||||||
|
|
||||||
|
content = ""
|
||||||
|
file_path = os.path.join(project_path, selected_file)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
except:
|
||||||
|
content = f"# Błąd odczytu pliku: {selected_file}"
|
||||||
|
|
||||||
|
return render_template("editor.html",
|
||||||
|
username=username,
|
||||||
|
project=project,
|
||||||
|
files=files,
|
||||||
|
selected_file=selected_file,
|
||||||
|
content=content)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/editor/<username>/<project>/save", methods=["POST"])
|
||||||
|
def save_editor_file(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return jsonify({"error": "Nie zalogowano"}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({"error": "Brak danych"}), 400
|
||||||
|
|
||||||
|
filename = data.get("filename", "").strip()
|
||||||
|
content = data.get("content", "")
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
return jsonify({"error": "Brak nazwy pliku"}), 400
|
||||||
|
|
||||||
|
project_path = os.path.join(BASE_DIR, username, project)
|
||||||
|
os.makedirs(project_path, exist_ok=True)
|
||||||
|
|
||||||
|
file_path = os.path.join(project_path, filename)
|
||||||
|
with open(file_path, "w", encoding='utf-8') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
return jsonify({"status": "ok", "filename": filename})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Tworzenie pliku
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/editor/<username>/<project>/create", methods=["POST"])
|
||||||
|
def create_file(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return jsonify({"error": "Nie zalogowano"}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({"error": "Brak danych"}), 400
|
||||||
|
|
||||||
|
filename = data.get("filename", "").strip()
|
||||||
|
content = data.get("content", "")
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
return jsonify({"error": "Brak nazwy pliku"}), 400
|
||||||
|
|
||||||
|
project_path = os.path.join(BASE_DIR, username, project)
|
||||||
|
os.makedirs(project_path, exist_ok=True)
|
||||||
|
|
||||||
|
file_path = os.path.join(project_path, filename)
|
||||||
|
|
||||||
|
# Sprawdź czy plik już istnieje
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
return jsonify({"error": "Plik już istnieje"}), 400
|
||||||
|
|
||||||
|
# Utwórz plik
|
||||||
|
with open(file_path, "w", encoding='utf-8') as f:
|
||||||
|
f.write(content if content else "")
|
||||||
|
|
||||||
|
return jsonify({"status": "ok", "filename": filename})
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Usuwanie pliku
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/editor/<username>/<project>/delete", methods=["POST"])
|
||||||
|
def delete_file(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return jsonify({"error": "Nie zalogowano"}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
filename = data.get("filename", "").strip()
|
||||||
|
|
||||||
|
if not filename:
|
||||||
|
return jsonify({"error": "Brak nazwy pliku"}), 400
|
||||||
|
|
||||||
|
file_path = os.path.join(BASE_DIR, username, project, filename)
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return jsonify({"error": "Plik nie istnieje"}), 404
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": f"Błąd usuwania: {str(e)}"}), 500
|
||||||
|
|
||||||
|
@app.route("/editor/<username>/<project>/execute", methods=["POST"])
|
||||||
|
def execute_ql_code(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return jsonify({"error": "Nie zalogowano"}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({"error": "Brak kodu"}), 400
|
||||||
|
|
||||||
|
code = data.get("code", "")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Użyj NOWEGO interpretera QL (bez verbose)
|
||||||
|
result = run_ql_code(code)
|
||||||
|
|
||||||
|
# Zwróć TYLKO output - bez dodatkowych komunikatów
|
||||||
|
return jsonify({
|
||||||
|
"status": "success",
|
||||||
|
"output": result['output'] # TYLKO to co w print()
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
"status": "error",
|
||||||
|
"output": [f"❌ {str(e)}"] # TYLKO błąd
|
||||||
|
})
|
||||||
|
# ------------------------------
|
||||||
|
# Konsola systemowa
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/editor/<username>/<project>/console", methods=["POST"])
|
||||||
|
def console_command(username, project):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return jsonify({"output": "Nie zalogowano"}), 403
|
||||||
|
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
return jsonify({"output": "Brak komendy"}), 400
|
||||||
|
|
||||||
|
cmd = data.get("command", "").strip()
|
||||||
|
|
||||||
|
if 'cwd' not in session:
|
||||||
|
session['cwd'] = os.path.join(BASE_DIR, username, project)
|
||||||
|
|
||||||
|
cwd = session.get('cwd', os.path.join(BASE_DIR, username, project))
|
||||||
|
user_root = os.path.join(BASE_DIR, username)
|
||||||
|
|
||||||
|
if not os.path.exists(cwd):
|
||||||
|
cwd = user_root
|
||||||
|
session['cwd'] = cwd
|
||||||
|
|
||||||
|
output = execute_console_command(cmd, cwd, user_root)
|
||||||
|
|
||||||
|
if 'cwd' in locals():
|
||||||
|
session['cwd'] = cwd
|
||||||
|
|
||||||
|
return jsonify({"output": output})
|
||||||
|
|
||||||
|
|
||||||
|
def execute_console_command(cmd, cwd, user_root):
|
||||||
|
parts = cmd.strip().split()
|
||||||
|
|
||||||
|
if not parts:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
command = parts[0].lower()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if command == "help":
|
||||||
|
return """Dostępne komendy:
|
||||||
|
help - wyświetla pomoc
|
||||||
|
ls - lista plików
|
||||||
|
cd <dir> - zmień katalog
|
||||||
|
pwd - pokaż aktualny katalog
|
||||||
|
mkdir <dir> - utwórz katalog
|
||||||
|
touch <file> - utwórz plik
|
||||||
|
cat <file> - pokaż zawartość pliku
|
||||||
|
rm <file> - usuń plik
|
||||||
|
clear - wyczyść konsolę"""
|
||||||
|
|
||||||
|
elif command == "ls":
|
||||||
|
items = os.listdir(cwd)
|
||||||
|
if not items:
|
||||||
|
return "Katalog jest pusty"
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for item in items:
|
||||||
|
item_path = os.path.join(cwd, item)
|
||||||
|
if os.path.isdir(item_path):
|
||||||
|
result.append(f"[DIR] {item}/")
|
||||||
|
else:
|
||||||
|
size = os.path.getsize(item_path)
|
||||||
|
result.append(f"[FILE] {item} ({size} bytes)")
|
||||||
|
return "\n".join(result)
|
||||||
|
|
||||||
|
elif command == "pwd":
|
||||||
|
rel_path = os.path.relpath(cwd, user_root)
|
||||||
|
return f"{rel_path}\nAbsolutna ścieżka: {cwd}"
|
||||||
|
|
||||||
|
elif command == "cd":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "Użycie: cd <katalog>"
|
||||||
|
|
||||||
|
target = os.path.normpath(os.path.join(cwd, parts[1]))
|
||||||
|
|
||||||
|
if not os.path.exists(target):
|
||||||
|
return f"Katalog nie istnieje: {parts[1]}"
|
||||||
|
|
||||||
|
if not target.startswith(user_root):
|
||||||
|
return "Błąd: Próba dostępu poza dozwolony obszar"
|
||||||
|
|
||||||
|
if not os.path.isdir(target):
|
||||||
|
return f"To nie jest katalog: {parts[1]}"
|
||||||
|
|
||||||
|
# Aktualizacja cwd
|
||||||
|
cwd = target
|
||||||
|
return f"Zmieniono katalog na: {os.path.relpath(target, user_root)}"
|
||||||
|
|
||||||
|
elif command == "mkdir":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "Użycie: mkdir <nazwa_katalogu>"
|
||||||
|
|
||||||
|
new_dir = os.path.join(cwd, parts[1])
|
||||||
|
os.makedirs(new_dir, exist_ok=True)
|
||||||
|
return f"Utworzono katalog: {parts[1]}"
|
||||||
|
|
||||||
|
elif command == "touch":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "Użycie: touch <nazwa_pliku>"
|
||||||
|
|
||||||
|
file_path = os.path.join(cwd, parts[1])
|
||||||
|
with open(file_path, 'a', encoding='utf-8'):
|
||||||
|
pass
|
||||||
|
return f"Utworzono plik: {parts[1]}"
|
||||||
|
|
||||||
|
elif command == "cat":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "Użycie: cat <nazwa_pliku>"
|
||||||
|
|
||||||
|
file_path = os.path.join(cwd, parts[1])
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return f"Plik nie istnieje: {parts[1]}"
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
return f"Zawartość pliku {parts[1]}:\n{'-' * 40}\n{content}"
|
||||||
|
|
||||||
|
elif command == "rm":
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "Użycie: rm <nazwa_pliku>"
|
||||||
|
|
||||||
|
file_path = os.path.join(cwd, parts[1])
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return f"Plik nie istnieje: {parts[1]}"
|
||||||
|
|
||||||
|
if os.path.isdir(file_path):
|
||||||
|
return "Użyj 'rmdir' do usuwania katalogów"
|
||||||
|
|
||||||
|
os.remove(file_path)
|
||||||
|
return f"Usunięto plik: {parts[1]}"
|
||||||
|
|
||||||
|
elif command == "clear":
|
||||||
|
return "CLEAR_CONSOLE"
|
||||||
|
|
||||||
|
else:
|
||||||
|
return f"Nieznana komenda: {command}\nWpisz 'help' aby zobaczyć dostępne komendy."
|
||||||
|
|
||||||
|
except PermissionError:
|
||||||
|
return "Błąd: Brak uprawnień"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Błąd wykonania: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------
|
||||||
|
# Projekty użytkowników
|
||||||
|
# ------------------------------
|
||||||
|
@app.route("/user_projects/<username>")
|
||||||
|
def user_projects(username):
|
||||||
|
if 'username' not in session or session['username'] != username:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
user_dir = os.path.join(BASE_DIR, username)
|
||||||
|
if not os.path.exists(user_dir):
|
||||||
|
projects = []
|
||||||
|
else:
|
||||||
|
projects = [d for d in os.listdir(user_dir)
|
||||||
|
if os.path.isdir(os.path.join(user_dir, d))]
|
||||||
|
|
||||||
|
project_info = []
|
||||||
|
for project in projects:
|
||||||
|
info = {
|
||||||
|
'name': project,
|
||||||
|
'path': os.path.join(user_dir, project),
|
||||||
|
'created': os.path.getctime(os.path.join(user_dir, project)),
|
||||||
|
'files': []
|
||||||
}
|
}
|
||||||
|
|
||||||
load_models()
|
# Liczba plików
|
||||||
|
project_path = os.path.join(user_dir, project)
|
||||||
|
for root, dirs, files in os.walk(project_path):
|
||||||
|
for file in files:
|
||||||
|
if file.endswith('.ql'):
|
||||||
|
info['files'].append(file)
|
||||||
|
|
||||||
|
# README
|
||||||
# ---------------- DASHBOARD ----------------
|
md_path = os.path.join(user_dir, project, "README.md")
|
||||||
@app.route("/")
|
if os.path.exists(md_path):
|
||||||
def dashboard():
|
|
||||||
models_with_md = {}
|
|
||||||
for name, model in MODELS.items():
|
|
||||||
md_content = ""
|
|
||||||
try:
|
|
||||||
md_path = os.path.join(model["path"], model["meta"]["description_file"])
|
|
||||||
with open(md_path, "r", encoding="utf-8") as f:
|
|
||||||
md_content = markdown(f.read())
|
|
||||||
except:
|
|
||||||
md_content = "<p>Brak opisu.</p>"
|
|
||||||
models_with_md[name] = {**model, "md_content": md_content}
|
|
||||||
|
|
||||||
return render_template("user.html", models=models_with_md)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- EDITOR ----------------
|
|
||||||
@app.route("/editor/<model_name>")
|
|
||||||
def editor(model_name):
|
|
||||||
return render_template("editor.html", model_name=model_name)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- RUN SCRIPT (MKScript) ----------------
|
|
||||||
@app.route("/run_script", methods=["POST"])
|
|
||||||
def run_script():
|
|
||||||
code = request.json.get("code", "")
|
|
||||||
output = []
|
|
||||||
variables = {}
|
|
||||||
modules = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
lines = code.split(";")
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if not line:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# use mk
|
|
||||||
if line.startswith("use "):
|
|
||||||
name = line.split()[1]
|
|
||||||
mod = importlib.import_module(name)
|
|
||||||
modules[name] = mod
|
|
||||||
variables[name] = mod
|
|
||||||
continue
|
|
||||||
|
|
||||||
# print(...)
|
|
||||||
if line.startswith("print"):
|
|
||||||
inside = line[line.find("(")+1:line.rfind(")")]
|
|
||||||
output.append(str(eval_expr(inside, variables)))
|
|
||||||
continue
|
|
||||||
|
|
||||||
# typy int/float/bool
|
|
||||||
if any(line.startswith(t) for t in ["int ", "float ", "bool "]):
|
|
||||||
_, rest = line.split(" ", 1)
|
|
||||||
var, expr = rest.split("=")
|
|
||||||
variables[var.strip()] = eval_expr(expr.strip(), variables)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# wywołanie funkcji np mk.add(1,2)
|
|
||||||
if "." in line and "(" in line:
|
|
||||||
obj, rest = line.split(".", 1)
|
|
||||||
fname = rest[:rest.find("(")]
|
|
||||||
args = rest[rest.find("(")+1:rest.find(")")]
|
|
||||||
args = [eval_expr(a.strip(), variables) for a in args.split(",")]
|
|
||||||
result = getattr(modules[obj], fname)(*args)
|
|
||||||
variables["_"] = result
|
|
||||||
continue
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({"error": str(e)})
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
"output": output,
|
|
||||||
"variables": {k: str(v) for k, v in variables.items()}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def eval_expr(expr, variables):
|
|
||||||
expr = expr.replace("true", "True").replace("false", "False")
|
|
||||||
return eval(expr, {}, variables)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- PREDICT ----------------
|
|
||||||
@app.route("/predict/<model_name>", methods=["POST"])
|
|
||||||
def predict(model_name):
|
|
||||||
model_entry = MODELS.get(model_name)
|
|
||||||
if not model_entry:
|
|
||||||
return jsonify({"output": "Model nie istnieje"}), 404
|
|
||||||
|
|
||||||
func = model_entry["func"]
|
|
||||||
inputs = model_entry["inputs"]
|
|
||||||
kwargs = {inp: request.form.get(inp) for inp in inputs}
|
|
||||||
|
|
||||||
try:
|
|
||||||
for k in kwargs:
|
|
||||||
try:
|
try:
|
||||||
kwargs[k] = float(kwargs[k])
|
with open(md_path, 'r', encoding='utf-8') as f:
|
||||||
|
html_content = markdown.markdown(f.read())
|
||||||
|
info['readme'] = Markup(html_content)
|
||||||
except:
|
except:
|
||||||
pass
|
info['readme'] = Markup("<p>Błąd odczytu README</p>")
|
||||||
output = func(**kwargs)
|
else:
|
||||||
except Exception as e:
|
info['readme'] = None
|
||||||
output = str(e)
|
|
||||||
|
|
||||||
return jsonify({"output": str(output)})
|
project_info.append(info)
|
||||||
|
|
||||||
|
return render_template("user_projects.html",
|
||||||
|
username=username,
|
||||||
|
projects=project_info)
|
||||||
|
|
||||||
|
|
||||||
# ---------------- DOWNLOAD SCRIPT ----------------
|
# ------------------------------
|
||||||
@app.route("/download_script/<filename>")
|
# Static files
|
||||||
def download_script(filename):
|
# ------------------------------
|
||||||
path = os.path.join(UPLOAD_FOLDER, filename)
|
@app.route('/static/<path:filename>')
|
||||||
if os.path.exists(path):
|
def serve_static(filename):
|
||||||
return send_file(path, as_attachment=True)
|
return send_from_directory(app.static_folder, filename)
|
||||||
return "Plik nie istnieje", 404
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- UPLOAD SCRIPT ----------------
|
# ------------------------------
|
||||||
@app.route("/upload_script", methods=["POST"])
|
# Uruchomienie
|
||||||
def upload_script():
|
# ------------------------------
|
||||||
file = request.files.get("file")
|
|
||||||
if not file:
|
|
||||||
return "Nie przesłano pliku", 400
|
|
||||||
filename = file.filename
|
|
||||||
path = os.path.join(UPLOAD_FOLDER, filename)
|
|
||||||
file.save(path)
|
|
||||||
return "OK"
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- MAIN ----------------
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(port=4500)
|
# Utwórz niezbędne katalogi
|
||||||
|
os.makedirs('static/css', exist_ok=True)
|
||||||
|
os.makedirs('static/js', exist_ok=True)
|
||||||
|
os.makedirs('templates', exist_ok=True)
|
||||||
|
os.makedirs('libs', exist_ok=True) # Dodaj katalog libs
|
||||||
|
os.makedirs(BASE_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
print("Serwer uruchamia się na http://localhost:5000")
|
||||||
|
print("Katalog libs:", os.path.abspath('libs'))
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
@ -1,73 +0,0 @@
|
|||||||
import re, importlib
|
|
||||||
|
|
||||||
variables = {}
|
|
||||||
modules = {}
|
|
||||||
functions = {}
|
|
||||||
|
|
||||||
# Rejestracja “handlerów” dla linii
|
|
||||||
handlers = []
|
|
||||||
|
|
||||||
def eval_expr(expr):
|
|
||||||
"""Bezpieczne obliczanie wyrażeń z użyciem zmiennych"""
|
|
||||||
expr = expr.replace("true", "True").replace("false", "False")
|
|
||||||
return eval(expr, {}, variables)
|
|
||||||
|
|
||||||
def handle_use(line):
|
|
||||||
if line.startswith("use "):
|
|
||||||
name = line.split()[1]
|
|
||||||
modules[name] = importlib.import_module(f"apks.{name}")
|
|
||||||
variables[name] = modules[name]
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle_print(line):
|
|
||||||
if line.startswith("print"):
|
|
||||||
inside = re.search(r"\((.*)\)", line).group(1)
|
|
||||||
print(eval_expr(inside))
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle_var(line):
|
|
||||||
for t in ["int ", "float ", "bool ", "string "]:
|
|
||||||
if line.startswith(t):
|
|
||||||
_, rest = line.split(" ", 1)
|
|
||||||
var, expr = rest.split("=")
|
|
||||||
val = expr.strip()
|
|
||||||
if t.strip() == "string":
|
|
||||||
val = val.strip('"').strip("'")
|
|
||||||
else:
|
|
||||||
val = eval_expr(val)
|
|
||||||
variables[var.strip()] = val
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle_func_call(line):
|
|
||||||
if "." in line and "(" in line:
|
|
||||||
obj, rest = line.split(".", 1)
|
|
||||||
fname = rest[:rest.find("(")]
|
|
||||||
args_str = rest[rest.find("(")+1:rest.rfind(")")]
|
|
||||||
args = [eval_expr(a.strip()) for a in args_str.split(",") if a.strip()]
|
|
||||||
result = getattr(modules[obj], fname)(*args)
|
|
||||||
variables["_"] = result # ostatni wynik
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Dodajemy wszystkie handlery do listy
|
|
||||||
handlers.extend([handle_use, handle_print, handle_var, handle_func_call])
|
|
||||||
|
|
||||||
def run(code):
|
|
||||||
"""Interpreter MKScript"""
|
|
||||||
lines = code.split(";")
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
if not line or line.startswith("//"): # komentarze
|
|
||||||
continue
|
|
||||||
|
|
||||||
# uruchamiamy wszystkie handlery aż któryś obsłuży linię
|
|
||||||
handled = False
|
|
||||||
for h in handlers:
|
|
||||||
if h(line):
|
|
||||||
handled = True
|
|
||||||
break
|
|
||||||
if not handled:
|
|
||||||
print(f"Nieznana linia: {line}")
|
|
||||||
22
libs/lib.py
Normal file
22
libs/lib.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
"""
|
||||||
|
Biblioteka przykładowa dla QL
|
||||||
|
"""
|
||||||
|
|
||||||
|
def lib(x):
|
||||||
|
"""Przykładowa funkcja"""
|
||||||
|
return f"Wynik z biblioteki: {x * 2}"
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
"""Dodawanie"""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def multiply(a, b):
|
||||||
|
"""Mnożenie"""
|
||||||
|
return a * b
|
||||||
|
|
||||||
|
def greet(name):
|
||||||
|
"""Powitanie"""
|
||||||
|
return f"Witaj, {name}!"
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ['add', 'multiply', 'greet']
|
||||||
125
libs/string.py
Normal file
125
libs/string.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"""
|
||||||
|
Biblioteka operacji na stringach dla QL
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def length(s):
|
||||||
|
"""Długość stringa"""
|
||||||
|
return len(str(s))
|
||||||
|
|
||||||
|
def upper(s):
|
||||||
|
"""Zamienia na wielkie litery"""
|
||||||
|
return str(s).upper()
|
||||||
|
|
||||||
|
def lower(s):
|
||||||
|
"""Zamienia na małe litery"""
|
||||||
|
return str(s).lower()
|
||||||
|
|
||||||
|
def capitalize(s):
|
||||||
|
"""Pierwsza litera wielka"""
|
||||||
|
return str(s).capitalize()
|
||||||
|
|
||||||
|
def title(s):
|
||||||
|
"""Każde słowo z wielkiej litery"""
|
||||||
|
return str(s).title()
|
||||||
|
|
||||||
|
def trim(s):
|
||||||
|
"""Usuwa białe znaki z początku i końca"""
|
||||||
|
return str(s).strip()
|
||||||
|
|
||||||
|
def ltrim(s):
|
||||||
|
"""Usuwa białe znaki z początku"""
|
||||||
|
return str(s).lstrip()
|
||||||
|
|
||||||
|
def rtrim(s):
|
||||||
|
"""Usuwa białe znaki z końca"""
|
||||||
|
return str(s).rstrip()
|
||||||
|
|
||||||
|
def replace(s, old, new):
|
||||||
|
"""Zamienia fragmenty stringa"""
|
||||||
|
return str(s).replace(old, new)
|
||||||
|
|
||||||
|
def split(s, delimiter=' '):
|
||||||
|
"""Dzieli string na listę"""
|
||||||
|
return str(s).split(delimiter)
|
||||||
|
|
||||||
|
def join(arr, delimiter=''):
|
||||||
|
"""Łączy listę w string"""
|
||||||
|
return delimiter.join(str(x) for x in arr)
|
||||||
|
|
||||||
|
def substring(s, start, end=None):
|
||||||
|
"""Wyciąga podciąg"""
|
||||||
|
if end is None:
|
||||||
|
return str(s)[start:]
|
||||||
|
return str(s)[start:end]
|
||||||
|
|
||||||
|
def char_at(s, index):
|
||||||
|
"""Znak na pozycji"""
|
||||||
|
return str(s)[index]
|
||||||
|
|
||||||
|
def index_of(s, search):
|
||||||
|
"""Pozycja pierwszego wystąpienia"""
|
||||||
|
return str(s).find(search)
|
||||||
|
|
||||||
|
def last_index_of(s, search):
|
||||||
|
"""Pozycja ostatniego wystąpienia"""
|
||||||
|
return str(s).rfind(search)
|
||||||
|
|
||||||
|
def contains(s, search):
|
||||||
|
"""Czy zawiera podciąg"""
|
||||||
|
return search in str(s)
|
||||||
|
|
||||||
|
def starts_with(s, prefix):
|
||||||
|
"""Czy zaczyna się od"""
|
||||||
|
return str(s).startswith(prefix)
|
||||||
|
|
||||||
|
def ends_with(s, suffix):
|
||||||
|
"""Czy kończy się na"""
|
||||||
|
return str(s).endswith(suffix)
|
||||||
|
|
||||||
|
def repeat(s, count):
|
||||||
|
"""Powtarza string"""
|
||||||
|
return str(s) * count
|
||||||
|
|
||||||
|
def reverse(s):
|
||||||
|
"""Odwraca string"""
|
||||||
|
return str(s)[::-1]
|
||||||
|
|
||||||
|
def is_alpha(s):
|
||||||
|
"""Czy tylko litery"""
|
||||||
|
return str(s).isalpha()
|
||||||
|
|
||||||
|
def is_digit(s):
|
||||||
|
"""Czy tylko cyfry"""
|
||||||
|
return str(s).isdigit()
|
||||||
|
|
||||||
|
def is_alnum(s):
|
||||||
|
"""Czy litery i cyfry"""
|
||||||
|
return str(s).isalnum()
|
||||||
|
|
||||||
|
def is_space(s):
|
||||||
|
"""Czy tylko białe znaki"""
|
||||||
|
return str(s).isspace()
|
||||||
|
|
||||||
|
def match(s, pattern):
|
||||||
|
"""Dopasowanie wyrażenia regularnego"""
|
||||||
|
return bool(re.match(pattern, str(s)))
|
||||||
|
|
||||||
|
def search(s, pattern):
|
||||||
|
"""Wyszukiwanie wyrażenia regularnego"""
|
||||||
|
match = re.search(pattern, str(s))
|
||||||
|
return match.group() if match else None
|
||||||
|
|
||||||
|
def format(template, *args):
|
||||||
|
"""Formatowanie stringa"""
|
||||||
|
return template.format(*args)
|
||||||
|
|
||||||
|
def encode_base64(s):
|
||||||
|
"""Kodowanie Base64"""
|
||||||
|
return base64.b64encode(str(s).encode()).decode()
|
||||||
|
|
||||||
|
def decode_base64(s):
|
||||||
|
"""Dekodowanie Base64"""
|
||||||
|
return base64.b64decode(str(s).encode()).decode()
|
||||||
8
models.json
Normal file
8
models.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Calculator": {
|
||||||
|
"source": "models/ai.py",
|
||||||
|
"funcs": {
|
||||||
|
"add": "a b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
#model Power
|
|
||||||
Binary file not shown.
@ -1,18 +0,0 @@
|
|||||||
# Model Power
|
|
||||||
|
|
||||||
Ten model oblicza potęgę liczby.
|
|
||||||
|
|
||||||
## Funkcje:
|
|
||||||
|
|
||||||
- `oblicz_potega(x, typ)`
|
|
||||||
|
|
||||||
### Parametry:
|
|
||||||
|
|
||||||
- `x` (int/float): liczba do podniesienia do potęgi
|
|
||||||
- `typ` (str): `"kwadrat"` lub `"sześcian"`
|
|
||||||
|
|
||||||
### Przykłady użycia:
|
|
||||||
|
|
||||||
```python
|
|
||||||
oblicz_potega(3, "kwadrat") # wynik: 9
|
|
||||||
oblicz_potega(2, "sześcian") # wynik: 8
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Power",
|
|
||||||
"function_name": "oblicz_potega",
|
|
||||||
"inputs": ["x","typ"],
|
|
||||||
"description_file": "description.md"
|
|
||||||
}
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
def oblicz_potega(x, typ):
|
|
||||||
"""
|
|
||||||
Funkcja oblicza kwadrat lub sześcian liczby x w zależności od parametru typ.
|
|
||||||
Parametry:
|
|
||||||
x (int/float): liczba, którą chcemy podnieść do potęgi
|
|
||||||
typ (str): "kwadrat" lub "sześcian"
|
|
||||||
|
|
||||||
Zwraca:
|
|
||||||
int/float: wynik działania
|
|
||||||
"""
|
|
||||||
if typ == "kwadrat":
|
|
||||||
return x ** 2
|
|
||||||
elif typ == "sześcian":
|
|
||||||
return x ** 3
|
|
||||||
else:
|
|
||||||
return "Niepoprawny typ. Wpisz 'kwadrat' lub 'sześcian'."
|
|
||||||
BIN
models/__pycache__/ai.cpython-313.pyc
Normal file
BIN
models/__pycache__/ai.cpython-313.pyc
Normal file
Binary file not shown.
2
models/ai.py
Normal file
2
models/ai.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
def add(a,b):
|
||||||
|
return a+b
|
||||||
2
projects/Maro/Projekt1/kod.py
Normal file
2
projects/Maro/Projekt1/kod.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# kod.py
|
||||||
|
# Utworzono: 30.01.2026, 20:45:03
|
||||||
3
projects/Maro/Projekt1/main.ql
Normal file
3
projects/Maro/Projekt1/main.ql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
use lib;
|
||||||
|
print(lib.lib(1))
|
||||||
|
print("elo")
|
||||||
101
ql_interpreter.py
Normal file
101
ql_interpreter.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class QLInterpreter:
|
||||||
|
def __init__(self):
|
||||||
|
self.output = []
|
||||||
|
self.functions = self._register_functions()
|
||||||
|
|
||||||
|
def _register_functions(self):
|
||||||
|
"""TUTAJ DODAJESZ NOWE FUNKCJE"""
|
||||||
|
return {
|
||||||
|
'print': self._func_print,
|
||||||
|
# DODAJ TU SWOJE FUNKCJE:
|
||||||
|
# 'powitanie': self._func_powitanie,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========== FUNKCJE WBUDOWANE ==========
|
||||||
|
def _func_print(self, *args):
|
||||||
|
"""Tylko dodaje do outputu - bez printowania"""
|
||||||
|
text = ' '.join(str(arg) for arg in args)
|
||||||
|
self.output.append(text)
|
||||||
|
|
||||||
|
def execute(self, code: str):
|
||||||
|
"""Wykonuje kod QL - NIE RUSZAJ TEJ FUNKCJI"""
|
||||||
|
self.output = []
|
||||||
|
|
||||||
|
for line in code.strip().split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith(('//', '#')):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# use lib; - ignoruj
|
||||||
|
if line.startswith('use '):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Wywołanie funkcji: nazwa(arg1, arg2, ...)
|
||||||
|
match = re.match(r'(\w+)\s*\((.*)\)', line.rstrip(';'))
|
||||||
|
if match:
|
||||||
|
func_name = match.group(1)
|
||||||
|
args_str = match.group(2)
|
||||||
|
|
||||||
|
if func_name in self.functions:
|
||||||
|
try:
|
||||||
|
args = self._parse_args(args_str)
|
||||||
|
self.functions[func_name](*args)
|
||||||
|
except Exception:
|
||||||
|
pass # Ignoruj błędy
|
||||||
|
|
||||||
|
return self.output
|
||||||
|
|
||||||
|
def _parse_args(self, args_str: str):
|
||||||
|
"""Parsuje argumenty - NIE RUSZAJ"""
|
||||||
|
args = []
|
||||||
|
current = ''
|
||||||
|
in_string = False
|
||||||
|
quote_char = None
|
||||||
|
|
||||||
|
for char in args_str:
|
||||||
|
if char in '"\'' and (quote_char is None or quote_char == char):
|
||||||
|
in_string = not in_string
|
||||||
|
quote_char = char if in_string else None
|
||||||
|
current += char
|
||||||
|
elif char == ',' and not in_string:
|
||||||
|
args.append(self._parse_arg(current.strip()))
|
||||||
|
current = ''
|
||||||
|
else:
|
||||||
|
current += char
|
||||||
|
|
||||||
|
if current.strip():
|
||||||
|
args.append(self._parse_arg(current.strip()))
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _parse_arg(self, arg: str):
|
||||||
|
"""Parsuje pojedynczy argument - NIE RUSZAJ"""
|
||||||
|
arg = arg.strip()
|
||||||
|
|
||||||
|
# String "tekst" lub 'tekst'
|
||||||
|
if (arg.startswith('"') and arg.endswith('"')) or \
|
||||||
|
(arg.startswith("'") and arg.endswith("'")):
|
||||||
|
return arg[1:-1]
|
||||||
|
|
||||||
|
# Liczba
|
||||||
|
if arg.replace('.', '', 1).isdigit():
|
||||||
|
return float(arg) if '.' in arg else int(arg)
|
||||||
|
|
||||||
|
return arg
|
||||||
|
|
||||||
|
|
||||||
|
# ========== FUNKCJA URUCHAMIAJĄCA ==========
|
||||||
|
def run_ql_code(code: str):
|
||||||
|
"""Uruchamia kod QL - NIE RUSZAJ"""
|
||||||
|
interpreter = QLInterpreter()
|
||||||
|
output = interpreter.execute(code)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'output': output, # TYLKO output z print()
|
||||||
|
'variables': {},
|
||||||
|
'functions': list(interpreter.functions.keys()),
|
||||||
|
'libraries': []
|
||||||
|
}
|
||||||
68
static/css/console.css
Normal file
68
static/css/console.css
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
.console-wrapper {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-header {
|
||||||
|
background: #252526;
|
||||||
|
padding: 8px 15px;
|
||||||
|
border-bottom: 1px solid #3e3e42;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-body {
|
||||||
|
padding: 15px;
|
||||||
|
min-height: 200px;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-line {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-prompt {
|
||||||
|
color: #4ec9b0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-command {
|
||||||
|
color: #dcdcdc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-response {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-error {
|
||||||
|
color: #f44747;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-success {
|
||||||
|
color: #6a9955;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input-line {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 15px;
|
||||||
|
background: #252526;
|
||||||
|
border-top: 1px solid #3e3e42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input-line input {
|
||||||
|
flex: 1;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #dcdcdc;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
padding: 5px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input-line input::placeholder {
|
||||||
|
color: #858585;
|
||||||
|
}
|
||||||
73
static/css/editor.css
Normal file
73
static/css/editor.css
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/* Podstawowe style dla edytora */
|
||||||
|
.code-editor-container {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-editor-toolbar {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-editor-textarea {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
border: none;
|
||||||
|
padding: 15px;
|
||||||
|
width: 100%;
|
||||||
|
min-height: 400px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-editor-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-explorer {
|
||||||
|
background: #f8f9fa;
|
||||||
|
min-height: 500px;
|
||||||
|
border-right: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-item.active {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-output {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
min-height: 200px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input {
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
background: #252526;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border: 1px solid #3e3e42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-input:focus {
|
||||||
|
background: #252526;
|
||||||
|
color: #d4d4d4;
|
||||||
|
border-color: #007bff;
|
||||||
|
}
|
||||||
314
static/js/console.js
Normal file
314
static/js/console.js
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
class QLConsole {
|
||||||
|
constructor(username, project) {
|
||||||
|
this.username = username;
|
||||||
|
this.project = project;
|
||||||
|
this.history = [];
|
||||||
|
this.historyIndex = -1;
|
||||||
|
this.currentCommand = '';
|
||||||
|
this.isProcessing = false;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindEvents();
|
||||||
|
this.printWelcome();
|
||||||
|
this.setupHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
const input = document.getElementById('consoleInput');
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
|
||||||
|
if (!input || !output) return;
|
||||||
|
|
||||||
|
// Obsługa klawisza Enter
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.executeCommand(input.value.trim());
|
||||||
|
input.value = '';
|
||||||
|
this.historyIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strzałki dla historii
|
||||||
|
else if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.navigateHistory(-1);
|
||||||
|
}
|
||||||
|
else if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.navigateHistory(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab dla autouzupełniania
|
||||||
|
else if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.autoComplete(input);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Obsługa przycisków konsoli
|
||||||
|
document.getElementById('clearConsoleBtn')?.addEventListener('click', () => this.clearConsole());
|
||||||
|
document.getElementById('helpConsoleBtn')?.addEventListener('click', () => this.showHelp());
|
||||||
|
|
||||||
|
// Fokus na input przy kliknięciu w konsolę
|
||||||
|
output.addEventListener('click', () => {
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-focus przy załadowaniu
|
||||||
|
setTimeout(() => input.focus(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeCommand(command) {
|
||||||
|
if (!command) return;
|
||||||
|
|
||||||
|
// Dodaj do historii
|
||||||
|
if (command !== this.history[this.history.length - 1]) {
|
||||||
|
this.history.push(command);
|
||||||
|
if (this.history.length > 50) {
|
||||||
|
this.history.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wyświetl komendę
|
||||||
|
this.printCommand(command);
|
||||||
|
|
||||||
|
// Ustaw flagę przetwarzania
|
||||||
|
this.isProcessing = true;
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/console`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
command: command
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
if (data.output === 'CLEAR_CONSOLE') {
|
||||||
|
this.clearConsole();
|
||||||
|
this.printWelcome();
|
||||||
|
} else {
|
||||||
|
this.printResponse(data.output);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.printError(data.output || 'Błąd wykonania komendy');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.printError(`Błąd sieciowy: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
this.isProcessing = false;
|
||||||
|
this.hideLoading();
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printWelcome() {
|
||||||
|
const welcome = `
|
||||||
|
========================================
|
||||||
|
QL Console - Terminal systemowy
|
||||||
|
Projekt: ${this.project}
|
||||||
|
Użytkownik: ${this.username}
|
||||||
|
Wpisz 'help' aby zobaczyć dostępne komendy
|
||||||
|
========================================
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
this.printInfo(welcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
printCommand(command) {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'console-line';
|
||||||
|
line.innerHTML = `
|
||||||
|
<span class="console-prompt">$</span>
|
||||||
|
<span class="console-command">${this.escapeHtml(command)}</span>
|
||||||
|
`;
|
||||||
|
output.appendChild(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
printResponse(response) {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
const lines = response.split('\n');
|
||||||
|
|
||||||
|
lines.forEach(line => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'console-line';
|
||||||
|
|
||||||
|
if (line.includes('Błąd:') || line.includes('Nie można')) {
|
||||||
|
div.className += ' console-error';
|
||||||
|
} else if (line.includes('Utworzono') || line.includes('Zmieniono') || line.includes('Usunięto')) {
|
||||||
|
div.className += ' console-success';
|
||||||
|
} else if (line.includes('Ścieżka:') || line.includes('Katalog:')) {
|
||||||
|
div.className += ' console-path';
|
||||||
|
} else {
|
||||||
|
div.className += ' console-info';
|
||||||
|
}
|
||||||
|
|
||||||
|
div.textContent = line;
|
||||||
|
output.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
printError(error) {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'console-line console-error';
|
||||||
|
line.textContent = `❌ ${error}`;
|
||||||
|
output.appendChild(line);
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
printInfo(info) {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = 'console-line console-info';
|
||||||
|
line.textContent = info;
|
||||||
|
output.appendChild(line);
|
||||||
|
this.scrollToBottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConsole() {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
output.innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
showHelp() {
|
||||||
|
const help = `Dostępne komendy:
|
||||||
|
• help - wyświetla tę pomoc
|
||||||
|
• ls - lista plików i folderów
|
||||||
|
• cd [dir] - zmień katalog
|
||||||
|
• pwd - pokaż aktualny katalog
|
||||||
|
• mkdir [dir] - utwórz nowy katalog
|
||||||
|
• touch [file] - utwórz nowy plik
|
||||||
|
• cat [file] - wyświetl zawartość pliku
|
||||||
|
• rm [file] - usuń plik
|
||||||
|
• clear - wyczyść konsolę`;
|
||||||
|
|
||||||
|
this.printResponse(help);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateHistory(direction) {
|
||||||
|
if (this.history.length === 0) return;
|
||||||
|
|
||||||
|
const input = document.getElementById('consoleInput');
|
||||||
|
|
||||||
|
if (direction === -1) { // Strzałka w górę
|
||||||
|
if (this.historyIndex < this.history.length - 1) {
|
||||||
|
if (this.historyIndex === -1) {
|
||||||
|
this.currentCommand = input.value;
|
||||||
|
}
|
||||||
|
this.historyIndex++;
|
||||||
|
}
|
||||||
|
} else { // Strzałka w dół
|
||||||
|
if (this.historyIndex > 0) {
|
||||||
|
this.historyIndex--;
|
||||||
|
} else if (this.historyIndex === 0) {
|
||||||
|
this.historyIndex = -1;
|
||||||
|
input.value = this.currentCommand;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.historyIndex >= 0) {
|
||||||
|
input.value = this.history[this.history.length - 1 - this.historyIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Przesuń kursor na koniec
|
||||||
|
input.selectionStart = input.selectionEnd = input.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
autoComplete(input) {
|
||||||
|
const current = input.value;
|
||||||
|
// Proste autouzupełnianie - można rozbudować
|
||||||
|
const commands = ['help', 'ls', 'cd', 'pwd', 'mkdir', 'touch', 'cat', 'rm', 'clear'];
|
||||||
|
|
||||||
|
const matching = commands.filter(cmd => cmd.startsWith(current));
|
||||||
|
if (matching.length === 1) {
|
||||||
|
input.value = matching[0];
|
||||||
|
} else if (matching.length > 1) {
|
||||||
|
this.printInfo(`Możliwe komendy: ${matching.join(', ')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
const loader = document.createElement('div');
|
||||||
|
loader.id = 'consoleLoading';
|
||||||
|
loader.className = 'console-loading';
|
||||||
|
output.appendChild(loader);
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
const loader = document.getElementById('consoleLoading');
|
||||||
|
if (loader) {
|
||||||
|
loader.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToBottom() {
|
||||||
|
const output = document.getElementById('consoleOutput');
|
||||||
|
output.scrollTop = output.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHistory() {
|
||||||
|
// Załaduj historię z localStorage
|
||||||
|
const savedHistory = localStorage.getItem(`console_history_${this.username}_${this.project}`);
|
||||||
|
if (savedHistory) {
|
||||||
|
this.history = JSON.parse(savedHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zapisz historię przy wyjściu
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
localStorage.setItem(`console_history_${this.username}_${this.project}`,
|
||||||
|
JSON.stringify(this.history.slice(-20)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicjalizacja konsoli
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const username = document.body.dataset.username;
|
||||||
|
const project = document.body.dataset.project;
|
||||||
|
|
||||||
|
if (username && project) {
|
||||||
|
window.qlconsole = new QLConsole(username, project);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Globalne funkcje dostępne z konsoli
|
||||||
|
window.Console = {
|
||||||
|
clear: function() {
|
||||||
|
if (window.qlconsole) {
|
||||||
|
window.qlconsole.clearConsole();
|
||||||
|
window.qlconsole.printWelcome();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
run: function(command) {
|
||||||
|
if (window.qlconsole) {
|
||||||
|
window.qlconsole.executeCommand(command);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
help: function() {
|
||||||
|
if (window.qlconsole) {
|
||||||
|
window.qlconsole.showHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
681
static/js/editor.js
Normal file
681
static/js/editor.js
Normal file
@ -0,0 +1,681 @@
|
|||||||
|
class QLEditor {
|
||||||
|
constructor(username, project, selectedFile) {
|
||||||
|
this.username = username;
|
||||||
|
this.project = project;
|
||||||
|
this.selectedFile = selectedFile;
|
||||||
|
this.editor = null;
|
||||||
|
this.autoSaveTimer = null;
|
||||||
|
this.isDirty = false;
|
||||||
|
this.consoleHistory = [];
|
||||||
|
this.consoleHistoryIndex = -1;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.bindEvents();
|
||||||
|
this.setupAutoSave();
|
||||||
|
this.updateStatus();
|
||||||
|
this.initConsole();
|
||||||
|
console.log(`QL Editor initialized for ${this.username}/${this.project}/${this.selectedFile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
// Zmiana pliku
|
||||||
|
document.querySelectorAll('.file-item').forEach(item => {
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
const filename = e.currentTarget.dataset.filename;
|
||||||
|
if (filename !== this.selectedFile) {
|
||||||
|
this.switchFile(filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zapisz przycisk
|
||||||
|
document.getElementById('saveBtn')?.addEventListener('click', () => this.saveFile());
|
||||||
|
|
||||||
|
// Nowy plik
|
||||||
|
document.getElementById('newFileBtn')?.addEventListener('click', () => this.showNewFileModal());
|
||||||
|
|
||||||
|
// Uruchom kod
|
||||||
|
document.getElementById('runBtn')?.addEventListener('click', () => this.runCode());
|
||||||
|
|
||||||
|
// Formatuj kod
|
||||||
|
document.getElementById('formatBtn')?.addEventListener('click', () => this.formatCode());
|
||||||
|
|
||||||
|
// Śledź zmiany w edytorze
|
||||||
|
const textarea = document.getElementById('codeEditor');
|
||||||
|
if (textarea) {
|
||||||
|
textarea.addEventListener('input', () => {
|
||||||
|
this.isDirty = true;
|
||||||
|
this.updateStatus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Skróty klawiaturowe w edytorze
|
||||||
|
textarea.addEventListener('keydown', (e) => {
|
||||||
|
// Ctrl+S - zapisz
|
||||||
|
if (e.ctrlKey && e.key === 's') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.saveFile();
|
||||||
|
}
|
||||||
|
// Ctrl+Enter - uruchom
|
||||||
|
if (e.ctrlKey && e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.runCode();
|
||||||
|
}
|
||||||
|
// Ctrl+F - formatuj
|
||||||
|
if (e.ctrlKey && e.key === 'f') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.formatCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Globalne skróty klawiaturowe
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.ctrlKey && e.key === 's') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.saveFile();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ostrzeżenie przed opuszczeniem strony
|
||||||
|
window.addEventListener('beforeunload', (e) => {
|
||||||
|
if (this.isDirty) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = 'Masz niezapisane zmiany. Czy na pewno chcesz opuścić stronę?';
|
||||||
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initConsole() {
|
||||||
|
const consoleInput = document.getElementById('consoleInput');
|
||||||
|
const consoleSend = document.getElementById('consoleSend');
|
||||||
|
const clearConsoleBtn = document.getElementById('clearConsoleBtn');
|
||||||
|
|
||||||
|
if (!consoleInput) return;
|
||||||
|
|
||||||
|
// Enter w konsoli
|
||||||
|
consoleInput.addEventListener('keypress', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
this.executeConsoleCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strzałki dla historii
|
||||||
|
if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.navigateConsoleHistory(-1);
|
||||||
|
}
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.navigateConsoleHistory(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Przycisk wysyłania
|
||||||
|
if (consoleSend) {
|
||||||
|
consoleSend.addEventListener('click', () => this.executeConsoleCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Przycisk czyszczenia konsoli
|
||||||
|
if (clearConsoleBtn) {
|
||||||
|
clearConsoleBtn.addEventListener('click', () => this.clearConsole());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-focus na konsoli
|
||||||
|
setTimeout(() => {
|
||||||
|
consoleInput?.focus();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
switchFile(filename) {
|
||||||
|
if (this.isDirty) {
|
||||||
|
if (!confirm('Masz niezapisane zmiany. Czy na pewno chcesz zmienić plik?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveFile() {
|
||||||
|
const content = document.getElementById('codeEditor').value;
|
||||||
|
const filename = this.selectedFile;
|
||||||
|
|
||||||
|
if (!content.trim()) {
|
||||||
|
this.showNotification('Plik jest pusty!', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.showNotification('Zapisywanie...', 'info');
|
||||||
|
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: filename,
|
||||||
|
content: content
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.showNotification('✓ Plik zapisany pomyślnie', 'success');
|
||||||
|
this.isDirty = false;
|
||||||
|
this.updateStatus();
|
||||||
|
this.addConsoleMessage('✓ Plik zapisany', 'success');
|
||||||
|
|
||||||
|
// Zaktualizuj czas modyfikacji w liście plików
|
||||||
|
const fileItem = document.querySelector(`[data-filename="${filename}"] .file-modified`);
|
||||||
|
if (fileItem) {
|
||||||
|
const now = new Date();
|
||||||
|
fileItem.textContent = now.toLocaleTimeString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Błąd zapisu');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
||||||
|
this.addConsoleMessage(`✗ Błąd zapisu: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFile(filename) {
|
||||||
|
if (!filename) {
|
||||||
|
this.showNotification('Podaj nazwę pliku', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/create`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: filename
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.showNotification('✓ Plik utworzony pomyślnie', 'success');
|
||||||
|
this.addConsoleMessage(`✓ Utworzono plik: ${filename}`, 'success');
|
||||||
|
|
||||||
|
// Przełącz na nowy plik
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/editor/${this.username}/${this.project}?file=${filename}`;
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Błąd tworzenia pliku');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile() {
|
||||||
|
if (!this.selectedFile || this.selectedFile === 'README.md') {
|
||||||
|
this.showNotification('Nie można usunąć tego pliku', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirm(`Czy na pewno chcesz usunąć plik "${this.selectedFile}"?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/delete`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: this.selectedFile
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.showNotification('✓ Plik usunięty pomyślnie', 'success');
|
||||||
|
this.addConsoleMessage(`✓ Usunięto plik: ${this.selectedFile}`, 'success');
|
||||||
|
|
||||||
|
// Wróć do głównego pliku
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = `/editor/${this.username}/${this.project}`;
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Błąd usuwania pliku');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showNotification(`✗ Błąd: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showNewFileModal() {
|
||||||
|
const filename = prompt('Podaj nazwę nowego pliku (np. program.ql):', 'nowy.ql');
|
||||||
|
if (filename) {
|
||||||
|
this.createFile(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAutoSave() {
|
||||||
|
// Auto-zapis co 30 sekund jeśli są zmiany
|
||||||
|
this.autoSaveTimer = setInterval(() => {
|
||||||
|
if (this.isDirty) {
|
||||||
|
this.autoSave();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async autoSave() {
|
||||||
|
const content = document.getElementById('codeEditor').value;
|
||||||
|
const filename = this.selectedFile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/save`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
filename: filename,
|
||||||
|
content: content
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.addConsoleMessage('✓ Auto-zapisano plik', 'success');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Auto-save error:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus() {
|
||||||
|
const status = document.getElementById('editorStatus');
|
||||||
|
if (status) {
|
||||||
|
const editor = document.getElementById('codeEditor');
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
const lines = editor.value.split('\n').length;
|
||||||
|
const chars = editor.value.length;
|
||||||
|
const dirtyIndicator = this.isDirty ? ' ●' : '';
|
||||||
|
|
||||||
|
status.textContent = `${this.selectedFile} | Linie: ${lines} | Znaki: ${chars}${dirtyIndicator}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async runCode() {
|
||||||
|
const code = document.getElementById('codeEditor').value;
|
||||||
|
|
||||||
|
if (!code.trim()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WYCZYŚĆ KONSOLĘ PRZED NOWYM WYKONANIEM
|
||||||
|
this.clearConsoleForRun();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/editor/${this.username}/${this.project}/execute`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ code: code })
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
// WYŚWIETL TYLKO OUTPUT Z PRINT()
|
||||||
|
if (data.output && data.output.length > 0) {
|
||||||
|
data.output.forEach(line => {
|
||||||
|
if (line && line.trim()) {
|
||||||
|
this.addConsoleMessage(line, 'response');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Tylko błędy
|
||||||
|
if (data.output && data.output.length > 0) {
|
||||||
|
this.addConsoleMessage(data.output[0], 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.addConsoleMessage(`Błąd: ${error.message}`, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dodaj tę nową funkcję do klasy:
|
||||||
|
clearConsoleForRun() {
|
||||||
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
|
if (!consoleOutput) return;
|
||||||
|
|
||||||
|
// WYCZYŚĆ KONSOLĘ, ale NIE dodawaj komunikatów "QL Console"
|
||||||
|
consoleOutput.innerHTML = '';
|
||||||
|
|
||||||
|
// Możesz dodać tylko nazwę polecenia jak w terminalu:
|
||||||
|
// this.addConsoleMessage(`$ ql ${this.selectedFile}`, 'command');
|
||||||
|
}
|
||||||
|
|
||||||
|
formatCode() {
|
||||||
|
const editor = document.getElementById('codeEditor');
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
let code = editor.value;
|
||||||
|
|
||||||
|
// Proste formatowanie kodu QL
|
||||||
|
// 1. Zamień taby na 4 spacje
|
||||||
|
code = code.replace(/\t/g, ' ');
|
||||||
|
|
||||||
|
// 2. Usuń nadmiarowe puste linie
|
||||||
|
code = code.replace(/\n\s*\n\s*\n/g, '\n\n');
|
||||||
|
|
||||||
|
// 3. Usuń białe znaki na końcu linii
|
||||||
|
code = code.replace(/\s+$/gm, '');
|
||||||
|
|
||||||
|
// 4. Dodaj wcięcia dla bloków
|
||||||
|
let indent = 0;
|
||||||
|
const lines = code.split('\n');
|
||||||
|
const formattedLines = [];
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
line = line.trim();
|
||||||
|
|
||||||
|
// Zmniejsz wcięcie przed zamknięciem bloku
|
||||||
|
if (line.includes('}')) {
|
||||||
|
indent = Math.max(0, indent - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dodaj wcięcie
|
||||||
|
const indentedLine = ' '.repeat(indent) + line;
|
||||||
|
formattedLines.push(indentedLine);
|
||||||
|
|
||||||
|
// Zwiększ wcięcie po otwarciu bloku
|
||||||
|
if (line.includes('{')) {
|
||||||
|
indent++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.value = formattedLines.join('\n');
|
||||||
|
this.isDirty = true;
|
||||||
|
this.updateStatus();
|
||||||
|
this.addConsoleMessage('✓ Kod sformatowany', 'success');
|
||||||
|
this.showNotification('Kod sformatowany!', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
executeConsoleCommand() {
|
||||||
|
const consoleInput = document.getElementById('consoleInput');
|
||||||
|
if (!consoleInput) return;
|
||||||
|
|
||||||
|
const command = consoleInput.value.trim();
|
||||||
|
if (!command) return;
|
||||||
|
|
||||||
|
// Dodaj do historii
|
||||||
|
this.consoleHistory.push(command);
|
||||||
|
this.consoleHistoryIndex = -1;
|
||||||
|
|
||||||
|
// Wyświetl komendę
|
||||||
|
this.addConsoleLine(`> ${command}`, 'command');
|
||||||
|
|
||||||
|
// Wykonaj komendę
|
||||||
|
const result = this.processConsoleCommand(command);
|
||||||
|
if (result) {
|
||||||
|
this.addConsoleLine(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wyczyść input
|
||||||
|
consoleInput.value = '';
|
||||||
|
consoleInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
processConsoleCommand(command) {
|
||||||
|
const parts = command.trim().split(' ');
|
||||||
|
const cmd = parts[0].toLowerCase();
|
||||||
|
const args = parts.slice(1);
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
'help': () => {
|
||||||
|
return `Dostępne komendy:
|
||||||
|
help - wyświetla tę pomoc
|
||||||
|
ls - lista plików w projekcie
|
||||||
|
clear - czyści konsolę
|
||||||
|
run - uruchamia aktualny plik
|
||||||
|
save - zapisuje aktualny plik
|
||||||
|
format - formatuje kod
|
||||||
|
status - pokazuje status projektu
|
||||||
|
time - pokazuje aktualny czas
|
||||||
|
echo [text] - wyświetla tekst`;
|
||||||
|
},
|
||||||
|
|
||||||
|
'ls': () => {
|
||||||
|
const fileList = document.getElementById('fileList');
|
||||||
|
if (!fileList) return 'Brak listy plików';
|
||||||
|
|
||||||
|
const files = Array.from(fileList.querySelectorAll('.list-group-item'))
|
||||||
|
.map(item => item.dataset.filename)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (files.length === 0) return 'Brak plików w projekcie';
|
||||||
|
return files.map(f => `📄 ${f}`).join('\n');
|
||||||
|
},
|
||||||
|
|
||||||
|
'clear': () => {
|
||||||
|
this.clearConsole();
|
||||||
|
return 'Konsola wyczyszczona';
|
||||||
|
},
|
||||||
|
|
||||||
|
'run': () => {
|
||||||
|
this.runCode();
|
||||||
|
return 'Uruchamianie kodu...';
|
||||||
|
},
|
||||||
|
|
||||||
|
'save': () => {
|
||||||
|
this.saveFile();
|
||||||
|
return 'Zapisywanie pliku...';
|
||||||
|
},
|
||||||
|
|
||||||
|
'format': () => {
|
||||||
|
this.formatCode();
|
||||||
|
return 'Formatowanie kodu...';
|
||||||
|
},
|
||||||
|
|
||||||
|
'status': () => {
|
||||||
|
const editor = document.getElementById('codeEditor');
|
||||||
|
if (!editor) return 'Brak edytora';
|
||||||
|
|
||||||
|
const lines = editor.value.split('\n').length;
|
||||||
|
const chars = editor.value.length;
|
||||||
|
return `Projekt: ${this.project}
|
||||||
|
Plik: ${this.selectedFile}
|
||||||
|
Linie: ${lines}
|
||||||
|
Znaki: ${chars}
|
||||||
|
Zmiany: ${this.isDirty ? 'TAK' : 'NIE'}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
'time': () => new Date().toLocaleString(),
|
||||||
|
|
||||||
|
'echo': () => args.join(' ')
|
||||||
|
};
|
||||||
|
|
||||||
|
if (commands[cmd]) {
|
||||||
|
try {
|
||||||
|
const result = commands[cmd]();
|
||||||
|
return typeof result === 'string' ? result : '';
|
||||||
|
} catch (error) {
|
||||||
|
return `Błąd: ${error.message}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Nieznana komenda: ${cmd}\nWpisz 'help' aby zobaczyć dostępne komendy`;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateConsoleHistory(direction) {
|
||||||
|
const consoleInput = document.getElementById('consoleInput');
|
||||||
|
if (!consoleInput || this.consoleHistory.length === 0) return;
|
||||||
|
|
||||||
|
if (direction === -1) { // Strzałka w górę
|
||||||
|
if (this.consoleHistoryIndex < this.consoleHistory.length - 1) {
|
||||||
|
if (this.consoleHistoryIndex === -1) {
|
||||||
|
this.currentCommand = consoleInput.value;
|
||||||
|
}
|
||||||
|
this.consoleHistoryIndex++;
|
||||||
|
}
|
||||||
|
} else { // Strzałka w dół
|
||||||
|
if (this.consoleHistoryIndex > 0) {
|
||||||
|
this.consoleHistoryIndex--;
|
||||||
|
} else if (this.consoleHistoryIndex === 0) {
|
||||||
|
this.consoleHistoryIndex = -1;
|
||||||
|
consoleInput.value = this.currentCommand || '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.consoleHistoryIndex >= 0) {
|
||||||
|
consoleInput.value = this.consoleHistory[this.consoleHistory.length - 1 - this.consoleHistoryIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Przesuń kursor na koniec
|
||||||
|
consoleInput.selectionStart = consoleInput.selectionEnd = consoleInput.value.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
addConsoleLine(text, type = 'response') {
|
||||||
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
|
if (!consoleOutput) return;
|
||||||
|
|
||||||
|
const line = document.createElement('div');
|
||||||
|
line.className = `console-${type}`;
|
||||||
|
line.textContent = text;
|
||||||
|
consoleOutput.appendChild(line);
|
||||||
|
consoleOutput.scrollTop = consoleOutput.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
addConsoleMessage(message, type = 'info') {
|
||||||
|
this.addConsoleLine(message, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearConsole() {
|
||||||
|
const consoleOutput = document.getElementById('consoleOutput');
|
||||||
|
if (!consoleOutput) return;
|
||||||
|
|
||||||
|
consoleOutput.innerHTML = '';
|
||||||
|
this.addConsoleMessage('=== QL Console ===', 'success');
|
||||||
|
this.addConsoleMessage('Wpisz \'help\' aby uzyskać pomoc', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
showNotification(message, type = 'info') {
|
||||||
|
// Utwórz powiadomienie Bootstrap
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
|
||||||
|
alert.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Ikona w zależności od typu
|
||||||
|
let icon = 'bi-info-circle';
|
||||||
|
if (type === 'success') icon = 'bi-check-circle';
|
||||||
|
if (type === 'warning') icon = 'bi-exclamation-triangle';
|
||||||
|
if (type === 'danger' || type === 'error') icon = 'bi-x-circle';
|
||||||
|
|
||||||
|
alert.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi ${icon} me-2"></i>
|
||||||
|
<span>${message}</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(alert);
|
||||||
|
|
||||||
|
// Auto-ukrywanie
|
||||||
|
setTimeout(() => {
|
||||||
|
if (alert.parentNode) {
|
||||||
|
// Użyj Bootstrapowego zamykania
|
||||||
|
const bsAlert = new bootstrap.Alert(alert);
|
||||||
|
bsAlert.close();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Czyszczenie interwału przy zamknięciu
|
||||||
|
destroy() {
|
||||||
|
if (this.autoSaveTimer) {
|
||||||
|
clearInterval(this.autoSaveTimer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicjalizacja edytora po załadowaniu strony
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const username = document.body.dataset.username;
|
||||||
|
const project = document.body.dataset.project;
|
||||||
|
const selectedFile = document.body.dataset.selectedFile;
|
||||||
|
|
||||||
|
if (username && project && selectedFile) {
|
||||||
|
window.qleditor = new QLEditor(username, project, selectedFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Podświetlanie składni dla QL (podstawowe)
|
||||||
|
const editor = document.getElementById('codeEditor');
|
||||||
|
if (editor) {
|
||||||
|
// Zapisz oryginalny tekst dla podświetlania
|
||||||
|
let originalText = editor.value;
|
||||||
|
|
||||||
|
editor.addEventListener('input', function() {
|
||||||
|
// Możesz dodać podświetlanie składni tutaj
|
||||||
|
// Przykład prostego podświetlania:
|
||||||
|
const keywords = ['func', 'use', 'if', 'else', 'while', 'for', 'return', 'print', 'true', 'false', 'null'];
|
||||||
|
let text = this.value;
|
||||||
|
|
||||||
|
// To tylko przykład - w prawdziwym edytorze użyj biblioteki jak CodeMirror lub Monaco
|
||||||
|
keywords.forEach(keyword => {
|
||||||
|
const regex = new RegExp(`\\b${keyword}\\b`, 'g');
|
||||||
|
text = text.replace(regex, `<span class="text-primary fw-bold">${keyword}</span>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zapisz oryginalny tekst
|
||||||
|
originalText = this.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Czyszczenie przy opuszczeniu strony
|
||||||
|
window.addEventListener('unload', () => {
|
||||||
|
if (window.qleditor) {
|
||||||
|
window.qleditor.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Globalne funkcje dostępne z konsoli (opcjonalnie)
|
||||||
|
window.Console = {
|
||||||
|
run: function() {
|
||||||
|
if (window.qleditor) {
|
||||||
|
window.qleditor.runCode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
save: function() {
|
||||||
|
if (window.qleditor) {
|
||||||
|
window.qleditor.saveFile();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
format: function() {
|
||||||
|
if (window.qleditor) {
|
||||||
|
window.qleditor.formatCode();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clear: function() {
|
||||||
|
if (window.qleditor) {
|
||||||
|
window.qleditor.clearConsole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
256
static/style.css
256
static/style.css
@ -1,256 +0,0 @@
|
|||||||
/* =========================
|
|
||||||
RESET PODSTAWOWY
|
|
||||||
========================= */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
BODY
|
|
||||||
========================= */
|
|
||||||
body {
|
|
||||||
background: #f4f7fc;
|
|
||||||
color: #333;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
NAGŁÓWEK
|
|
||||||
========================= */
|
|
||||||
h2 {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: #4a4a4a;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
KONTENER MODELI
|
|
||||||
========================= */
|
|
||||||
#models-container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
KARTA MODELU
|
|
||||||
========================= */
|
|
||||||
.model-card {
|
|
||||||
position: relative;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 6px 18px rgba(0,0,0,0.08);
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 12px 24px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
3 KROPKI (MENU)
|
|
||||||
========================= */
|
|
||||||
.menu-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
right: 14px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 22px;
|
|
||||||
color: #777;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-btn:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
BOCZNY PANEL (SLIDE)
|
|
||||||
========================= */
|
|
||||||
.side-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: -260px;
|
|
||||||
width: 250px;
|
|
||||||
height: 100%;
|
|
||||||
background: #111;
|
|
||||||
color: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
transition: right 0.3s ease;
|
|
||||||
z-index: 20;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu.open {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu h4 {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu input {
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: none;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu button {
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
background: #6c63ff;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu button:hover {
|
|
||||||
background: #574fd6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
NAZWA MODELU
|
|
||||||
========================= */
|
|
||||||
.model-card h3 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #1a1a1a;
|
|
||||||
font-size: 1.4em;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
OPIS MODELU (Markdown)
|
|
||||||
========================= */
|
|
||||||
.description {
|
|
||||||
font-style: italic;
|
|
||||||
color: #555;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
FORMULARZ
|
|
||||||
========================= */
|
|
||||||
form.predict-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.predict-form label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.predict-form input {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
font-size: 0.95em;
|
|
||||||
transition: border 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.predict-form input:focus {
|
|
||||||
border-color: #6c63ff;
|
|
||||||
box-shadow: 0 0 5px rgba(108, 99, 255, 0.4);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
PRZYCISK
|
|
||||||
========================= */
|
|
||||||
form.predict-form button {
|
|
||||||
padding: 10px 15px;
|
|
||||||
background: #6c63ff;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: background 0.2s, transform 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.predict-form button:hover {
|
|
||||||
background: #574fd6;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
OUTPUT
|
|
||||||
========================= */
|
|
||||||
.output {
|
|
||||||
margin-top: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #1a73e8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
DOWNLOAD
|
|
||||||
========================= */
|
|
||||||
.download {
|
|
||||||
margin-top: 15px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #6c63ff;
|
|
||||||
margin-right: 10px;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.download a:hover {
|
|
||||||
color: #574fd6;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
LINIE
|
|
||||||
========================= */
|
|
||||||
hr {
|
|
||||||
border: none;
|
|
||||||
height: 1px;
|
|
||||||
background: #e0e0e0;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================
|
|
||||||
RESPONSYWNOŚĆ
|
|
||||||
========================= */
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
h2 {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-card {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form.predict-form input,
|
|
||||||
form.predict-form button {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.side-menu {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.side-menu.open {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
.side-menu {
|
|
||||||
position: fixed;
|
|
||||||
}
|
|
||||||
163
templates/dashboard.html
Normal file
163
templates/dashboard.html
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Dashboard - QL Platform</title>
|
||||||
|
<!-- Bootstrap 5 z ikonkami -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
height: 200px;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.dashboard-card:hover {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
box-shadow: 0 15px 30px rgba(0,0,0,0.3);
|
||||||
|
}
|
||||||
|
.dashboard-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0,0,0,0.1);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.dashboard-card-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.card-icon {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
.card-title {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.card-desc {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.9;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.welcome-card {
|
||||||
|
background: linear-gradient(135deg, #2c3e50 0%, #4ca1af 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.user-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #667eea;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<!-- Karta powitalna -->
|
||||||
|
<div class="welcome-card">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<h1>Witaj, {{ username }}! 👋</h1>
|
||||||
|
<p class="lead">Twoja platforma programistyczna QL jest gotowa do działania.</p>
|
||||||
|
<p>Wybierz jedną z poniższych opcji, aby rozpocząć pracę.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 text-center">
|
||||||
|
<div class="user-avatar">
|
||||||
|
<i class="bi bi-person-circle"></i>
|
||||||
|
</div>
|
||||||
|
<h5>{{ username }}</h5>
|
||||||
|
<a href="{{ url_for('logout') }}" class="btn btn-outline-light btn-sm mt-2">
|
||||||
|
<i class="bi bi-box-arrow-right"></i> Wyloguj
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Kafelki -->
|
||||||
|
<div class="row g-4">
|
||||||
|
{% for tile in tiles %}
|
||||||
|
<div class="col-md-4 col-lg-3">
|
||||||
|
<a href="{{ tile.link }}" class="text-decoration-none">
|
||||||
|
<div class="dashboard-card" style="background: linear-gradient(135deg, {{ tile.bg_color }} 0%, {{ tile.bg_color2 }} 100%);">
|
||||||
|
<div class="dashboard-card-content">
|
||||||
|
<div class="card-icon">
|
||||||
|
<i class="bi {{ tile.icon }}"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="card-title">{{ tile.title }}</h5>
|
||||||
|
<p class="card-desc">{{ tile.desc }}</p>
|
||||||
|
<span class="btn btn-outline-light btn-sm">
|
||||||
|
Otwórz <i class="bi bi-arrow-right"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Projekt list -->
|
||||||
|
{% if projects %}
|
||||||
|
<div class="mt-5">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<h5 class="mb-0"><i class="bi bi-folder"></i> Twoje projekty</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
{% for project in projects %}
|
||||||
|
<div class="col-md-3 mb-3">
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<i class="bi bi-folder-fill text-warning" style="font-size: 2rem;"></i>
|
||||||
|
<h6 class="mt-2">{{ project }}</h6>
|
||||||
|
<a href="{{ url_for('ql_editor', username=username, project=project) }}"
|
||||||
|
class="btn btn-primary btn-sm mt-2">
|
||||||
|
<i class="bi bi-pencil"></i> Edytuj
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bootstrap JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
34
templates/login.html
Normal file
34
templates/login.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Logowanie</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Hasło</label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Zaloguj</button>
|
||||||
|
</form>
|
||||||
|
<p class="mt-3">Nie masz konta? <a href="{{ url_for('register') }}">Zarejestruj się</a></p>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="alert alert-warning mt-3">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div>{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
templates/models.html
Normal file
22
templates/models.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Modele QL</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('dashboard') }}">QL Platform</a>
|
||||||
|
<span class="text-light">{{ username }}</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>Modele i Funkcje</h2>
|
||||||
|
<p>Ta sekcja jest w budowie...</p>
|
||||||
|
<a href="{{ url_for('dashboard') }}" class="btn btn-primary">Powrót</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
34
templates/register.html
Normal file
34
templates/register.html
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Rejestracja</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-light">
|
||||||
|
<div class="container mt-5">
|
||||||
|
<h2>Rejestracja</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Username</label>
|
||||||
|
<input type="text" name="username" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Hasło</label>
|
||||||
|
<input type="password" name="password" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-success">Zarejestruj się</button>
|
||||||
|
</form>
|
||||||
|
<p class="mt-3">Masz już konto? <a href="{{ url_for('login') }}">Zaloguj się</a></p>
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="alert alert-warning mt-3">
|
||||||
|
{% for message in messages %}
|
||||||
|
<div>{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,100 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="pl">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>AI Models</title>
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<h2>Lista modeli AI</h2>
|
|
||||||
|
|
||||||
<div id="models-container">
|
|
||||||
{% for name, model in models.items() %}
|
|
||||||
<div class="model-card" id="model-{{name}}">
|
|
||||||
|
|
||||||
<!-- 3 KROPKI -->
|
|
||||||
<div class="menu-btn" onclick="toggleMenu('{{name}}')">⋮</div>
|
|
||||||
|
|
||||||
<!-- WYSUWANY PANEL -->
|
|
||||||
<div class="side-menu" id="menu-{{name}}">
|
|
||||||
<h4>Akcje modelu</h4>
|
|
||||||
|
|
||||||
<input type="text" value="{{name}}" readonly>
|
|
||||||
|
|
||||||
<button onclick="goToEditor('{{name}}')">
|
|
||||||
Edytor kodu
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button onclick="toggleMenu('{{name}}')">
|
|
||||||
Zamknij
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- NAZWA -->
|
|
||||||
<h3>{{name}}</h3>
|
|
||||||
|
|
||||||
<!-- OPIS (Markdown -> HTML) -->
|
|
||||||
<div class="description">
|
|
||||||
{{ model.md_content | safe }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- FORMULARZ -->
|
|
||||||
<form class="predict-form">
|
|
||||||
{% for inp in model.inputs %}
|
|
||||||
<label>{{inp}}:</label>
|
|
||||||
<input name="{{inp}}">
|
|
||||||
{% endfor %}
|
|
||||||
<button type="submit">Wyślij</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- OUTPUT -->
|
|
||||||
<div class="output" id="output-{{name}}"></div>
|
|
||||||
|
|
||||||
<!-- DOWNLOAD -->
|
|
||||||
{% if model.meta.downloadable %}
|
|
||||||
<div class="download">
|
|
||||||
<a href="/download/{{name}}/model.py" target="_blank">model.py</a> |
|
|
||||||
<a href="/download/{{name}}/description.md" target="_blank">description.md</a> |
|
|
||||||
<a href="/download/{{name}}/meta.json" target="_blank">meta.json</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* ---- PREDICT ---- */
|
|
||||||
document.querySelectorAll(".predict-form").forEach(form => {
|
|
||||||
form.addEventListener("submit", async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const parent = e.target.closest(".model-card");
|
|
||||||
const modelName = parent.id.replace("model-", "");
|
|
||||||
const outputDiv = parent.querySelector(".output");
|
|
||||||
|
|
||||||
const formData = new FormData(form);
|
|
||||||
const response = await fetch(`/predict/${modelName}`, {
|
|
||||||
method: "POST",
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
outputDiv.textContent = data.output;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* ---- MENU ---- */
|
|
||||||
function toggleMenu(name) {
|
|
||||||
const menu = document.getElementById("menu-" + name);
|
|
||||||
menu.classList.toggle("open");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---- EDITOR ---- */
|
|
||||||
function goToEditor(name) {
|
|
||||||
window.location.href = `/editor/${name}`;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
45
templates/user_projects.html
Normal file
45
templates/user_projects.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="pl">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Moje Projekty</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-dark bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('dashboard') }}">QL Platform</a>
|
||||||
|
<span class="text-light">Projekty: {{ username }}</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<h2>Moje Projekty</h2>
|
||||||
|
|
||||||
|
{% for project in projects %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-folder"></i> {{ project.name }}
|
||||||
|
</h5>
|
||||||
|
<a href="{{ url_for('ql_editor', username=username, project=project.name) }}"
|
||||||
|
class="btn btn-primary btn-sm">
|
||||||
|
Otwórz w edytorze
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if project.readme %}
|
||||||
|
<div class="mb-3">{{ project.readme }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
<i class="bi bi-file-earmark-code"></i>
|
||||||
|
{{ project.files|length }} plików QL
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Powrót</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
users.json
Normal file
5
users.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Maro": {
|
||||||
|
"password": "770528"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user