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

629 feature add background images to canvas #631

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
/.vscode/launch.json
.DS_Store
dump.rdb
backend/secrets
1,266 changes: 709 additions & 557 deletions backend/package-lock.json

Large diffs are not rendered by default.

9 changes: 7 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@
"dotenv": "^16.0.0",
"express": "^4.17.3",
"express-jwt": "^7.7.0",
"gridfs-stream": "^1.1.1",
"ioredis": "^5.3.2",
"jsonwebtoken": "^8.5.1",
"migrate-mongo": "^9.0.0",
"mongo-dot-notation": "^3.1.0",
"mongoose": "^6.3.2",
"mongoose": "^6.7.5",
"mongoose-autopopulate": "^0.17.1",
"multer": "^1.4.5-lts.1",
"nodemon": "^2.0.16",
"prettier": "^2.8.8",
"socket.io": "^4.7.5",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"mongodb": "4.11.0"
},
"scripts": {
"build": "tsc",
Expand All @@ -41,7 +44,9 @@
"devDependencies": {
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.13",
"@types/gridfs-stream": "^0.5.39",
"@types/jsonwebtoken": "^8.5.8",
"@types/multer": "^1.4.12",
"@types/node": "^18.19.50",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.22.0",
Expand Down
79 changes: 78 additions & 1 deletion backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import todoItems from './api/todoItem';
import learner from './api/learner';
import { isAuthenticated } from './utils/auth';
import RedisClient from './utils/redis';

import Grid from 'gridfs-stream';
import multer from 'multer';
import { GridFSBucket } from 'mongodb';

import aiRouter from './api/ai';
import chatHistoryRouter from './api/chatHistory';
dotenv.config();
Expand All @@ -33,7 +38,7 @@ const dbName = process.env.DB_NAME;
const dbURI = `mongodb+srv://${dbUsername}:${dbPassword}@${dbUrl}.mongodb.net/${dbName}?retryWrites=true&w=majority`;

const redisHost = process.env.REDIS_HOST || 'localhost';
const redisPort = (process.env.REDIS_PORT || 6379) as number;
const redisPort = Number(process.env.REDIS_PORT) || 6379;
const redisPassword = process.env.REDIS_PASSWORD || '';

const app = express();
Expand Down Expand Up @@ -91,3 +96,75 @@ mongoose
console.log('HTTP server running at ' + port);
})
.catch((err) => console.log(err));

const connection = mongoose.connection;
let gfs: Grid.Grid;
let gridFSBucket: GridFSBucket; // Use GridFSBucket type directly

connection.once('open', () => {
gridFSBucket = new GridFSBucket(connection.db, {
bucketName: 'uploads',
});
gfs = Grid(connection.db, mongoose.mongo);
gfs.collection('uploads'); // Set the GridFS collection
console.log('MongoDB connected and GridFS set up');
});

const storage = multer.memoryStorage(); // Store files in memory temporarily
const upload = multer({ storage });

// Upload image to MongoDB using GridFS
app.post('/api/upload', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}

const uploadStream = gridFSBucket.openUploadStream(req.file.originalname, {
contentType: req.file.mimetype,
});

uploadStream.on('error', (error: Error) => {
return res.status(500).send('Error uploading file: ' + error.message);
});

uploadStream.on('finish', () => {
const fileId = uploadStream.id;

// Return the uploaded file ID to the client
res.status(201).json({
message: 'File uploaded successfully',
fileId: fileId,
// Optionally return other details if needed
});
});

uploadStream.end(req.file.buffer); // No callback here
});

// Delete image from MongoDB using GridFS
app.delete('/api/upload/:id', (req, res) => {
const fileId = new mongoose.Types.ObjectId(req.params.id);

gridFSBucket.delete(fileId, (err) => {
if (err) {
return res
.status(500)
.json({ message: 'Error deleting file', error: err.message });
}
return res.status(200).json({ message: 'File deleted successfully' });
});
});

