website/web/templates/photo/upload.html
Noah 27a7abaae7 Private Profiles & Misc Improvements
* Add setting to mark profile as "private"
* If a profile is private you can't see their profile page or user photo
  gallery unless you are friends (or admin)
* The Site Gallery never shows pictures from private profiles.
* Add HTML5 drag/drop upload support for photo gallery.
* Suppress SQL logging except in debug mode.
* Clean up extra logs.
2022-08-21 17:29:39 -07:00

512 lines
24 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 }}
<!-- Drag/Drop Modal -->
<div class="modal" id="drop-modal">
<div class="modal-background"></div>
<div class="modal-content">
<div class="box content has-text-centered">
<h1><i class="fa fa-upload mr-2"></i> Drop image to select it for upload</h1>
</div>
</div>
</div>
{{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"),
$dropArea = document.querySelector("#drop-modal")
$body = document.querySelector("body");
// Common handler for file selection, either via input
// field or drag/drop onto the page.
let onFile = (file) => {
$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);
};
// Set up drag/drop file upload events.
$body.addEventListener("dragenter", function(e) {
e.preventDefault();
e.stopPropagation();
$dropArea.classList.add("is-active");
});
$body.addEventListener("dragover", function(e) {
e.preventDefault();
e.stopPropagation();
$dropArea.classList.add("is-active");
});
$body.addEventListener("dragleave", function(e) {
e.preventDefault();
e.stopPropagation();
$dropArea.classList.remove("is-active");
});
$body.addEventListener("drop", function(e) {
e.preventDefault();
e.stopPropagation();
$dropArea.classList.remove("is-active");
// Grab the file.
let dt = e.dataTransfer;
let file = dt.files[0];
// Set the file on the input field too.
$file.files = dt.files;
onFile(file);
});
// Clear the answer in case of page reload.
$cropField.value = "";
$file.addEventListener("change", function() {
let file = this.files[0];
onFile(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}}