Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature coding camp framework #3728

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
37 changes: 35 additions & 2 deletions timApp/document/course/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
Contains course related routes.
"""

from flask import Blueprint, current_app, Response
from flask import Blueprint, current_app, Response, request
from sqlalchemy.orm import selectinload

from timApp.auth.sessioninfo import get_current_user_object
from timApp.document.docentry import DocEntry, get_documents
from timApp.item.block import Block
from timApp.document.create_item import create_or_copy_item
from timApp.folder.folder import Folder
from timApp.item.block import Block, BlockType
from timApp.item.manage import do_copy_folder
from timApp.timdb.sqa import db
from timApp.util.flask.responsehelper import json_response

course_blueprint = Blueprint("course", __name__, url_prefix="/courses")
Expand Down Expand Up @@ -52,3 +56,32 @@ def get_documents_from_bookmark_folder(foldername: str) -> Response:
query_options=selectinload(DocEntry._block).selectinload(Block.tags),
)
return json_response(docs)


@course_blueprint.post("/from-template")
def create_course_from_template() -> Response:
data = {**request.get_json()}
copy_to_dir_name: str = data.get("copy_to_dir_name")
copy_from_id: str = data.get("copy_from_id")
copy_from_path: str = data.get("copy_from_path")
folder = Folder.find_by_path(copy_from_path)

# documents: list[DocEntry] = get_documents(
# include_nonpublic=True, filter_folder=copy_from_path
# )
#
# for item in documents:
# created = create_or_copy_item(
# f"kurssit/{copy_to_dir_name}/{item.short_name}",
# BlockType.Document,
# item.short_name,
# use_template=False,
# )

# TODO: provide info, if document exist

copied = do_copy_folder(
folder_id=folder.id, destination=f"oscar/camps/{copy_to_dir_name}", exclude=None
)

return Response(json_response(copied[0].url))
1 change: 1 addition & 0 deletions timApp/document/macroinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def get_macros_preserving_user(self) -> dict[str, object]:
"useremail": f"{self.macro_delimiter}useremail{self.macro_delimiter}",
"loggedUsername": f"{self.macro_delimiter}loggedUsername{self.macro_delimiter}",
"userfolder": f"{self.macro_delimiter}userfolder{self.macro_delimiter}",
"profilepicture": f"{self.macro_delimiter}profilepicture{self.macro_delimiter}",
}
)
return macros
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Add table for steps phase

Revision ID: c18fb65697ee
Revises: 417094192806
Create Date: 2024-11-14 09:12:12.236411

"""

# revision identifiers, used by Alembic.
revision = 'c18fb65697ee'
down_revision = '417094192806'

