Noah
36ba8c5c4d
* Add photo upload quotas. * Non-certified users can upload few photos; certified users more * Fix foreign key issues around deleting user profile photos for psql
464 lines
22 KiB
HTML
464 lines
22 KiB
HTML
{{define "title"}}Upload a Photo{{end}}
|
|
{{define "content"}}
|
|
<div class="container">
|
|
<section class="hero is-info is-bold">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<h1 class="title">
|
|
{{if .EditPhoto}}
|
|
Edit Photo
|
|
{{else if eq .Intent "profile_pic"}}
|
|
Upload a Profile Picture
|
|
{{else}}
|
|
Upload a Photo
|
|
{{end}}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{ $User := .CurrentUser }}
|
|
|
|
{{if .EditPhoto}}
|
|
<form action="/photo/edit" method="POST">
|
|
<input type="hidden" name="id" value="{{.EditPhoto.ID}}">
|
|
{{else}}
|
|
<form action="/photo/upload" method="POST" enctype="multipart/form-data">
|
|
{{end}}
|
|
{{InputCSRF}}
|
|
<input type="hidden" id="intent" name="intent" value="{{.Intent}}">
|
|
|
|
<div class="block p-4">
|
|
<!-- Upload disclaimers, but not if editing a photo -->
|
|
{{if not .EditPhoto}}
|
|
<div class="content block">
|
|
<p>
|
|
You can use this page to upload a new photo to your profile. Please remember
|
|
the rules below:
|
|
</p>
|
|
|
|
<ul>
|
|
<li>
|
|
🤳 <strong>Self pictures only:</strong> you may only upload pictures which depict
|
|
<em>you</em> in them. If the picture also contains other people, be sure you
|
|
have their consent to post it here!
|
|
</li>
|
|
<li>
|
|
🔞 <strong>Mark whether your picture is explicit:</strong> not all nudists want to
|
|
see sexual content or close-up shots of genitalia. If your picture is not a
|
|
"normal nude" please check the Explicit box to help the rest of us out!
|
|
</li>
|
|
<li>
|
|
🧑 <strong>Your main profile picture must show your face:</strong> it doesn't have
|
|
to be a nude pic but your face needs to be in it. Additional photos uploaded to
|
|
your page do not need to require your face in them.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Quota notification -->
|
|
{{if not .EditPhoto}}
|
|
<div class="notification {{if ge .PhotoCount .PhotoQuota}}is-warning{{else}}is-info{{end}} block">
|
|
<p class="block">
|
|
You have currently uploaded <strong>{{.PhotoCount}}</strong> of your allowed {{.PhotoQuota}} photos.
|
|
{{if ge .PhotoCount .PhotoQuota}}
|
|
To upload a new photo, please <a href="/photo/u/{{.CurrentUser.Username}}">delete</a>
|
|
an existing photo first to make room.
|
|
{{end}}
|
|
</p>
|
|
|
|
<p class="block">
|
|
You may upload <strong>{{SubtractInt .PhotoQuota .PhotoCount}}</strong> more photo{{Pluralize (SubtractInt .PhotoQuota .PhotoCount)}}.
|
|
{{if not .CurrentUser.Certified}}
|
|
After your account has been <a href="/photo/certification">certified</a>, you will be able to upload
|
|
additional pictures.
|
|
{{end}}
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if or .EditPhoto (lt .PhotoCount .PhotoQuota)}}
|
|
<div class="columns">
|
|
<div class="column">
|
|
|
|
<div class="card">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-camera pr-2"></i>
|
|
{{if .EditPhoto}}
|
|
Your Photo
|
|
{{else}}
|
|
Select a Photo
|
|
{{end}}
|
|
</p>
|
|
</header>
|
|
|
|
<!-- Upload field, not when editing -->
|
|
{{if not .EditPhoto}}
|
|
<div class="card-content">
|
|
<p class="block">
|
|
Browse or drag a photo onto this page:
|
|
</p>
|
|
|
|
<div class="field block">
|
|
<div class="file has-name is-fullwidth">
|
|
<label class="file-label">
|
|
<input class="file-input" type="file"
|
|
name="file"
|
|
id="file"
|
|
accept=".jpg,.jpeg,.jpe,.png"
|
|
required>
|
|
<span class="file-cta">
|
|
<span class="file-icon">
|
|
<i class="fas fa-upload"></i>
|
|
</span>
|
|
<span class="file-label">
|
|
Choose a file…
|
|
</span>
|
|
</span>
|
|
<span class="file-name" id="fileName">
|
|
Select a file
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="box" id="imagePreview" style="display: none">
|
|
<h3 class="subtitle">
|
|
{{if .NeedsCrop}}Crop image:{{else}}Selected image:{{end}}
|
|
</h3>
|
|
|
|
{{if .NeedsCrop}}
|
|
<p class="block">
|
|
Select a square crop of this image for your profile picture. The full
|
|
image will go among the rest of your photos, and the square version
|
|
will be used as your profile pic and avatar.
|
|
</p>
|
|
{{end}}
|
|
|
|
<!-- Container of img tags for the selected photo preview. -->
|
|
<div id="previewBox" class="block"></div>
|
|
|
|
{{if .NeedsCrop}}
|
|
<div class="block has-text-centered">
|
|
<button type="button" class="button block is-info" onclick="resetCrop()">Reset</button>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Holder of image crop coordinates in x,y,w,h format. -->
|
|
<input type="hidden" name="crop" id="cropCoords">
|
|
</div>
|
|
{{else}}<!-- when .EditPhoto -->
|
|
<div class="card-content">
|
|
<figure id="editphoto-preview" class="image block">
|
|
<img src="{{PhotoURL .EditPhoto.Filename}}" id="editphoto-img">
|
|
</figure>
|
|
|
|
<div class="block has-text-centered" id="editphoto-begin-crop">
|
|
<button type="button" class="button" onclick="setProfilePhoto()">
|
|
<span class="icon"><i class="fa fa-crop-simple"></i></span>
|
|
<span>Set this as my profile photo (crop image)</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="block has-text-centered" id="editphoto-cropping" style="display: none">
|
|
<button type="button" class="button block is-info" onclick="resetCrop()">Reset</button>
|
|
</div>
|
|
|
|
<!-- Holder of image crop coordinates in x,y,w,h format. -->
|
|
<input type="hidden" name="crop" id="cropCoords">
|
|
</div>
|
|
{{end}}
|
|
|
|
</div><!-- /card -->
|
|
</div><!-- /column -->
|
|
|
|
<div class="column">
|
|
|
|
<div class="card">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-pencil pr-2"></i>
|
|
Photo Settings
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
|
|
<div class="field">
|
|
<label class="label" for="caption">Caption</label>
|
|
<input type="text" class="input"
|
|
name="caption"
|
|
id="caption"
|
|
placeholder="Caption"
|
|
value="{{.EditPhoto.Caption}}">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Explicit Content</label>
|
|
{{if eq .Intent "profile_pic"}}
|
|
<span class="has-text-danger">
|
|
Your default profile picture should
|
|
<strong class="has-text-danger">NOT</strong>
|
|
contain explicit content.
|
|
</span>
|
|
|
|
<p class="help">
|
|
Your default profile picture is about your face. You can have nudity
|
|
in it, too, but not a close-up shot of your genitals or sporting an
|
|
erection or engaging in sexual conduct. You can upload pictures like
|
|
that to your page, just not as your default profile picture!
|
|
</p>
|
|
{{else}}
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="explicit"
|
|
value="true"
|
|
{{if .EditPhoto.Explicit}}checked{{end}}>
|
|
This photo contains explicit content
|
|
</label>
|
|
<p class="help">
|
|
Mark this box if this photo contains any explicit content, including an
|
|
erect penis, close-up of genitalia, or any depiction of sexual activity.
|
|
Use your best judgment. "Normal nudes" such as full body nudes in a
|
|
non-sexual context do not need to check this box.
|
|
</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Photo Visibility</label>
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="public"
|
|
{{if or (not .EditPhoto) (eq .EditPhoto.Visibility "public")}}checked{{end}}>
|
|
<strong>Public:</strong> this photo will appear on your profile page
|
|
and can be seen by any logged-in user account. It may also appear
|
|
on the site-wide Photo Gallery if that option is enabled, below.
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="friends"
|
|
{{if eq .EditPhoto.Visibility "friends"}}checked{{end}}>
|
|
<strong>Friends only:</strong> only users you have added as a friend
|
|
can see this photo on your profile page.
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="private"
|
|
{{if eq .EditPhoto.Visibility "private"}}checked{{end}}>
|
|
<strong>Private:</strong> this photo is not visible to anybody except
|
|
for people whom you allow to see your private pictures (not implemented yet!)
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Site Photo Gallery</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="gallery"
|
|
value="true"
|
|
checked
|
|
{{if .EditPhoto.Gallery}}checked{{end}}>
|
|
Show this photo in the site-wide Photo Gallery (public photos only)
|
|
</label>
|
|
<p class="help">
|
|
Leave this box checked and your (public only) photo can appear in the site's
|
|
Photo Gallery page. If you uncheck this box, your (public) photo will still
|
|
appear on your profile page but not on the site photo gallery. Friends-only
|
|
or private photos never appear in the gallery even if this box is checked.
|
|
</p>
|
|
</div>
|
|
|
|
{{if not .EditPhoto}}
|
|
<div class="field">
|
|
<label class="label">Confirm Upload</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="confirm1"
|
|
value="true"
|
|
required>
|
|
I assert that this is a photo <strong>of myself</strong> and that I have
|
|
permission to upload this picture.
|
|
</label>
|
|
|
|
{{if eq .Intent "profile_pic"}}
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="confirm2"
|
|
value="true"
|
|
required>
|
|
I assert that this picture shows my face and is not explicit
|
|
</label>
|
|
{{else}}
|
|
<input type="hidden" name="confirm2" value="true">
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
{{if .EditPhoto}}
|
|
Save Changes
|
|
{{else}}
|
|
Upload Photo
|
|
{{end}}
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div><!-- /columns -->
|
|
{{end}}<!-- if under quota -->
|
|
</div>
|
|
</form>
|
|
|
|
<!-- image cropper -->
|
|
<!-- <script src="/static/js/jquery-3.6.0.min.js"></script> -->
|
|
<link rel="stylesheet" href="/static/js/croppr/croppr.min.css">
|
|
<script src="/static/js/croppr/croppr.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
var croppr = null;
|
|
const usingCroppr = {{if .NeedsCrop}}true{{else}}false{{end}};
|
|
|
|
function resetCrop() {
|
|
if (croppr !== null) {
|
|
croppr.reset();
|
|
}
|
|
}
|
|
|
|
{{if not .EditPhoto}}
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
let $file = document.querySelector("#file"),
|
|
$fileName = document.querySelector("#fileName"),
|
|
$hiddenPreview = document.querySelector("#imagePreview"),
|
|
$previewBox = document.querySelector("#previewBox"),
|
|
$cropField = document.querySelector("#cropCoords");
|
|
|
|
// Clear the answer in case of page reload.
|
|
$cropField.value = "";
|
|
|
|
$file.addEventListener("change", function() {
|
|
let file = this.files[0];
|
|
$fileName.innerHTML = file.name;
|
|
|
|
// Read the image to show the preview on-page.
|
|
const reader = new FileReader();
|
|
reader.addEventListener("load", () => {
|
|
const uploadedImg = reader.result;
|
|
$hiddenPreview.style.display = "block";
|
|
|
|
// Create a new <img> tag the first time.
|
|
if (croppr !== null) {
|
|
croppr.setImage(uploadedImg);
|
|
croppr.reset();
|
|
return;
|
|
}
|
|
|
|
// If not using croppr, flush the old img preview out.
|
|
if (!usingCroppr) {
|
|
$previewBox.innerHTML = "";
|
|
}
|
|
|
|
let img = document.createElement("img");
|
|
img.src = uploadedImg;
|
|
img.style.display = "block";
|
|
img.style.maxWidth = "100%";
|
|
img.style.height = "auto";
|
|
|
|
// Add it to the wrapper div.
|
|
$previewBox.appendChild(img);
|
|
|
|
if (usingCroppr) {
|
|
croppr = new Croppr(img, {
|
|
aspectRatio: 1,
|
|
minSize: [ 32, 32, 'px' ],
|
|
returnMode: 'real',
|
|
onCropStart: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropMove: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropEnd: (data) => {
|
|
// console.log(data);
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
},
|
|
onInitialize: (inst) => {
|
|
// Populate the default crop value into the form field.
|
|
let data = inst.getValue();
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
reader.readAsDataURL(file);
|
|
|
|
});
|
|
});
|
|
{{end}}
|
|
|
|
// EditPhoto only: a button to crop their photo to set as a profile pic.
|
|
function setProfilePhoto() {
|
|
let $begin = document.querySelector("#editphoto-begin-crop"),
|
|
$cropRow = document.querySelector("#editphoto-cropping"),
|
|
$preview = document.querySelector("#editphoto-preview")
|
|
$cropField = document.querySelector("#cropCoords"),
|
|
$intent = document.querySelector("#intent");
|
|
img = document.querySelector("#editphoto-img");
|
|
|
|
// Toggle the button display, from begin crop to the crop reset button.
|
|
$begin.style.display = 'none';
|
|
$cropRow.style.display = 'block';
|
|
|
|
// Set intent to profile-pic so when the form posts the crop coords will
|
|
// create a new profile pic for this user.
|
|
$intent.value = "profile-pic";
|
|
|
|
croppr = new Croppr(img, {
|
|
aspectRatio: 1,
|
|
minSize: [ 32, 32, 'px' ],
|
|
returnMode: 'real',
|
|
onCropStart: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropMove: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropEnd: (data) => {
|
|
// console.log(data);
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
},
|
|
onInitialize: (inst) => {
|
|
// Populate the default crop value into the form field.
|
|
let data = inst.getValue();
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
</div>
|
|
{{end}} |