app.get('/api/image/:id', (req, res) => {
const fileId = new mongoose.Types.ObjectId(req.params.id);

gfs.files.findOne({ _id: fileId }, (err, file) => {
if (!file || file.length === 0) {
return res.status(404).json({ message: 'File not found' });
}

// File found, stream it to the response
const readStream = gridFSBucket.openDownloadStream(file._id);
readStream.pipe(res);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,8 @@ <h1 mat-dialog-title>Board Configuration</h1>
<mat-tab-group dynamicHeight animationDuration="0ms">
<mat-tab label="Board">
<h2 style="margin-top: 20px">Board Type</h2>
<mat-radio-group
[(ngModel)]="boardType"
>
<mat-radio-button
style="margin-left: 10px"
value="BRAINSTORMING"
>
<mat-radio-group [(ngModel)]="boardType">
<mat-radio-button style="margin-left: 10px" value="BRAINSTORMING">
Idea generation
</mat-radio-button>
<mat-radio-button style="margin-left: 10px" value="QUESTION_AUTHORING">
Expand Down Expand Up @@ -77,33 +72,79 @@ <h2 style="margin-top: 20px">Board Type</h2>
<p>
<label><strong>Default View</strong></label>
<mat-radio-group [(ngModel)]="defaultView">
<mat-radio-button style="margin-left: 10px;" value="CANVAS"
[disabled]="viewSettings.allowCanvas === false">Canvas</mat-radio-button>
<mat-radio-button style="margin-left: 10px;" value="WORKSPACE"
[disabled]="viewSettings.allowWorkspace === false">Workspace</mat-radio-button>
<mat-radio-button style="margin-left: 10px;" value="BUCKETS"
[disabled]="viewSettings.allowBuckets === false">Buckets</mat-radio-button>
<mat-radio-button style="margin-left: 10px;" value="MONITOR"
[disabled]="viewSettings.allowMonitor === false">Monitor</mat-radio-button>
<mat-radio-button
style="margin-left: 10px"
value="CANVAS"
[disabled]="viewSettings.allowCanvas === false"
>Canvas</mat-radio-button
>
<mat-radio-button
style="margin-left: 10px"
value="WORKSPACE"
[disabled]="viewSettings.allowWorkspace === false"
>Workspace</mat-radio-button
>
<mat-radio-button
style="margin-left: 10px"
value="BUCKETS"
[disabled]="viewSettings.allowBuckets === false"
>Buckets</mat-radio-button
>
<mat-radio-button
style="margin-left: 10px"
value="MONITOR"
[disabled]="viewSettings.allowMonitor === false"
>Monitor</mat-radio-button
>
</mat-radio-group>
</p>
<p>
<label><strong>Enable/Disable Views</strong></label>
<mat-checkbox style="margin-left: 10px;" [(ngModel)]="viewSettings.allowCanvas"
[disabled]="defaultView === 'CANVAS'">Canvas</mat-checkbox>
<mat-checkbox style="margin-left: 10px;" [(ngModel)]="viewSettings.allowWorkspace"
[disabled]="defaultView === 'WORKSPACE'">Workspace</mat-checkbox>
<mat-checkbox style="margin-left: 10px;" [(ngModel)]="viewSettings.allowBuckets"
[disabled]="defaultView === 'BUCKETS'">Buckets</mat-checkbox>
<mat-checkbox style="margin-left: 10px;" [(ngModel)]="viewSettings.allowMonitor"
[disabled]="defaultView === 'MONITOR'">Monitor</mat-checkbox>
<mat-checkbox
style="margin-left: 10px"
[(ngModel)]="viewSettings.allowCanvas"
[disabled]="defaultView === 'CANVAS'"
>Canvas</mat-checkbox
>
<mat-checkbox
style="margin-left: 10px"
[(ngModel)]="viewSettings.allowWorkspace"
[disabled]="defaultView === 'WORKSPACE'"
>Workspace</mat-checkbox
>
<mat-checkbox
style="margin-left: 10px"
[(ngModel)]="viewSettings.allowBuckets"
[disabled]="defaultView === 'BUCKETS'"
>Buckets</mat-checkbox
>
<mat-checkbox
style="margin-left: 10px"
[(ngModel)]="viewSettings.allowMonitor"
[disabled]="defaultView === 'MONITOR'"
>Monitor</mat-checkbox
>
</p>
<h4>Set Background Image:</h4>
<mat-chip (click)="compressFile()" color="primary" selected>
<mat-chip
*ngIf="bgImgURL === null"
(click)="uploadImage()"
color="primary"
selected
>
<mat-icon class="chip-icon">upload</mat-icon>
Upload New Background Image
</mat-chip>
<mat-chip *ngIf="bgImgURL"> Image Uploaded </mat-chip>
</mat-tab>
<!-- Hidden file input -->
<input
type="file"
#fileInput
(change)="onFileSelected($event)"
style="display: none"
accept="image/*"
/>
<mat-tab label="Task">
<mat-form-field
appearance="outline"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
} from 'src/app/models/board';
import { Project } from 'src/app/models/project';
import { Tag } from 'src/app/models/tag';
// import { FileUploadService } from 'src/app/services/fileUpload.service';
import { FileUploadService } from 'src/app/services/fileUpload.service';
import { UserService } from 'src/app/services/user.service';
import { FabricUtils, ImageSettings } from 'src/app/utils/FabricUtils';
import { generateUniqueID } from 'src/app/utils/Utils';
import { TAG_DEFAULT_COLOR } from 'src/app/utils/constants';
import { environment } from 'src/environments/environment';

@Component({
selector: 'app-add-board-modal',
Expand All @@ -27,7 +28,7 @@
export class AddBoardModalComponent implements OnInit {
readonly tagDefaultColor = TAG_DEFAULT_COLOR;

boardID: string;
boardID: string = '';

permissions: BoardPermissions;
boardType: BoardType = BoardType.BRAINSTORMING;
Expand All @@ -38,7 +39,7 @@
boardName = '';
boardScope = BoardScope.PROJECT_SHARED;

bgImgURL: any = null;

Check warning on line 42 in frontend/src/app/components/add-board-modal/add-board-modal.component.ts

View workflow job for this annotation

GitHub Actions / Linting and Code Formating Check CI (frontend)

Unexpected any. Specify a different type
bgImgSettings: ImageSettings;

taskTitle = '';
Expand All @@ -48,7 +49,7 @@
defaultTags: Tag[];

newTagText = '';
newTagColor: any = TAG_DEFAULT_COLOR;

Check warning on line 52 in frontend/src/app/components/add-board-modal/add-board-modal.component.ts

View workflow job for this annotation

GitHub Actions / Linting and Code Formating Check CI (frontend)

Unexpected any. Specify a different type

initialZoom = 100;
backgroundSize = 100;
Expand All @@ -58,14 +59,14 @@

projects: Project[];
selectedProject = '';
selectedFile: File | null = null; // File to upload

constructor(
public dialogRef: MatDialogRef<AddBoardModalComponent>,
public UserService: UserService,
public userService: UserService,
// public fileUploadService: FileUploadService,
public fileUploadService: FileUploadService,
public fabricUtils: FabricUtils,
@Inject(MAT_DIALOG_DATA) public data: any

Check warning on line 69 in frontend/src/app/components/add-board-modal/add-board-modal.component.ts

View workflow job for this annotation

GitHub Actions / Linting and Code Formating Check CI (frontend)

Unexpected any. Specify a different type
) {
this.permissions = {
allowStudentMoveAny: true,
Expand Down Expand Up @@ -108,12 +109,37 @@
this.tags = this.tags.filter((tag) => tag != tagRemove);
}

async compressFile() {
// const image = await this.fileUploadService.compressFile();
// this.bgImgURL = await this.fileUploadService.upload(image);
fabric.Image.fromURL(this.bgImgURL, async (image) => {
this.bgImgSettings = this.fabricUtils.createImageSettings(image);
});
uploadImage(): void {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.click();

input.onchange = () => {
const file = input.files?.[0];
if (file) {
this.fileUploadService.uploadImage(file).subscribe({
next: (response: any) => {

Check warning on line 122 in frontend/src/app/components/add-board-modal/add-board-modal.component.ts

View workflow job for this annotation

GitHub Actions / Linting and Code Formating Check CI (frontend)

Unexpected any. Specify a different type
console.log('Image uploaded successfully', response);
this.bgImgURL =
environment.ckboardDomain + '/api/image/' + response.imageUrl;
fabric.Image.fromURL(this.bgImgURL, async (image) => {
this.bgImgSettings = this.fabricUtils.createImageSettings(image);
});
},
error: (error) => {
console.error('Image upload failed', error);
},
});
}
};
}

onFileSelected(event: Event) {
const fileInput = event.target as HTMLInputElement;
if (fileInput.files) {
this.selectedFile = fileInput.files[0]; // Get the selected file
}
}

handleDialogSubmit() {
Expand Down
Loading
Loading