from alembic import op
import sqlalchemy as sa


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('stepsphase',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('document_id', sa.Integer(), nullable=False),
sa.Column('user_working_group', sa.Integer(), nullable=False),
sa.Column('user_group', sa.Integer(), nullable=False),
sa.Column('name', sa.Text(), nullable=False),
sa.Column('current_phase', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user_group'], ['usergroup.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('stepsphase')
# ### end Alembic commands ###
7 changes: 5 additions & 2 deletions timApp/modules/cs/js/util/file-select.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* eslint no-underscore-dangle: ["error", { "allow": ["files_", "multipleElements_"] }] */
import type {QueryList} from "@angular/core";
import type {OnInit, QueryList} from "@angular/core";
import {
ChangeDetectorRef,
Component,
Expand Down Expand Up @@ -433,7 +433,7 @@ export class FileSelectComponent {
[maxSize]="maxSize">
</file-select>`,
})
export class FileSelectManagerComponent {
export class FileSelectManagerComponent implements OnInit {
// TODO: translations
@Input() allowMultiple: boolean = true;
@Input() dragAndDrop: boolean = true;
Expand Down Expand Up @@ -461,6 +461,9 @@ export class FileSelectManagerComponent {
}[] = [];

constructor(public cdr: ChangeDetectorRef) {}
ngOnInit() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tein pakotetun puskun tässä välissä, Simon tekemät muutokset on temp haarassa tallessa.

console.log("File select init.");
}
Comment on lines +464 to +466
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially unnnecessary.


get multipleElements() {
return this.multipleElements_;
Expand Down
34 changes: 34 additions & 0 deletions timApp/modules/fields/js/standalone-textfield.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
@import "fields-box-common";

.form-control {
display: block;
width: 100%;
}

textarea.form-control {
height: unset;

}

p {
padding-top: 0px;
padding-left: 0px;
padding-bottom: 0px;
margin-bottom: 0px;
margin-top: 0px;
}

.timButton {
margin-bottom: 5px;
}

.textfield input{
padding: .3em;
margin: .5em 0 .5em 0;
}

.textfield textarea {
padding: .3em;
overflow:hidden;
height: auto;
}
140 changes: 140 additions & 0 deletions timApp/modules/fields/js/standalone-textfield.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Defines the client-side implementation of textfield/label plugin.
*/
import * as t from "io-ts";
import type {
ApplicationRef,
DoBootstrap,
OnInit,
SimpleChanges,
OnChanges,
} from "@angular/core";
import {
Component,
Output,
Input,
NgModule,
NgZone,
EventEmitter,
} from "@angular/core";
import {HttpClient, HttpClientModule} from "@angular/common/http";
import {FormsModule} from "@angular/forms";
import {TooltipModule} from "ngx-bootstrap/tooltip";
import type {PluginJson} from "tim/plugin/angular-plugin-base.directive";
import {TimUtilityModule} from "tim/ui/tim-utility.module";
import {PurifyModule} from "tim/util/purify.module";
import {registerPlugin} from "tim/plugin/pluginRegistry";
import {CommonModule} from "@angular/common";
import {nullable} from "tim/plugin/attributes";

export const FieldContent = t.union([t.string, t.number, t.null]);
export const FieldBasicData = t.type({
c: FieldContent,
});
export const FieldDataWithStyles = t.intersection([
FieldBasicData,
t.partial({
styles: nullable(t.record(t.string, t.string)),
}),
]);

export type InputType = "TEXTAREA" | "TEXT";

@Component({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mietin että ei ehkä sittenkään ole kovin mielekästä pitää tätä erillisenä komponenttina. Ajatus oli että textfield-plugin ja tämä sekä tulevaisuudessa muut mahdolliset kentät voisi käyttää ns "yleiskäyttöistä" tekstiboksia, mutta nyt kun katsoo tarkemmin mitä tuo user-profile-component tuolla tekstilaatikoillaan tekee niin ehkä ylläpidon kannalta on selkeämpi vain jättää tämä pois. Sen sijaan profiilikomponentissa voisi suoraan käyttää ja <textarea>-elementtejä.

Tällä hetkellä ainoa toisto mistä pääsisi eroon erillisellä komponentilla on nuo varoitusvärit, mutta niiden css-pätkien kopsiminen toiseen paikkaan ei ole kovin iso homma vrt. uuden komponentin ylläpito. Nuo nykyiset komponentin bindaukset saa helposti suoraan siihen -elementtiin, eikä silloin tarvitse kikkailla angularin inputtien ja eventtien kanssa. Esim valueChange voi olla suoraan ngModelChange-bindaus ja sisällön saa suoraan kaksisuuntaisella ngModel-bindauksella

selector: "tim-standalone-textfield",
standalone: true,
styleUrls: ["standalone-textfield.component.scss"],
imports: [FormsModule, TooltipModule, CommonModule],
template: `
<div>
<span class="textfield">
<input type="text"
*ngIf="inputType == 'TEXT'"
class="form-control textarea"
[name]="name"
[(ngModel)]="initialValue"
(ngModelChange)="updateInput()"
[ngModelOptions]="{standalone: true}"
[tooltip]="errormessage"
[placeholder]="placeholder"
[class.warnFrame]="inputWarn">
<textarea
*ngIf="inputType == 'TEXTAREA'"
class="form-control textarea"
[name]="name"
[(ngModel)]="initialValue"
[ngModelOptions]="{standalone: true}"
(ngModelChange)="updateInput()"
[tooltip]="errormessage"
[placeholder]="placeholder"
[class.warnFrame]="inputWarn">
</textarea>
</span>
</div>
`,
})
export class StandaloneTextfieldComponent
implements OnInit, OnChanges, PluginJson
{
@Input() inputType: InputType = "TEXT";
@Input() initialValue: string = "Your description.";
@Input() placeholder: string = "Words are powerful.";
@Input() name: string = "";
@Input() inputWarn: boolean | null = false;
@Output() valueChange = new EventEmitter<string>();
isRunning = false;
userword = "";
errormessage?: string;
styles: Record<string, string> = {};
saveFailed = false;

constructor(private http: HttpClient, private zone: NgZone) {
this.json = "{}";
}

ngOnInit() {}

ngOnChanges(change: SimpleChanges) {
console.log(change);
}

/**
* Returns (user) content in string form.
*/
getContent(): string {
return this.userword;
}

updateInput() {
this.valueChange.emit(this.initialValue);
console.log(this.inputWarn);
if (!this.inputWarn) {
this.inputWarn = true;
// this.hideSavedText = true;
}
}

json: string;
}

@NgModule({
imports: [
CommonModule,
HttpClientModule,
TimUtilityModule,
FormsModule,
PurifyModule,
TooltipModule.forRoot(),
StandaloneTextfieldComponent,
],
exports: [StandaloneTextfieldComponent],
})
export class StandaloneTextfieldModule implements DoBootstrap {
ngDoBootstrap(appRef: ApplicationRef) {}
}

registerPlugin(
"standalone-textfield",
StandaloneTextfieldModule,
StandaloneTextfieldComponent
);
1 change: 1 addition & 0 deletions timApp/plugin/containerLink.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def get_plugins() -> dict[str, PluginReg]:
PluginReg(
name="calendar", domain=internal_domain, port=qst_port, path="/calendar/"
),
PluginReg(name="steps", domain=internal_domain, port=qst_port, path="/steps/"),
PluginReg(
name="quantumCircuit",
domain=internal_domain,
Expand Down
1 change: 1 addition & 0 deletions timApp/plugin/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"importData",
"userSelect",
"calendar",
"steps",
"timMenu",
"symbolbutton",
}
Expand Down
Empty file added timApp/plugin/steps/__init__.py
Empty file.
Loading
Loading